准备好 node,pnpm 环境。
初始化项目
先创建一个空的文件夹langchain-demo,执行命令以下命令初始化一个项目。
cd langchain-demo/pnpm init
复制代码
接下来,我们准备一个测试数据,然后将数据摄取到向量数据库中。具体步骤如下:首先在根目录下创建一个 ingest-data.js然后在项目根目录下执行如下命令:
# 安装LangChainpnpm i langchain
复制代码
安装完成后:修改 package.json 文件,如图所示,增加"type": "module"。
读取数据
准备数据
先准备一个 markdown 格式的文件。可以直接在网上找一个,比如 Vue3 的官方文档。然后在页面文档选中一部分,打开 F12 通过输入命令$0.innerHTML并回车后即可获得数据。简单如图所示:
或者自行准备数据也可。将网页中爬取的文本内容 copy 到 html-to-markdown网站上,转换成 markdown。并将转换后的文件放在项目根目录下的vue3-document.md中,如图所示
读取数据
编辑我们在之前已经准备好的文件ingest-data.js,文件内容如下:
// 读取markdown文件import { UnstructuredLoader } from "langchain/document_loaders/fs/unstructured";
// 实例化 并传入数据的路径const unstructuredLoader = new UnstructuredLoader("./vue3-document.md");
// 读取文件,这是个promise 使用awaitconst docs = await unstructuredLoader.load()
console.log(docs)
复制代码
然后通过如下命令进行验证:
cd langchain-demo/node ingest-data.js
复制代码
执行成功结果如图所示:
切割数据
继续编写ingest-data.js具体如下:
// 1. 读取markdown文件import { UnstructuredLoader } from "langchain/document_loaders/fs/unstructured";
// 2. 文档拆分,将文档拆分成一块一块的// 2.1 导入langchain提供的拆分工具import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
// 1.1 实例化UnstructuredLoader 并传入数据的路径const unstructuredLoader = new UnstructuredLoader("./vue3-document.md");
// 1.2 读取文件,这是个promise 使用awaitconst rawDocs = await unstructuredLoader.load()// 1.3console.log(rawDocs)
// 2.2 实例化 RecursiveCharacterTextSplitterconst splitter = new RecursiveCharacterTextSplitter({ // 一段文本按照1000的大小去拆分 chunkSize: 1000, // 当前一段文本与上一段文本重叠的大小为200 chunkOverlap: 200});// 2.3 切分数据const docs = await splitter.splitDocuments(rawDocs);
// 打印console.log(docs);
复制代码
然后通过如下命令进行验证:
cd langchain-demo/node ingest-data.js
复制代码
成功结果如图所示:对比之前多了一个 loc: [Object]
接入向量数据库
这里选择的向量数据库是:pinecone具体可参考官网文档自行选择:
https://python.langchain.com/docs/get_started/introduction.html
根据官网安装手册进行安装
https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/pinecone
pip install pinecone-client openai tiktoken# dotenv用来读取环境变量 安装向量数据库pnpm install -S dotenv langchain @pinecone-database/pinecone
复制代码
将数据写入向量数据库
这里采用的是Pinecone向量数据库。首先我们要注册一个 Pinecone 的数据库。官网地址:
https://www.pinecone.io/
注册完成后我们需要创建一个Index,如图所示操作:
注意文中的数字 1536 不要写错。
同时我们要记住自己创建的Index Name并找到,API Key,以及environment 记下来,后面会用到。
完成上述操作后 接下来继续编辑ingest-data.js文件,如下所示:
// 1. 读取markdown文件import { UnstructuredLoader } from "langchain/document_loaders/fs/unstructured";
// 2. 文档拆分,将文档拆分成一块一块的// 2.1 导入 langchain 提供的拆分工具import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";// 利用这个将数据放入向量数据库里面import { PineconeStore } from "langchain/vectorstores/pinecone"import { OpenAIEmbeddings } from "langchain/embeddings/openai"
// 3. 导入向量数据库import { PineconeClient } from "@pinecone-database/pinecone";
// 读取环境变量import dotenv from "dotenv";dotenv.config();
// 1.1 实例化UnstructuredLoader 并传入数据的路径const unstructuredLoader = new UnstructuredLoader("./vue3-document.md");
// 1.2 读取文件,这是个promise 使用awaitconst rawDocs = await unstructuredLoader.load();// 1.3//console.log(rawDocs);
// 2.2 实例化 RecursiveCharacterTextSplitterconst splitter = new RecursiveCharacterTextSplitter({ // 一段文本按照1000的大小去拆分 chunkSize: 1000, // 当前一段文本与上一段文本重叠的大小为200 chunkOverlap: 200});// 2.3 切分数据const docs = await splitter.splitDocuments(rawDocs);
// 打印//console.log(docs);
// 3.1 初始化 pineconeconst pineconeClient = new PineconeClient();await pineconeClient.init({ apiKey: process.env.PINECONE_API_KEY, environment: process.env.PINECONE_ENV,});
const pineconeIndex = pineconeClient.Index(process.env.PINECONE_INDEX);
try { PineconeStore.fromDocuments(docs, new OpenAIEmbeddings(),{ // 向量数据库的信息 pineconeIndex, textKey: "text", namespace: "teach-vue3-document", });}catch (e) { console.log(e)}
复制代码
同时在项目根目录下创建一个.env文件,文件内容如下:
OPENAI_API_KEY="your openai key"PINECONE_API_KEY="pinecone api key"PINECONE_ENV="environment"PINECONE_INDEX="index name"
复制代码
完成上述操作后,执行如下命令:
cd langchain-demo/node ingest-data.js
复制代码
运行成功结果如图所示:
通过 Pinecone 的控制台我们可以看到,文档数据已经成功的写入到了向量数据库中。
构建 chains
创建一个chain.js,内容如下:
import { ConversationalRetrievalQAChain } from "langchain/chains";// 导入大语言模型import {OpenAI} from "langchain/llms/openai"import { PineconeClient } from "@pinecone-database/pinecone";import { PineconeStore } from "langchain/vectorstores/pinecone"import { OpenAIEmbeddings } from "langchain/embeddings/openai"// 读取环境变量import dotenv from "dotenv";dotenv.config();
// 创建大语言模型const model = new OpenAI({ // 准确度,越小越精准,越大越具有创造性 temperature: 0,});
// 创建向量数据库const pineconeClient = new PineconeClient();await pineconeClient.init({ apiKey: process.env.PINECONE_API_KEY, environment: process.env.PINECONE_ENV,});const pineconeIndex = pineconeClient.Index(process.env.PINECONE_INDEX);const pineconeStore = await PineconeStore.fromExistingIndex(new OpenAIEmbeddings(), { // 向量数据库的信息 pineconeIndex, textKey: "text", namespace: "teach-vue3-document",});
const chain = ConversationalRetrievalQAChain.fromLLM( model, pineconeStore.asRetriever(),{ // 返回信息是参考了什么资料的开关 sourceDocuments: returnSourceDocuments: true, });const res = await chain.call({ question: "vue3 如何为为 reactive() 标注类型 请给我写一个demo", // 聊天历史,也就是记忆的功能 chat_history: [],});
console.log(res);
// langchain 0.90版本这样写有问题,具体可参考:
// const secondRes = await chain.call({// question: "能否提供更多的demo",// // 聊天历史,也就是记忆的功能// chat_history: [// "vue3 如何为 ref() 标注类型 请给我写一个demo",// res.text// ],// });
console.log(secondRes);
复制代码
完成上述操作后,执行如下命令:
cd langchain-demo/node ingest-data.js
复制代码
运行结果如下:
其实我个人认为这个结果不如人意,但是至少这样做的方向是正确的。
构建后端
使用 koa-setup 构建后端
# 安装koa-setupnpm install -g koa-setup
koa-setup
复制代码
如图:
然后修改生成的server文件夹中的package.json文件中的 type 为 module。并添加**"dev": "nodemon index.js"**如图:
修改 server 文件夹下的index.js文件,如下:
import Koa from "koa"import Router from "koa-router"import { koaBody } from "koa-body";
// const Koa = require("koa")// const Router = require("koa-router")// const koaBody = require("koa-body")
const app = new Koa()
app.use(koaBody({ multipart:true}))
const router = new Router()router.get("/",(ctx)=>{ ctx.body = "hello server"})app.use(router.routes())
app.listen(8080,()=>{ console.log("open server localhost:8080")})
复制代码
安装 nodemon
cd server/pnpm i nodemon -D
复制代码
nodemon 类似于热部署,修改代码无需重新启动。
启动项目
启动成功:
构建 API
将chat.js ingest-data.js vue3-document.md .env文件移动到 server 文件夹下,如图:
然后修改 index.js 和 chat.js,详情如下:index.js
import Koa from "koa"import Router from "koa-router"import { koaBody } from "koa-body";import { chat } from "./chain.js";
// const Koa = require("koa")// const Router = require("koa-router")// const koaBody = require("koa-body")
const app = new Koa()
app.use(koaBody({ multipart:true}))
const router = new Router()router.get("/",(ctx)=>{ ctx.body = "hello server"})
// 编写接口router.post("/chat",async (ctx) => { const { message } = ctx.request.body; const result = await chat(message)
ctx.body = { data: result, state: 1, }})
app.use(router.routes())
app.listen(8080,()=>{ console.log("open server localhost:8080")})
复制代码
chat.js
import { ConversationalRetrievalQAChain } from "langchain/chains";// 导入大语言模型import {OpenAI} from "langchain/llms/openai"import { PineconeClient } from "@pinecone-database/pinecone";import { PineconeStore } from "langchain/vectorstores/pinecone"import { OpenAIEmbeddings } from "langchain/embeddings/openai"// 读取环境变量import dotenv from "dotenv";dotenv.config();
// 创建大语言模型const model = new OpenAI({ // 准确度,越小越精准,越大越具有创造性 temperature: 0,});
// 创建向量数据库const pineconeClient = new PineconeClient();await pineconeClient.init({ apiKey: process.env.PINECONE_API_KEY, environment: process.env.PINECONE_ENV,});const pineconeIndex = pineconeClient.Index(process.env.PINECONE_INDEX);const pineconeStore = await PineconeStore.fromExistingIndex(new OpenAIEmbeddings(), { // 向量数据库的信息 pineconeIndex, textKey: "text", namespace: "teach-vue3-document",});
const chain = ConversationalRetrievalQAChain.fromLLM( model, pineconeStore.asRetriever(),{ // 返回信息是参考了什么资料的开关 sourceDocuments: returnSourceDocuments: true, });
export async function chat(message) { const res = await chain.call({ question: message, // 聊天历史,也就是记忆的功能 chat_history: [], }); return res;}
复制代码
然后启动运行pnpm dev并使用 postman 访问接口 "/chat" 如图:
构建前端
安装组件tailwindcss
cd client/
pnpm install -D tailwindcss@latest postcss@latest autoprefixer@latest
# 执行initnpx tailwindcss init -p
# 安装axios 后续使用pnpm i axios
# 安装 marked marked 是一个流行的 JavaScript 库,用于将 Markdown 文本转换为 HTMLpnpm install marked
pnpm install --save-dev @types/marked
pnpm install typescript --save-dev
复制代码
在tailwind.config.js添加配置(参考官网:https://v2.tailwindcss.com/docs/guides/vue-3-vite)
purge: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
复制代码
修改 style.css 文件(参考官网:https://v2.tailwindcss.com/docs/guides/vue-3-vite)
@tailwind base;@tailwind components;@tailwind utilities;
复制代码
然后在App.vue中编写 Hello World。
<script setup></script>
<template> <div class="text-blue-400"> Hello World </div></template>
<style scoped></style>
复制代码
启动
页面如下:
接下来编写视图,(前端组件、js 等)viteproxy 文档:https://cn.vitejs.dev/config/server-options.html
源码获取:微信搜索【码上遇见你】回复【langchain-demo】
上述文章也是依据网络整理的。侵权删之。
评论