vue3 langchain.js node.js 实现语音聊天

2,397 阅读4分钟

前言

最近学习了一下langchain.js的使用,心血来潮,做了一个可以对话的小网页,主要的功能点有:

  • 语音识别
  • 调用大模型接口
  • 格式化处理大模型返回的数据

koa后端

这里的后端接口部分使用了nodekoa库,这里就不展示了,比较简单。

语音识别

首先看一下语音识别的功能, 这个我用了浏览器自带的语音识别功能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的官网,支持的语言是pythonjs

先定义一个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库,可以用来结构化的描述我们的数据结构,这样得到的结果就比较稳定。

展示

image.png

语音输入

image.png

连续对话

image.png

格式化输出

进阶

语音对话项目进阶,接入百度语音技术