前言
最近学习了一下langchain.js的使用,心血来潮,做了一个可以对话的小网页,主要的功能点有:
- 语音识别
- 调用大模型接口
- 格式化处理大模型返回的数据
koa后端
这里的后端接口部分使用了node的koa库,这里就不展示了,比较简单。
语音识别
首先看一下语音识别的功能,
这个我用了浏览器自带的语音识别功能webkitSpeechRecognition,使用起来很简单
// 创建一个语音识别的实例
const recognition = new webkitSpeechRecognition()
// 启动
recognition.start()
// 关闭
recognition.stop()
if ('webkitSpeechRecognition' in window) {
// 一些参数设置
recognition.lang = currentLang
recognition.continuous = true
recognition.interimResults = false
// 启动之后的监听
recognition.onresult = function (event) {
// 这样能得到识别出来的文字信息
transcript = Array.from(event.results)
.map((result) => result[0])
.map((result) => result.transcript)
.join('')
}
} else {
alert('Speech recognition is not supported by this browser.')
}
大模型调用
我用的是智谱ai,上一篇文章简单讲了一下智谱AI最简单的调用方法,在网页端进行的。
那么在这个小项目里面,肯定是要在后端进行大模型的调用,并且也不能像我们之前那样直接调用,我们这里用上了langchain.js来实现大模型调用相关的功能。
langchain
这是langchain的官网,支持的语言是python和js。
先定义一个zhipuAi的实例出来,后面的调用都用它。
import { ChatZhipuAI } from '@langchain/community/chat_models/zhipuai'
const secretKey = process.env.ZHIPU_API_KEY
export default new ChatZhipuAI({
model: 'GLM-4-Flash',
apiUrl: 'https://open.bigmodel.cn/api/paas/v4/chat/completions',
apiKey: secretKey
})
实现带有上下文的对话功能
这里的对话功能就实际上就是一个和chatbot,需要的就是增加一个本次聊天所有的上下文。
export const chat_chat = async (userInput, returnId) => {
const config = {
configurable: {
sessionId: 'a1'
}
}
if (returnId) {
} else {
messageHistories = {}
const prompt = ChatPromptTemplate.fromMessages([
[
'system',
`你是一个优秀的英文聊天助手,你的名字叫小识,现在你将根据输入的信息,进行连续的英文对话,注意:你必须用英文进行对话,请确保你的回答中没有中文`
],
['placeholder', '{chat_history}'],
['human', '{input}']
])
const chain = prompt.pipe(llm)
withMessageHistory = new RunnableWithMessageHistory({
runnable: chain,
getMessageHistory: async (sessionId) => {
if (messageHistories[sessionId] === undefined) {
messageHistories[sessionId] = new InMemoryChatMessageHistory()
}
return messageHistories[sessionId]
},
inputMessagesKey: 'input',
historyMessagesKey: 'chat_history'
})
}
const res = await withMessageHistory.invoke(
{
input: userInput
},
config
)
return new Promise((resolve, reject) => {
resolve(res.content)
})
}
这里主要用到了ChatPromptTemplate,定义模版,可以将每次的输入信息填充进去。
还用到了RunnableWithMessageHistory,用来把之前的聊天记录也就是上下文和当前的聊天结合到一块。
这样,一个chatbot也就实现了。
实现稳定的格式输出
普通的chatbot就是直接把返回的信息直接输出就好了,但是如果我们希望输入的是一个特定的并且稳定的格式,那么就需要借助额外的工具。
import { z } from 'zod'
export const chat_translate = async (userInput, returnId) => {
const examlpeSchema = z.object({
example_sentence: z
.string()
.describe('英文例句,输入为单词和短语的时候出现'),
example_translation: z.string().describe('英文例句的翻译')
})
const personSchema = z
.object({
english: z.string().describe('识别出来的英文'),
explain: z
.string()
.describe('完整的包含词性和中文解释,内容要全面,只保留中文部分'),
phonetic: z
.string()
.describe('单词的音标,输入为单词的时候出现,检查格式正确性')
.optional(),
examlpe: z.array(examlpeSchema).optional()
})
.describe('对提供的中文寻找发音相似的英文')
const parser = StructuredOutputParser.fromZodSchema(personSchema)
const prompt = PromptTemplate.fromTemplate(
`识别并翻译输入的{content}, 首先判断输入的是单词还是短语还是完整的句子。
如果是单词,回答中要有翻译,音标和例句;
如果是短语,回答中要有翻译,例句,不要带上音标;
如果是句子,就只返回英文和翻译,不需要带上例句;
如果输入的是中文,那么就将其完整的翻译成英文,规则同上
仔细检查你输入的英文和你返回的英文格式,大小写是否正确,如果不对,改正
Wrap the output in json tags\n{format_instructions}`
)
const partialedPrompt = await prompt.partial({
format_instructions: parser.getFormatInstructions()
})
const chain = partialedPrompt.pipe(llm).pipe(parser)
const res = await chain.invoke({ content: userInput })
return new Promise((resolve, reject) => {
resolve(res)
})
}
这里的用的zod库,可以用来结构化的描述我们的数据结构,这样得到的结果就比较稳定。
展示
语音输入
连续对话
格式化输出