RAG 不是让模型"学"新知识,而是让模型"查"新知识。 它把"记忆"和"推理"拆开------知识存在向量数据库里,推理交给大模型。模型不再需要记住一切,只需要学会"先查后答"。
为什么 Agent 需要外挂知识库
大模型有两个天然缺陷:
知识滞后:GPT-4 的训练数据截止到 2024 年,它不知道 2025 年的新闻、2026 年的政策、你公司昨天刚发布的内部文档。
幻觉:当模型不知道答案时,它不会说"我不知道",而是会编一个看起来合理的答案。问它"《红楼梦》里林黛玉的手机号是多少",它可能真的给你编一个。
这两个问题,微调解决不了------微调是让模型"记住"知识,但知识会变、会过期、会冲突。正确的解法是:把知识从模型里抽出来,放到外部存储里,让模型按需查询。
这就是 RAG(Retrieval-Augmented Generation,检索增强生成)。
RAG 的工作流程
RAG 分两条线:
索引线(离线) :把文档切成小块 → 每块转成向量 → 存进向量数据库。这一步只做一次,或者文档更新时重跑。
查询线(在线) :用户提问 → 问题转成向量 → 在向量库里搜最相似的文档块 → 把搜到的内容拼进 prompt → 模型基于这些内容回答。
核心公式:
ini
最终 prompt = 系统指令 + 检索到的文档片段 + 用户问题
模型不再是"凭记忆回答",而是"基于给定材料回答"。材料里没有的,它就老实说不知道。
手写一个最小 RAG
不用任何框架,50 行代码就能跑通:
javascript
import { OpenAIEmbeddings, ChatOpenAI } from '@langchain/openai';
import { MemoryVectorStore } from 'langchain/vectorstores/memory';
import { Document } from '@langchain/core/documents';
// 1. 准备知识
const docs = [
new Document({
pageContent: 'LangChain 1.0 的 create_agent 底层使用 LangGraph 作为运行时引擎。',
metadata: { source: 'blog' },
}),
new Document({
pageContent: 'LangGraph 的核心概念是 State(状态)、Node(节点)、Edge(边)。',
metadata: { source: 'docs' },
}),
new Document({
pageContent: '新版 LangChain 记忆机制使用 Checkpointer 实现持久化,支持 SQLite 和 PostgreSQL。',
metadata: { source: 'docs' },
}),
];
// 2. 建索引
const embeddings = new OpenAIEmbeddings({ model: 'text-embedding-3-small' });
const vectorStore = await MemoryVectorStore.fromDocuments(docs, embeddings);
// 3. 查询
const model = new ChatOpenAI({ model: 'gpt-4o-mini' });
const question = 'LangGraph 有哪些核心概念?';
const results = await vectorStore.similaritySearch(question, 2);
const context = results.map(d => d.pageContent).join('\n');
const prompt = `你是技术助手。请基于以下材料回答问题,材料里没有的信息不要编造。
材料:
${context}
问题:${question}`;
const answer = await model.invoke(prompt);
console.log(answer.content);
// 输出:LangGraph 的核心概念包括 State(状态)、Node(节点)和 Edge(边)。
关键点:
similaritySearch(question, 2):把问题转成向量,在库里找最相似的 2 个文档块context:检索到的材料拼进 prompt,模型基于这些材料回答- 材料里没有的信息不要编造:这句系统指令是防幻觉的关键
接入 LangGraph:让 RAG 变成 Agent 的工具
前面的 RAG 是"一问一答"模式。把它变成 Agent 的工具,就能实现"模型自主决定什么时候查知识库":
php
import { tool } from '@langchain/core/tools';
import { createAgent } from '@langchain/core/agents';
// 定义检索工具
const knowledgeSearch = tool(
async ({ query }) => {
const results = await vectorStore.similaritySearch(query, 3);
return results.map(d => d.pageContent).join('\n');
},
{
name: 'knowledge_search',
description: '查询公司内部知识库,适用于技术文档、政策规定、内部流程等问题',
schema: z.object({ query: z.string() }),
}
);
// 创建带 RAG 工具的 Agent
const agent = createAgent({
model: new ChatOpenAI({ model: 'gpt-4o-mini' }),
tools: [knowledgeSearch],
systemPrompt: '你是技术助手。遇到知识类问题时,优先使用 knowledge_search 工具查询知识库。',
});
// 调用
const result = await agent.invoke({
messages: [new HumanMessage('LangGraph 的核心概念是什么?')],
});
模型看到问题后,会自主判断:"这个问题需要查知识库" → 调用 knowledge_search 工具 → 拿到检索结果 → 基于结果组织回答。
RAG 从"固定流程"变成了"自主决策" ------模型自己决定什么时候查、查什么、怎么整合检索结果。
生产环境的三个坑
1. 分块策略决定检索质量
文档怎么切,直接影响检索效果:
- 固定字符数切分(如 500 字一块):简单,但可能把完整语义切断
- 按段落/标题切分:保留语义完整性,但块大小不均匀
- 重叠切分(如 500 字一块,重叠 50 字):兼顾完整性和召回率,生产首选
LangChain 提供了 RecursiveCharacterTextSplitter,支持递归按字符、词、段落切分:
ini
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 500,
chunkOverlap: 50,
});
const chunks = await splitter.splitDocuments(docs);
2. 混合检索比纯向量检索更准
纯向量检索的问题是:它只懂"语义相似",不懂"关键词匹配"。
问"LangGraph State 的 Reducer 是什么",向量检索可能召回一堆讲"状态管理"的无关内容,因为语义相近但关键词不匹配。
混合检索 = 向量检索(语义匹配)+ BM25 关键词检索(精确匹配)+ 重排序(Reranker)。三者结合,召回率和准确率都更高。
LangChain 的 EnsembleRetriever 支持这种组合:
ini
import { EnsembleRetriever } from 'langchain/retrievers/ensemble';
const ensembleRetriever = new EnsembleRetriever({
retrievers: [vectorRetriever, bm25Retriever],
weights: [0.7, 0.3], // 向量检索权重 0.7,关键词检索权重 0.3
});
3. 引用溯源防幻觉
模型基于检索结果回答后,最好标注"这个答案来自哪篇文档",方便用户核实:
javascript
const context = results
.map((d, i) => `[${i + 1}] ${d.pageContent} (来源:${d.metadata.source})`)
.join('\n');
输出时带上 [1]、[2] 这样的引用标记,用户点击就能看到原文。这不仅是防幻觉,也是建立信任。
RAG 与记忆的分工
很多人会混淆 RAG 和记忆,其实两者解决的是不同问题:
| 维度 | 记忆(Checkpoint) | RAG |
|---|---|---|
| 存储内容 | 对话历史、用户偏好 | 外部知识、文档资料 |
| 更新频率 | 每轮对话自动更新 | 文档更新时手动/定时更新 |
| 查询方式 | 按 thread_id 直接读取 | 向量相似度搜索 |
| 作用 | 让 Agent 记住"之前聊过什么" | 让 Agent 知道"训练数据里没有的东西" |
记忆解决"上下文连续性",RAG 解决"知识完备性" 。两者配合,Agent 才既记得住、又懂得多。
小结
- RAG 的核心思路:把知识从模型里抽出来,放到外部向量库,让模型"先查后答"
- 两条线:索引线(文档→向量→入库)和查询线(问题→向量→检索→拼 prompt→回答)
- 接入 LangGraph:把检索变成 Agent 的工具,让模型自主决定什么时候查
- 生产三坑:分块策略、混合检索、引用溯源
- RAG vs 记忆:记忆管"对话历史",RAG 管"外部知识",分工不同