1. 先搞清楚:Agent 为什么需要一条“知识获取管道”
聊到 AI Agent,很多人的第一反应是让它规划任务、调用工具、拆解目标。这没有错,但有个很现实的问题:LLM 在训练时已经把参数“冻结”了,它知道的只有预训练阶段的数据,对你的私有文档、最新知识、实时数据一概不知。早期我自己搭 Agent 时踩过最深的坑,就是把模型本身的记忆当成了全部知识来源,结果 Agent 在回答内部系统问题时自信满满地编造出了根本不存在的 API 字段。
RAG(Retrieval-Augmented Generation,检索增强生成)解决的就是这个问题:给 LLM 加一条可随时查询的外部知识通道,让它在回答前先从专有知识库中检索相关片段,再结合上下文生成答案。放在 Agent 体系里,这条通道就是知识获取管道,它决定了 Agent 的“知识底盘”稳不稳。没有这条管道,Agent 就像只带了大脑没带图书馆通行证的学生;有了它,Agent 才能在拿到具体任务时,按需查证、按证回答。
这篇文章是系列第四篇,前面几篇我们聊了基础架构、工作流设计和工具调用。今天重点拆解 RAG 这条知识获取管道的基础实现,从核心环节、参数设计、实操流程到问题排查,一并讲透。适合刚搭完 Agent 雏形、想让它在真实业务中“靠谱回答”的开发者,也适合准备基于 LangChain 或 Spring AI 这类框架做知识库集成的朋友,看完可以直接照搬思路。
1.1 RAG 并不是简单的“搜索 + 拼接”
很多人第一次接触 RAG,直觉里把它理解成“搜文档,拼上下文,丢给 LLM”。逻辑上没错,但实际操作中,这种理解会坑死你。真正的 RAG 管道,在工程上至少包含索引构建、检索召回、重排融合、生成增强四个阶段,每个阶段都有独立的设计变量。比如分块长度怎么切才合适,嵌入模型用哪个领域表现更好,top-k 取多少能平衡信息量和噪声,这些问题不拆开处理,直接堆在一起调参,很容易陷入“什么都调了但什么都很差”的困境。
我在实践中的一个类比是:RAG 不是给模型配词典,而是给它配一个受过训练的图书管理员。管理员(索引与检索)负责快速定位书架,检索到的书页(chunks)经过筛选(rerank)后才摆到模型桌上,模型再结合问题写下回答。管理员找的书不对、页码切得太碎、筛选环节缺失,最后桌上的材料再好也白搭。
这套管道放进 Agent 里还有个特殊之处:Agent 通常会根据当前目标动态生成多个检索意图,而不是像传统问答那样一次性检索。也就是说,Agent 可能先查一个概念的背景,再用结果决定下一步检索下去的方向。这个“检索→思考→再检索”的循环,就是热词里常说的 Agentic RAG 的核心玩法,也是后面几篇文章要展开的内容。而理解基础的管道,是这一切的前提。
1.2 为什么不能全靠长上下文硬撑
有朋友会说:既然 LLM 现在上下文窗口动辄几十万 token,直接把知识库塞进 System Prompt 不就行了?这个问题我当时也纠结过,但实测下来有三个绕不过去的坎。
第一是成本问题。主流模型的 token 计费是随输入长度线性增长的,把动辄几百 MB 的文档全塞进上下文,每次请求都在为海量无关内容付费。RAG 检索只取最相关的几千 token 进上下文,成本可能差两个数量级。
第二是干扰问题。长上下文并非越长越聪明,模型在无关信息包围下反而更容易丢失关键线索,这在当时用 128k 上下文实测时表现尤其明显。知识库里 1000 条文档,真正回答当前问题可能只需要其中 3 条,剩下的 997 条全是噪声,只会干扰模型注意力。
第三是时效性问题。文档是持续更新的,RAG 可以按天增量更新索引,而预训练模型的参数是固定的,长上下文方式只能每次重新灌入最新全文。Agent 一旦依赖固定的预训练知识,遇到业务规则变更就只能干瞪眼,这在企业场景里尤其致命。
所以结论很清楚:RAG 不是长上下文的替代品,而是长上下文的高性价比补充。Agent 内部真正的知识底座,应该是 RAG 管道而不是上下文窗口。
2. 核心环节拆解:索引、检索、生成一个都不能少
基础 RAG 管道可以拆成离线与在线两条链路。离线链路负责把原始文档切成块、算向量、建索引;在线链路负责把用户问题向量化、检索、重排、交给 LLM 生成。下面逐个环节展开。
2.1 文本切分:块大小与重叠率怎么定
文本切分是最容易被低估的环节。很多人上来就用固定长度硬切,结果语义被拦腰截断,检索到的块牛头不对马嘴。块的大小直接决定了两件事:切出来的块是否保留了完整的语义单元,以及检索结果是否足够精准。
块太小(比如 100 token),一个完整的结论往往被拆到多个块里,检索命中一个片段但回答缺少上下文;块太大(比如 2000 token),噪音多,向量表示的语义被稀释,检索精度直线下降。我个人在中文文档实践的经验值:通用场景 400-800 token 是甜区,代码文档可以稍微加密到 300-500 token,技术方案类文本建议按章节结构切而不是纯按字数切。
重叠率也很关键。固定窗口切分,边界处语义容易断裂,所以相邻块之间留 10%-20% 的重叠 token。例如块大小 500、重叠 50,能让边界上下文保持连续。实测下来,重叠率低于 5% 时,常见问题的检索命中率会下降 5%-10%,这个损失在检索环节很难再找回来。
一个可用参考配置:中文文档按语义段落分割,段落超长时再按字数二次切分,块大小 600 token、重叠 80 token。如果你的文档结构感强,优先用 Markdown 标题或章节层级切分;如果只是 PDF 导出的纯文本,则固定长度切分加适当重叠是更稳的方案。
2.2 嵌入模型:文本向量化的关键选择
文本切完之后,每个块要转成向量才能进向量库做相似度检索,这一步叫嵌入(Embedding)。嵌入模型的选型直接决定了 RAG 系统检索的“手感”。
选 embedding 模型有几个关键指标:向量维度、最大输入长度、中文效果、推理速度和成本。维度不是越大越好,高维度存储和计算成本高,且有时小维度模型检索效果反而更集中。中文场景下,通用型模型如 text2vec 系列、BGE 系列、m3e 等都是常见选择,实际效果要拿自己的语料评测,不能只看公开 benchmark。因为公开 benchmark 测的是通用语义,而你的业务术语、缩写、特有表达才是检索质量的真正考验。
有个容易被忽略的点:用户查询往往比文档块短得多,语义表达方式也不同。所以很多项目里会为“查询侧”和“文档侧”分别配置不同的嵌入模型或查询改写逻辑。基础阶段可以先共用同一个模型,但你要知道这个优化方向存在。
我实际偏好的做法:先用一个中等规模模型跑通全流程,再拿业务样本跑一遍 hit-rate 评测,看 top-5 召回是否覆盖正确结果。如果召回差,再升级更强模型并同步对比,这样每一步都有数据支撑。
2.3 向量检索与重排:召回不是终点
向量库负责用余弦相似度或内积距离找最相似的 top-k 块。这里有两个关键参数:相似度阈值和召回数量 top-k。
top-k 太小,检索结果信息量不够,回答容易断章取义;top-k 太大,无关块混进来,模型分不清重点。我的经验值:单轮问答取 4-6 块,多跳推理 Agent 场景可以适度放大到 8-12 块,但要在提示词中明确让模型忽略无关内容。
重排(Rerank)是基础管道里最值得早期投入的环节。向量召回本质是语义初筛,排在前面的块不一定是对问题最有用的块。用一个跨编码器模型对向量召回结果做精排,能明显改善最终回答质量。我在实践中,加了 rerank 之后回答准确率大约提升 10%-15%,这个收益在复杂问题上尤其明显。
检索时还要注意元数据过滤。比如按时间范围、文档类型、权限维度先行筛选,这能降低向量检索的候选集大小。多轮对话场景下,建议把历史对话压缩成当前意图再去检索,而不是把整段历史直接叠加。这些都是基础管道里容易提前规划好的细节。
2.4 生成环节:Prompt 模板与上下文组装
检索到的块最终要变成一个“有引用、可追溯、不硬编”的 prompt。生成环节的设计目标是减少幻觉、提升回答的可信度。
基础 prompt 模板至少包含三块:任务指令(根据提供的上下文回答问题)、检索到的文档块(标注来源)、用户问题。这里的关键是约束模型:如果文档块中没有答案,必须明确说不知道,不得编造。这一步看似简单,但很多 AI 幻觉问题其实都出在缺少约束。
上下文组装时有个细节:不要把几十个检索块全部塞进去,即使模型上下文足够,也要有所取舍。通常把重排后的 top-3 到 top-5 块按相关度顺序拼接,并在每块前加上文档来源标识。回答生成后甚至可以在后处理阶段附上引用列表,这在企业场景中对可信度影响巨大。
另外,Agent 场景下的生成环节往往还有一个“判断逻辑”:模型可能发现检索结果不足,从而决定发起二次检索或调用工具获取实时数据。这就是 Agentic RAG 的基础形态。在第四篇里,我建议先把基础生成链路跑通,再往这个方向演进。
3. 实操过程:用 LangChain 搭一条最小 RAG 管道
理论基础讲完了,实际动手最容易踩坑。下面用一个最小可运行的示例,把整条管道串一遍。我选择 LangChain 做示例,因为它在社区生态中最成熟,组件链条清晰,适合作为学习骨架。如果你用的是 Spring AI 或 LlamaIndex,核心思路完全一致,只是 API 风格不同。
3.1 工具选型与安装
最小管道需要四类组件:文档加载器、文本切分器、嵌入模型、向量库。再加一条 LLM 调用链路。下面是我实测过的一组稳定搭配:
- 文档加载:unstructured 或 langchain 内置的文本加载器
- 文本切分:RecursiveCharacterTextSplitter
- 嵌入:BGE-small-zh-v1.5 或 text2vec-large-chinese
- 向量库:Chroma(本地起步首选,零配置)
- LLM:OpenAI 兼容接口或本地部署模型均可
安装依赖:
pip install langchain langchain-community chromadb sentence-transformers如果你用国产大模型或本地模型,只需要替换 LLM 的 base_url 与 api_key,链路其余部分不变。
3.2 数据准备与向量库写入
下面是一段可以直接运行的索引构建代码。注意这个示例省略了权限、去重等生产细节,但提供了完整管道骨架。
from langchain_community.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 1. 加载文档 loader = TextLoader("knowledge_base.txt", encoding="utf-8") documents = loader.load() # 2. 切分文档,块大小500、重叠80 text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=80, length_function=len, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) chunks = text_splitter.split_documents(documents) print(f"切分后共 {len(chunks)} 个块") # 3. 初始化嵌入模型 embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5") # 4. 写入向量库(首次运行会自动创建) vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db" ) vectorstore.persist()这里有一个值得留意的细节:separators 的排序是递归切分器的核心策略,它会按顺序尝试分隔符,优先用段落、然后是换行、中文标点、空格,保证语义尽量完整。很多人在这一步直接用默认值,遇到中文文本时切分效果会不如预期,因为英文默认分隔符里没有中文标点。把中文句读加进去之后,切出来的块语义明显更完整。
3.3 检索与生成链路
索引建好之后,在线链路的实现同样简洁。下面是包含检索、组装 prompt、调 LLM 的完整流程。
from langchain.prompts import PromptTemplate # 检索:余弦相似度召回top-k retriever = vectorstore.as_retriever(search_kwargs={"k": 5}) hits = retriever.invoke("Agent 如何获取外部知识?") for i, hit in enumerate(hits): print(f"[{i}] {hit.page_content[:50]}...") # 组装生成prompt template = """你是一个严谨的AI助手。请仅基于以下已知信息回答用户问题。 如果已知信息不足以回答问题,请直接回复“我不知道”,严禁编造。 已知信息: {context} 用户问题:{question} """ prompt = PromptTemplate.from_template(template) formatted_prompt = prompt.format( context="\n\n---\n\n".join([h.page_content for h in hits]), question="Agent 如何获取外部知识?" ) print("组装后的提示词长度:", len(formatted_prompt))到这一步,RAG 的基础链路已经完整了。我们需要做的只是把 formatted_prompt 发送给任意兼容的 LLM 接口。检索命中多少个块,决定了回答是否有据可依。如果把 k 设为 1,模型可能因信息不足而答非所问;如果 k 设为 20,无关信息又会干扰判断。我建议 4-6 起步,根据你自己的评测数据再调。
3.4 参数调优实测记录
这里分享一组我自己用中文技术文档做的调优实测,你可以在自己的数据集上复现这套方法论。测试集是 100 个问答对,评测指标为“回答正确率”和“top-5 召回率”。
| 配置 | 块大小 | 重叠 | k值 | 检索召回率 | 回答正确率 |
|---|---|---|---|---|---|
| A | 200 | 20 | 5 | 61% | 52% |
| B | 500 | 80 | 5 | 78% | 69% |
| C | 800 | 100 | 5 | 74% | 65% |
| D | 500 | 80 | 10 | 82% | 71% |
| E | 500 | 80 | 20 | 83% | 63% |
三组关键观察:
块大小 500 明显优于 200,说明语义完整性对检索质量影响很大;但 800 反而下降,说明过长的块引入了噪声。
top-k 从 5 提升到 10,召回率与正确率都有增益,但再增加到 20,正确率反而下降。这说明召回足够以后,更多上下文只会干扰模型判断。
这个测试也说明了为什么不能只追 hit-rate:把 k 调到 20 时检索召回率升到 83%,但回答正确率只有 63%,就是因为信息过载导致的注意力稀释。生产调优时,永远要以最终回答质量为准,而不是只盯着检索指标。
4. 常见问题与排查技巧实录
基础管道跑起来容易,跑得稳需要积累。下面把我在实际项目中遇到过的高频问题整理成一份排查速查表,每一条都是踩过坑之后的经验沉淀。
4.1 检索效果差的排查思路
检索环节最让人抓狂的问题是:明明数据在里面,就是搜不到。排查时不要盲目换向量库,按下面顺序逐层检查。
先检查切分。打开检索到的 chunk,看语义边界是否被截断。如果 chunk 开头或结尾是不完整的半句话,八成是切分器对中文处理得不好,把中文标点加入 separators 往往立竿见影。
再检查嵌入模型。拿几条典型 query 到向量库里检索,人工判断召回结果是否合理。如果相关文档排在第 10 名开外,说明嵌入模型对领域术语理解不足,这时候值得换更强的模型。我之前在一个机械维修数据集上,从通用模型换到领域微调模型后,top-5 命中率从 45% 提升到了 72%。
还要注意查询预处理。真实用户搜索时用的是自然语言,但知识库里的文档可能是指标体系、代码片段、规范条款,表达风格完全不一致。这种情况下,检索前做一个 query 改写(提取关键词、补全同义词)会有明显收益。
最后是过滤条件。如果向量库数据量大,且检索时没有按时间、来源等元数据过滤,无关文档的干扰会非常严重。搭建阶段就应该把文档来源、时间戳等字段写入 metadata,这会让后续问题排查轻松很多。
4.2 回答质量差的排查思路
检索能命中了,但回答依然不如预期,这往往是生成环节的问题。最常见的四种情况:
第一,提示词缺少约束。模型在开放式指令下倾向于“发挥”,即使语境中明显缺少信息,它也会编一个看起来合理的答案。给 prompt 加上“信息不足时明确说不知道”的硬性约束,能消除大部分幻觉。
第二,上下文顺序有问题。LangChain 默认按相似度降序拼接上下文,但有时候把更概括性的段落放在前面,回答效果更好。这个没有统一答案,需要看你的文档结构。
第三,块来源标识缺失。没有来源标引,模型无法对信息做可信度区分,容易把某一块的表述当作绝对事实。在上下文中标注“摘自文档A”,回答时要求模型区分引用来源,能显著提升稳定性。
第四,模型长度限制。个别模型在输出层存在隐性长度偏好,长上下文输入时容易丢失开头的信息。如果检索块多而出现“回答只基于最后一段”的情况,尝试减少块数或把最重要的块放在开头、结尾两处。
4.3 性能与成本的平衡技巧
RAG 管道上线后,性能与成本通常是一对矛盾。有四个技巧可以在不明显牺牲质量的前提下压成本:
- 缓存复用:相同或相近的 query,可复用已生成的答案。在 Agent 问答场景中,大约能命中 20%-30% 的重复请求。
- 查询改写后用小模型粗筛:先用小模型或 BM25 做一层廉价召回,命中精确时就不用再走昂贵的大模型。只有模糊场景才升级到向量检索,能省接近一半的检索成本。
- 控制 top-k:在回答质量不下降的前提下,用最小可用的 k 值。k 从 10 降到 5,生成的 token 数量和成本大约减少 40%。
- 增量索引:文档更新时不用全量重建向量库,按文档粒度增量更新即可。Chroma 与 Elasticsearch 都支持按 id 覆盖,实测增量更新 100 篇文档比全量重建快 10 倍以上。
性能方面,如果向量库检索延迟过高,优先检查候选集规模。超过百万向量后,暴力检索已经吃力,需要切分索引或使用 HNSW 这类 ANN 算法。Chroma 默认的索引在十万级文档下体验尚可,再往上建议迁移到专门的向量数据库。
最后聊两句实战体会
RAG 这条知识获取管道,是 Agent 从“能聊天”走向“能干活”的分水岭。我自己从第一版“裸奔 Agent”(完全靠模型记忆)迭代到接入 RAG 后再接 Agentic 循环,最直观的感受是:模型还是在那个模型,但回答突然有了依据和边界,业务方也更愿意把问题交给它处理。
如果你现在准备动手,我的建议是:别一上来就追 GraphRAG、Agentic RAG 这些进阶方向。先把基础管道跑通,收集自己数据上的评测指标,再根据短板做定向优化。切分粒度是不是不够?检索召回是不是不准?prompt 约束是不是不够硬?每一步都可以单独调优。等你对基础管道的“手感”有了准确的直觉,再往后上高级玩法,才会事半功倍。