一、核心说明
你提供的代码是 LangChain.js 实现 RAG(Retrieval-Augmented Generation,检索增强生成) 的标准范例,核心是将「向量数据库的相似性检索」与「大模型生成」结合,确保回答严格基于知识库文本(避免模型幻觉)。整个流程实现了「用户提问→检索相关文本→拼接上下文→大模型回答」的端到端知识库问答能力。
二、代码核心逻辑拆解
整体执行流程
flowchart TD
A[加载文档并分割] --> B[文本块存入内存向量库]
C[用户提问] --> D[向量库检索Top1相关文本]
D --> E[格式化检索结果为上下文]
E --> F[拼接上下文+问题生成提示词]
F --> G[打印最终提示词]
G --> H[大模型基于上下文生成回答]
H --> I[输出回答结果]
完整带注释代码(可直接运行)
typescript
import { TextLoader } from "@langchain/classic/document_loaders/fs/text";
import { RecursiveCharacterTextSplitter } from "@langchain/classic/text_splitter";
import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";
import { OpenAIEmbeddings } from "@langchain/openai";
import { ChatOpenAI } from "@langchain/openai";
import type { Document } from "@langchain/core/documents";
import type { ChatPromptValueInterface } from "@langchain/core/prompt_values";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { RunnableLambda, RunnablePassthrough, RunnableSequence } from "@langchain/core/runnables";
// ===================== 1. 定义提示词模板(约束回答范围) =====================
// 核心:强制大模型只能基于{context}(检索到的知识库文本)回答{input}(用户问题)
const promptTemplate = ChatPromptTemplate.fromMessages([
["system", "你是一个专业的问答机器人,你的回答必须基于上下文,不能编造信息"],
["human", "知识:{context},问题:{input}"],
]);
// ===================== 2. 初始化大模型(通义千问) =====================
// 通义千问旗舰版模型
const chatModel = new ChatOpenAI({
model: "qwen-max",
// 设为0减少随机性,保证回答精准
temperature: 0,
configuration: {
// 阿里百炼OpenAI兼容接口
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
// 替换为个人有效API Key
apiKey: "[你的阿里百炼API Key]",
},
});
// ===================== 3. 初始化嵌入模型+内存向量库 =====================
// 嵌入模型:将文本转为数值向量,用于相似性检索
const embeddingsModel = new OpenAIEmbeddings({
// 通义千问文本嵌入模型(768维向量)
model: "text-embedding-v2",
configuration: {
// 阿里百炼OpenAI兼容接口
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
// 替换为个人有效API Key
apiKey: "[你的阿里百炼API Key]",
},
});
// 内存向量库:存储文本向量,支持相似性检索(程序重启后数据丢失)
const vectorStore = new MemoryVectorStore(embeddingsModel);
// ===================== 4. 加载并处理文档(存入向量库) =====================
// 加载TXT格式的知识库文档
const loader = new TextLoader("./data/data.txt");
const rawDocuments = await loader.load();
// 文本分割器:将长文本拆分为小文本块(适配嵌入模型上下文,提升检索精度)
const splitter = new RecursiveCharacterTextSplitter({
// 每个文本块最大字符数
chunkSize: 25,
// 块间重叠字符(保证语义连贯)
chunkOverlap: 5,
// 优先按中文标点分割,避免语义断裂
separators: [",", "。"],
});
// 分割文档并将文本块存入向量库(自动生成向量)
const splitDocuments = await splitter.splitDocuments(rawDocuments);
await vectorStore.addDocuments(splitDocuments);
// ===================== 5. 自定义Runnable组件(格式化/调试) =====================
// 5.1 格式化检索结果:将Document数组转为纯文本字符串(方便拼接提示词)
const formatDocuments = RunnableLambda.from((documents: Document[]): string => {
return documents.map(doc => doc.pageContent).join("\n"); // 多个文本块换行分隔
});
// 5.2 打印提示词:调试用,输出最终传给大模型的完整提示词
const printPrompt = RunnableLambda.from((input: ChatPromptValueInterface): ChatPromptValueInterface => {
console.log("【最终传给大模型的提示词】:\n", input.toString(), "\n");
return input; // 透传提示词,不修改内容
});
// ===================== 6. 构建检索器(向量库检索入口) =====================
// asRetriever(1):检索Top1最相似的文本块(可调整数字扩大检索范围)
const retriever = vectorStore.asRetriever(1);
// ===================== 7. 构建RAG链式调用 =====================
// 定义用户问题
const question: string = "李娟的出生于哪里?";
// 链式调用流程:
// 1. 并行处理:context=检索结果格式化,input=用户问题透传
// 2. 拼接提示词模板
// 3. 打印提示词(调试)
// 4. 大模型生成回答
const chain = RunnableSequence.from([
{
// 检索→格式化
context: retriever.pipe(formatDocuments),
// 透传用户问题
input: new RunnablePassthrough(),
},
// 拼接提示词
promptTemplate,
// 打印提示词
printPrompt,
// 大模型生成回答
chatModel,
]);
// ===================== 8. 执行链式调用并输出结果 =====================
const result = await chain.invoke(question);
console.log("【最终回答】:", result.content);
三、核心组件详解
1. 关键Runnable组件
| 组件 | 作用 | 核心说明 |
|---|---|---|
RunnablePassthrough |
透传数据 | 不修改输入,直接传递到下一个环节(此处用于保留用户问题) |
RunnableLambda |
自定义逻辑 | 封装格式化、打印等自定义函数,融入链式调用 |
RunnableSequence |
链式执行 | 按顺序执行多个Runnable,前一个输出作为后一个输入 |
retriever.pipe(formatDocuments) |
管道组合 | 检索结果直接传入格式化函数,简化嵌套调用 |
2. 核心代码段解析
(1)检索器构建
typescript
// asRetriever(k):k表示返回最相似的k个文本块,此处设为1表示只取最相关的1条
// 检索器本质是封装了similaritySearch方法,更适配LangChain的Runnable体系
const retriever = vectorStore.asRetriever(1);
(2)链式调用数据流转
typescript
{
// 执行时:用户问题→检索器→Top1文本块→格式化字符串
context: retriever.pipe(formatDocuments),
// 执行时:直接等于用户问题(李娟的出生于哪里?)
input: new RunnablePassthrough(),
}
- 执行时会并行生成两个字段,作为参数传入
promptTemplate,替换{context}和{input}占位符。
(3)提示词最终效果
假设检索到的文本块是 李娟,1979年7月出生于新疆生产建设兵团,则最终提示词为:
makefile
System: 你是一个专业的问答机器人,你的回答必须基于上下文,不能编造信息
Human: 知识:李娟,1979年7月出生于新疆生产建设兵团,问题:李娟的出生于哪里?
四、运行效果示例
输入输出示例
yaml
【最终传给大模型的提示词】:
System: 你是一个专业的问答机器人,你的回答必须基于上下文,不能编造信息
Human: 知识:李娟,1979年7月出生于新疆生产建设兵团,问题:李娟的出生于哪里?
【最终回答】: 李娟于1979年7月出生于新疆生产建设兵团。
五、关键优化与注意事项
1. RAG核心优化点
- 检索参数调整 :
asRetriever(1)的k值可根据需求调整(如设为3,取Top3相关文本),平衡检索全面性和回答精准度; - 提示词模板优化:强化「仅基于给定上下文回答」的约束,例如添加「如果上下文没有相关信息,直接回复'未查询到相关内容',不要编造」;
- 文本分割优化 :根据知识库文档类型调整
chunkSize(如长文档设为50-100字符),保证检索到的文本块语义完整;
2. RAG常见问题排查
- 检索不到结果:检查文本分割是否过细、嵌入模型与向量库是否兼容、用户问题表述是否与文档语义匹配;
- 模型回答偏离上下文 :优化提示词模板的约束性,或增大检索
k值补充更多相关上下文; - 提示词拼接异常 :确保格式化函数正确处理空检索结果(如返回「无相关知识」),避免提示词缺失
context字段。
六、总结
- 该代码是 LangChain.js 实现 RAG 的标准范式,核心是「检索+生成」结合,解决大模型幻觉问题;
RunnableSequence是链式调用的核心,通过pipe/passthrough实现灵活的数据流控制;- 文本分割的「中文标点分隔」和「块重叠」是保证中文语义完整性,提升RAG检索精度的关键;
- 检索器的
k值、提示词模板的约束性,直接决定RAG最终回答的精准度和可靠性。