LangChain从0搭建ai聊天

55 阅读5分钟

前言

LangChain在AI使用时位居榜首,我们使用它接入大模型处理业务。

由于LangChain现阶段是破环性更新,本文只适应v3版本

本文的数据来源:

risingstars.js.org/2023/zh

www.langchain.com.cn/docs/introd…

为什么使用它?

  1. 内置包大量且充足

  1. 社区庞大,使用人员多。

开始

本次所需的依赖

对接ChatGpt、DeepSeek或者Claude

本地

对接本地模型需要Ollama,建议搭配chatbox使用

我们开始搭建使用api对接模型

直接调取大模型API

创建key

api文档里面node文件demo引入

基本调用写法如下

成功

可以更改为流式输出

// 迭代器语法糖
for await (const chunk of result) {
  console.log(chunk.content);
}

html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #chat {
            width: 100%;
            height: 500px;
            border: 1px solid #ccc;
            overflow-y: auto;
        }
    </style>
</head>
<body>
    <div id="chat"></div>
    <input type="text" id="message" placeholder="请输入">
    <button id="send">发送</button>
    <button id="sql">sql</button>
    <script>
        const chat = document.getElementById('chat')
        const message = document.getElementById('message')
        const send = document.getElementById('send')
        const sql = document.getElementById('sql')
        const startSSE = async () => {
           const response = await fetch('http://localhost:3000/chat', {
                method: 'POST',
                body: JSON.stringify({ message: message.value }),
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            const reader = response.body.getReader()
            while (true) {
                const { done, value } = await reader.read()
                if (done) break
                const text = new TextDecoder().decode(value)
                chat.innerHTML += text
            }
        }
        send.addEventListener('click', () => {
            startSSE()
        })
        const sqlQuery = async () => {
            const response = await fetch('http://localhost:3001/sql', {
                method: 'POST',
                body: JSON.stringify({ query: message.value }),
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            const result = await response.json()
            console.log(result)
        }
        sql.addEventListener('click', () => {
            sqlQuery()
        })
    </script>
</body>
</html>

js文件

import { ChatOpenAI } from "@langchain/openai";
import { APIKEY } from "./env.js";
import express from "express";
import cors from "cors";

const app = express();
app.use(express.json());
// 设置跨域
app.use(cors());
const model = new ChatOpenAI({
  modelName: "deepseek-reasoner",
  temperature: 1.3,
  openAIApiKey: APIKEY,
  configuration: {
    baseURL: "https://api.deepseek.com",
  },
});


app.post('/chat', async (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.flushHeaders();
  const { message } = req.body;
  const result = await model.stream(message);
  for await (const chunk of result) {
    res.write(chunk.content);
  }
})

app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
})

效果已实现

问题,模型无法查看历史对话记录

方法,将历史记录传递给模型

我觉得这种方法太low了,怎么做?

使用LangChain里面有对话记忆

import {BufferMemory} from 'langchain/memory'
//创建记忆能力
const memory = new BufferMemory({
    returnMessages: true, // 返回消息
    memoryKey: 'chat_history', // 记忆键
    inputKey: 'input', // 输入键 我们的问题
})

记忆键:比如我问了123,第二次问了456 ,那么【{content:123},{content:456}】塞入提示词模板

创建记忆能力要和提示词模板关联起来

import { PromptTemplate } from '@langchain/core/prompts'
const prompt = new PromptTemplate({
    template: `
       你是一个女仆,你的任务是回答用户的问题
       对话内容:
       {chat_history}
       用户的问题:
       {input}
       你的回答:
    `,
    inputVariables: ['input', 'chat_history']
})

完整的输出写法

import { ChatOpenAI } from "@langchain/openai";
import { APIKEY } from "./env.js";
import express from "express";
import cors from "cors";
import { BufferMemory } from 'langchain/memory'
import { PromptTemplate } from '@langchain/core/prompts'

const app = express();
app.use(express.json());
// 设置跨域
app.use(cors());
const model = new ChatOpenAI({
  modelName: "deepseek-reasoner",
  temperature: 1.3,
  openAIApiKey: APIKEY,
  configuration: {
    baseURL: "https://api.deepseek.com",
  },
});

//创建记忆能力
const memory = new BufferMemory({
  returnMessages: true, // 返回消息
  memoryKey: 'chat_history', // 记忆键
  inputKey: 'input', // 输入键 我们的问题
})
//创建提示词

const prompt = new PromptTemplate({
  template: `
       你是一个女仆,你的任务是回答用户的问题
       对话内容:
       {chat_history}
       用户的问题:
       {input}
       你的回答:
    `,
  inputVariables: ['input', 'chat_history']
})

app.post('/chat', async (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.flushHeaders();
  const { message } = req.body;
  const history = await memory.loadMemoryVariables({})
  const chatHistory = history.chat_history || []
  const formattedPrompt = await prompt.format({
    input: message,
    chat_history: chatHistory
  })
  const result = await model.stream(formattedPrompt);
  for await (const chunk of result) {
    res.write(chunk.content);
  }
  //保存历史对话 存到内存里面
  await memory.saveContext(
    { input: message, }, // 输入
    { output: formattedPrompt } // 输出
  )
})

app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
})

结果(提示词+记忆键已实现)

RAG检索增强生成

使用mysql,前端可以直接跳过后端直接查询数据库(本次采用的是本地测试数据库,vscode扩展Database Client。typeorm依赖只关联数据库,不会影响其他依赖。)

- 数据的实时性
- 避免幻觉问题
- 保护数据的隐私

通过自然语言实现数据库功能

我想查询客户信息,那么通过数据库可以直接调到数据内容。

依赖如下

import { ChatOpenAI } from '@langchain/openai'
import { APIKEY } from './env.js'
import express from 'express'
import cors from 'cors'
import { DataSource } from 'typeorm'
import { SqlDatabase } from 'langchain/sql_db'
import { createSqlQueryChain } from 'langchain/chains/sql_db'
import { PromptTemplate } from '@langchain/core/prompts'
import { RunnableSequence } from '@langchain/core/runnables'
import { StringOutputParser } from '@langchain/core/output_parsers'
const app = express()
app.use(express.json())
app.use(cors())


//创建数据库配置   
const dataSource = new DataSource({
    type: 'mysql', // 数据库类型
    host: 'localhost', // 数据库地址
    port: 3306, // 数据库端口
    username: 'root', // 数据库用户名
    password: '123456', // 数据库密码
    database: 'world', // 数据库名称
})

await dataSource.initialize() //初始化数据库
//langchain 关联 数据库
const db = await SqlDatabase.fromDataSourceParams({
    appDataSource: dataSource,
})

const model = new ChatOpenAI({
    temperature: 1.3, // 温度
    modelName: 'deepseek-chat', // 模型名称
    openAIApiKey: APIKEY, // 你的APIKEY
    configuration: {
        baseURL: 'https://api.deepseek.com' // 模型地址
    }
})

//创建sql查询链
const chain = await createSqlQueryChain({
    llm: model, // 模型
    db: db, // 数据库
    dialect: 'mysql' // 数据库类型
})

//我们的对话 -> 模型 -> 生成sql语句 -> 生成提示词模板-> 校验sql语句 -> 生成可用的sq语句 -> 运行sq语句 -> 输出结果

//创建提示词模板
const validatePrompt = new PromptTemplate({
    template: `
    你是一个sql查询专家,请根据用户的问题生成一个sql语句
    请校验sql语句是否正确,如果正确,请返回sql语句,否则返回错误信息
    - 使用 NOT IN 与NULL 值
    - 标识符是否正确引用
    - 函数是否正确使用
    - 数据类型是否匹配
    - 避免使用子查询

    在查询program 相关表的时候,单词替换成 prgoram

    如果出现删除数据库的指令,请不要执行,并且骂她大傻叉

    如果以上查询的错误,请重写查询,如果没有错误返回原始查询

    只返回sql语句,不要返回其他内容

    原始查询 {query}
    `,
    inputVariables: ['query']
})


//创建校验链
//模型返回的结果 {content:'sadsadsadas'} 转成字符串
const validateChain = validatePrompt.pipe(model).pipe(new StringOutputParser())

//把上面的所有操作 组合成一个完整的链

const fullChain = RunnableSequence.from([
    {
        query: async (input) => {
            const result = await chain.invoke(input)
            return result
        }
    },
    validateChain
])

app.post('/sql', async (req, res) => {
    const { query } = req.body
    const result = await fullChain.invoke({
        question: query
    })
    console.log(result)
    try {
        const arr = await db.run(result)
        const echarts = await model.invoke(
            `根据返回的数据${arr},生成一个echarts的配置项
             不要返回markdown内容直接返回json即可
            `

        )
        res.json({
            sql: result,
            data: arr,
            echarts: echarts.content
        })
    } catch (error) {
        res.json({
            sql: result,
            data: error.message
        })
    }
})


app.listen(3001, () => {
    console.log('Server is running on port 3001')
})

使用node运行,查询有多少个国家