大家好,我是双越老师,也是 wangEditor 作者。
我正开发一个 Node 全栈 AIGC 知识库 划水AI,包括 AI 写作、多人协同编辑。复杂业务,真实上线,大家可以去注册试用,围观项目研发过程。
开始
LangChain 是一个开发 LLM 应用的框架,是目前 LLM 开发最流行的解决方案之一。最早是 Python 开发的,后来也出来 JS 语言的,现在已经更新到 v0.3 版本。
在上篇博客中我使用 langChain.js 开发了一个基础的 AI Agent ,使用自然语言查询天气。
本文将继续使用 langChain.js 实现一个基础的 RAG 语义搜索,这也是开发 AI Agent 时常用的解决方案。
RAG 是什么
RAG - Retrieval Augmented Generation 检索增强生成,一般用于 LLM 整合知识库,模糊搜索非结构化数据,找到相似的结构以后,再交给 LLM 去处理,这样会大大增加搜索结果的准确性。

创建代码环境
新建一个 nodejs 代码库,安装 langchain 和 dotenv ,后面我们需要使用环境变量。
bash
npm i langchain @langchain/community @langchain/core dotenv
新建一个 rag.js
下文的代码都会在这个文件中写。
加载文档到向量数据库
先加载文档到内存,然后拆分文档内容为小 chunk ,再生成 embed 格式,最后存储到向量数据库。

加载文档
先准备一个 PDF 文档,不易太短(如几页的简历)也不易太长(如几百页的需求文档),没有的可以下载这个。
把 pdf 放在代码库,然后使用 new PDFLoader
加载到内存中。
js
import path from 'path'
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf'
const pdfPath = path.resolve('data/nke-10k-2023.pdf')
const loader = new PDFLoader(pdfPath)
const docs = await loader.load()
// console.log(docs.length)
// console.log(docs[0].metadata)
可以执行 node rag.js
打印一下 docs.length
拆分文档
把当前的文档拆分为更小的 chunk ,size 是 1000 字符,overlap 是相邻 chunk 可以重叠 200 字符。
js
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters'
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
})
const allSplits = await textSplitter.splitDocuments(docs)
// console.log('allSplits.length:', allSplits.length)
可以执行 node rag.js
打印 allSplits.length
,它会比 docs.length
更大,因为 chunk 拆分的更小了。
Embeddings
接下来要把刚拆分出来的 allSplit 转换为 embed 向量格式,然后才能存储到向量数据库。
langChain 内置了很多 text_embedding js.langchain.com/docs/integr...
它默认的是 OpenAIEmbeddings 但是我们国内网络不能用,我测试了其他几个推荐的,也都有各种问题,在这卡住了。
最后我找到了 Alibaba Tongyi 这个 embeddings 模型可以使用 js.langchain.com/docs/integr...
首先登录阿里云百炼平台去申请一个 API key ,然后写入 .env
文件中
ini
ALIBABA_API_KEY=xxxx
然后修改 rag.js
代码,把 allSplits
写入到 vectorStore 中
js
import { AlibabaTongyiEmbeddings } from '@langchain/community/embeddings/alibaba_tongyi'
import { MemoryVectorStore } from 'langchain/vectorstores/memory'
import 'dotenv/config'
const embeddings = new AlibabaTongyiEmbeddings({})
const vectorStore = new MemoryVectorStore(embeddings)
await vectorStore.addDocuments(allSplits)
然后可以写代码测试一下,从向量数据库中查询一个信息
js
const results1 = await vectorStore.similaritySearch(
'When was Nike incorporated?'
)
console.log('results1:', results1.length, results1[0])
执行 node rag.js
可以看到打印的结果,这是根据 PDF 内容搜索出来的答案。

检索和生成
以上是把文档内容转换并存储到 vectorStore 向量数据库了,接下来再加入 llm 进行检索并生成答案。

加载网页内容到 vectorStore
新建一个文件 rag2.js
,这次我们不用本地 PDF 文档了,而用 CheerioWebBaseLoader
去加载一个网页内容。
js
import 'cheerio'
import { CheerioWebBaseLoader } from '@langchain/community/document_loaders/web/cheerio'
// Load and chunk contents of blog
const pTagSelector = 'p'
const cheerioLoader = new CheerioWebBaseLoader(
'https://www.wangeditor.com/v5/development.html',
{
selector: pTagSelector,
}
)
const docs = await cheerioLoader.load()
// console.assert(docs.length === 1)
// console.log(`Total characters: ${docs[0].pageContent.length}`)
然后把内容拆分为小的 chunk 和之前一样
js
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters'
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
})
const allSplits = await splitter.splitDocuments(docs)
// console.log('allSplits.length:', allSplits.length)
然后转换为 Embeddings 并存储到向量数据库
js
import 'dotenv/config'
import { AlibabaTongyiEmbeddings } from '@langchain/community/embeddings/alibaba_tongyi'
import { MemoryVectorStore } from 'langchain/vectorstores/memory'
const embeddings = new AlibabaTongyiEmbeddings({})
const vectorStore = new MemoryVectorStore(embeddings)
await vectorStore.addDocuments(allSplits)
选择一个 LLM 大模型
langChain 集成了有很多 LLM 可供选择 js.langchain.com/docs/integr...
它默认推荐的是 OpenAI 但是在国内我们没法直接调用它的 API ,所以我当前选择的是 DeepSeek 。
注册登录 DeepSeek 创建一个 API key 并把它放在 .env
文件中
env
DEEPSEEK_API_KEY=xxx
安装 langChain deepseek 插件
css
npm i @langchain/deepseek
写代码,定义 llm
js
import { ChatDeepSeek } from '@langchain/deepseek'
const llm = new ChatDeepSeek({
model: 'deepseek-chat',
temperature: 0,
// other params...
})
定义 Agent 工作流
定义数据结构。在 Agent 工作流执行过程中,多个流程之间的数据传输,使用如下结构。
js
import { Annotation } from '@langchain/langgraph'
const StateAnnotation = Annotation.Root({
question: Annotation, // 用户输入的问题
context: Annotation, // 从 vectorStore 中检索出来的结果
answer: Annotation, // 最终输入给用户的结果
})
定义检索方法。通过用户输入的问题 state.question
,在 vectorStore 中检索相似数据 vectorStore.similaritySearch
,最终返回检索结果。
js
const retrieve = async (state) => {
console.log('retrieve... question: ', state.question)
const retrievedDocs = await vectorStore.similaritySearch(state.question)
return { context: retrievedDocs }
}
定义生成答案的方法。先远程获取一个 promptTemplate (也可以手写),然后结合 retrievedDocs
一起生成 messages
,最后调用 llm 生成自然语言的结果。
js
import { pull } from 'langchain/hub'
// 从 langChain hub 中获取 promptTemplate
const promptTemplate = await pull('rlm/rag-prompt')
// console.log('promptTemplate ', promptTemplate)
const generate = async (state) => {
console.log('generate... context: ', state.context.length)
const docsContent = state.context.map((doc) => doc.pageContent).join('\n')
const messages = await promptTemplate.invoke({
question: state.question,
context: docsContent,
})
const response = await llm.invoke(messages)
return { answer: response.content }
}
定义 workflow 工作流。定义两个节点 retrieve
和 generate
,再定义三个边用于连接这两个节点。
javascript
import { StateGraph } from '@langchain/langgraph'
const graph = new StateGraph(StateAnnotation)
.addNode('retrieve', retrieve)
.addNode('generate', generate)
.addEdge('__start__', 'retrieve')
.addEdge('retrieve', 'generate')
.addEdge('generate', '__end__')
.compile()
整体的流程图如下:

调用 Agent
定义 question
然后使用 invoke
方法调用 Agent
javascript
let inputs = { question: '什么是 ModalMenu?' }
const result = await graph.invoke(inputs)
console.log('res ', result.answer)
执行 node rag2.js
可以看到如下结果,先执行 retrieve 再执行 generate 最后返回结果

还可以使用 stream
流式输出,配合前端能力可以实现打字效果。
csharp
const stream = await graph.stream(inputs, { streamMode: 'messages' })
for await (const [message, _metadata] of stream) {
process.stdout.write(message.content + '|')
}
最后
在实际项目中,向量数据不能存储在内存中,还使用商用向量数据库,例如 Pinecone ,它可以免费试用。关注我,后续将继续分享 Agent MCP 等 AI 开发相关话题。
最后,前端想学全栈 + AI 开发,可以看看我做的 划水AI 项目,AI 写作、多人协同编辑。复杂业务,真实上线,可注册试用。