
1. 为什么 Agent 场景里先打 RAG 基础做 Lang-Agent 系列做到第 5 篇前面已经聊过 Agent 的基本框架、工具调用、记忆管理。这一篇开始进入一个绕不开的核心环节RAG也就是检索增强生成。我在实际项目里见过不少同学一上来就让 Agent 直接对接大模型 API对话一深就开始胡说八道问一个内部文档里写得很清楚的问题模型愣是给你编出一个不存在的答案。这不是模型智商不够而是它手里根本没有那份资料。RAG 要解决的就是这个问题让模型在回答之前先到知识库里查资料再基于查到的内容做推理。那什么是“知识库”说白了就是你自己的私有资料集合可能是产品手册、技术文档、客服话术、历史工单、实验记录等等。这些内容模型在训练时没见过也不可能见过因为它们要么是内部数据要么是实时更新的动态内容。RAG 的做法是先把这些资料切成小块做向量化存进向量库等用户提问时把问题也向量化再从库里检索最相关的内容把这些内容塞进提示词让模型照着资料回答。整个过程像极了开卷考试模型不需要把书背下来只需要知道翻哪一页。这篇内容适合正在搭建自己的知识问答系统、想把 Agent 接进业务数据的开发者也适合刚接触 RAG、被各种概念绕晕的入门者。我会按照一条完整的构建路径来讲先做数据准备再讲向量化和检索链路最后把知识库封装成 Agent 的工具并分享我实际踩过的坑和评测方法。读完之后你应该能独立搭出一个能跑、能测、能迭代的 RAG 知识库而不是只看了一堆概念。2. 知识库构建前的数据准备2.1 先想清楚资料边界再动手写代码很多人搭建 RAG 知识库的第一步就是写脚本批量切片导入向量库这其实顺序反了。我见过一个项目团队把几千份格式杂乱的旧文档一股脑灌进去结果用户问任何问题都能召回一堆无关片段回答质量还不如直接问大模型。问题根源在于知识库没有边界数据质量参差不齐脏数据比没有数据更可怕。动手之前你应该先做三件事圈定知识范围、确认数据格式、估算体量。知识范围意味着明确哪些文档允许被检索哪些必须排除比如保密协议、内部闲聊记录、未定稿的方案都不该进知识库。数据格式决定了你的解析管线怎么设计PDF、Word、Markdown、HTML、表格各自有不同的处理方式。体量估算则影响向量库选型和检索性能设计几百条文档和几百万条文档是完全不同的架构思路。我习惯在第一步输出一份简单的资料清单字段包括来源、文档类型、负责人、更新频率、是否可公开检索。这份清单不需要多复杂但它能在后续迭代时帮你快速定位问题比如某个回答不准你能立刻查到是哪份来源在拖后腿。2.2 清洗和解析决定知识库质量的下限数据清洗是枯燥但决定成败的环节。以最常见的 PDF 为例文字型 PDF 直接用解析库抽取文本扫描件需要 OCR双层 PDF 要小心重复抽取。解析完不要急着切片先做一轮噪声清理去掉页眉页脚、目录页码、表格线符号、异常换行把全角半角统一。我常用的做法是正则清洗加人工抽检清洗规则逐项加注释方便后续排查。清洗之后还有个容易忽略的点保留结构信息。纯文本会丢失标题层级、列表关系、加粗强调而这些信息对后续切分和检索非常有价值。所以我在处理时会给段落打上元数据标签比如“标题”“正文”“代码块”“表格”并把来源文档名、章节路径、页码一起记下来。这样后期检索不仅能召回内容还能告诉用户“这句话来自哪份文档的哪个章节”极大地提升可信度。做个小提醒清洗过度也不行。我试过把所有标点都清掉结果检索时语义发生变化召回质量反而下降。清洗的原则是去掉干扰、保留语义而不是把文本压得越干净越好。2.3 分块策略切片大小直接影响检索命中率分块是 RAG 最容易被低估的环节。同样一份文档分块大小不同检索效果天差地别。块太大向量里混入太多无关信息检索精度下降块太小语义被切断模型无法理解上下文。我在实践中通常从 300 到 800 字中文场景起步根据文档类型调整并且设置 50 到 100 字的重叠保证相邻切块之间的语义连续性。分块不是单纯按字符数硬切我更推荐先按文档结构切。比如一个章节标题下是一段完整的论述那就整段作为一个候选块如果段落过长再按句号或空行二次切分。这么做的好处是保留自然语义边界模型拿到的每一块都是相对完整的论述而不是从句子中间硬掰断的文字。这里给出一个简单的分块思路示例def split_document_by_structure(text, max_chunk500, overlap80): # 伪代码先按结构分割再按长度合并 sections split_by_heading(text) chunks [] buffer for section in sections: buffer section while len(buffer) max_chunk: cut_idx find_nearest_boundary(buffer, max_chunk) chunks.append(buffer[:cut_idx]) buffer buffer[cut_idx - overlap:] if buffer: chunks.append(buffer) return chunks确定分块参数后不要拍脑袋就定而是用一组有代表性的测试问题反复验证。我在后面评测章节会详细说怎么做这里先记住一个原则分块策略服务于检索效果而不是服务于存储容量。3. 向量化与检索链路搭建3.1 嵌入模型怎么选效果和成本的平衡知识库准备好之后下一步就是把文本变成向量。嵌入模型的选择直接影响检索效果但很多人容易走极端要么直接调一大型模型的接口效果不错但成本高尤其数据量大时非常烧钱要么本地跑一个小模型快是快语义理解一塌糊涂。我的建议是分场景对待。如果你的知识库内容接近日常口语比如客服对话、邮件、聊天记录选一个通用性强、支持中文的嵌入模型就够用。如果知识库专业术语密集比如医学、法律、工程文档那就要考察模型在垂直领域的表现最好用你自己的数据做对照测试而不是只看榜单分数。嵌入模型还需要注意输入长度限制。有些模型单次最多编码 512 个 token如果你的分块超过了这个长度向量化时会被截断等于后半段内容白切了。所以分块参数必须与嵌入模型的编码上限匹配这是我踩过的一个实际教训一开始分块设成 1000 字嵌入模型上限 512 token结果长文档后半截语义丢失检索出来的内容总是缺尾。3.2 向量库选型从原型到生产向量库的选择取决于你的规模和使用场景。做原型验证时一个小型本地向量库就足够了数据量在几十万条以内检索性能完全够用。到了生产环境如果涉及多端访问、高并发、权限隔离就要考虑更完整的数据库方案但部署和运维成本也更高。我整理了一份简单的选型对照场景建议方案理由个人学习、原型验证轻量级本地向量库零配置快速跑通流程中小团队内部工具独立向量库服务支持多端访问便于管理大规模生产环境分布式向量检索集群性能、扩展性、权限控制更完善已有关系型数据库数据库内置向量检索能力减少额外组件统一维护这里的核心不是让你照搬而是提醒你向量库不等于最终方案它是检索链路的一个组件。你的数据权限逻辑、更新机制、备份策略都需要围绕它设计。比如知识库内容经常更新那向量库必须支持按文档 ID 删除和增量插入否则旧数据残留会导致回答过时。3.3 召回策略向量检索之外还要做什么单纯向量检索在实践中往往不够尤其是知识库内容庞杂时。向量检索擅长捕捉语义相似但它不理解精确匹配也不懂关键词的重要性。比如用户查找“设备型号 ABC-200 的故障代码 E01”向量检索可能召回一堆关于“设备故障”的泛泛内容而精确匹配“ABC-200”“E01”的记录反而没排到前面。我现在的检索链路通常是混合召回向量检索负责语义召回关键词检索负责精确命中最后把两路结果做融合。融合时需要设定权重我的习惯是语义为主、关键词为辅比例大约 7:3然后加权排序取 Top N。召回之后还可以加一层重排用一个更强的模型对候选块做精细打分把最相关的内容排到最前。重排的价值在知识库内容丰富时特别明显它能显著提升最终答案的相关性。检索参数的调节也很讲究召回数量 top_k 不是越大越好因为塞进提示词的上下文过多模型容易分不清主次反而降低回答质量。我的初始值通常是 top_k 4 到 6重排后取前 3 到 4 个块进入提示词。具体数值要根据评测结果迭代没有标准答案。4. 把 RAG 接进 Agent 工作流4.1 工具化封装让 Agent 主动去查书RAG 链路本身可以独立工作但放进 Agent 场景后它的价值会放大。原因在于 Agent 的核心能力是规划和决策它可以判断当前问题是否需要查知识库需要查哪个知识库查完之后怎么用结果继续推理。这意味着知识库不再是被动被调用的接口而是 Agent 的一个主动决策分支。具体做法是把检索能力封装成一个工具函数注册给 Agent。工具函数通常接收一个查询字符串返回检索到的相关内容片段。比如def search_knowledge_base(query_text: str): # 伪代码调用检索链路返回结果 candidates hybrid_retrieve(query_text, top_k6) reranked rerank(query_text, candidates, top_k3) return format_for_prompt(reranked)函数本身不难写难在工具的描述信息。Agent 依赖工具描述来判断“什么时候该用这个工具”描述写得太笼统它遇到数学题也会跑去翻知识库描述太具体它又容易漏掉该触发的场景。我常用的写法是在描述里包含这个知识库覆盖哪些领域、适合回答哪类问题、不适合回答哪类问题、返回结果是什么格式。这套描述需要反复打磨要和检索链路一起调优。4.2 提示词组装上下文不是越多越好Agent 查到结果后如何把这些片段正确地喂给大模型也是一门学问。我会在提示词里明确划分区域检索到的资料放一个区块用户原始问题放一个区块模型的任务是“优先依据资料回答资料不足时明确说明”。这种显式引导比直接把资料堆在对话上下文里要稳定得多。同时要保留引用信息。每条检索结果都携带来源文档、章节、页码模型在回答时应当把这些信息一并输出。这样做有几个好处用户能验证答案、你能定位知识库问题、模型在知道会被溯源的状态下也会更谨慎地组织语言。我的输出格式通常是“回答正文”加“参考来源列表”来源列表用编号与正文中的引用标记对应。上下文溢出是我经常看到的另一个问题。知识库返回 6 个块每个块 500 字加起来 3000 字再加上对话历史和 Agent 的推理过程很快就把上下文窗口挤满。模型注意力分散回答质量直线下降。我的处理办法是控制进入提示词的块数优先返回最相关的 3 个块级去重多个检索结果来自同一段落时只保留完整度最高的对话历史做摘要压缩避免每轮问答都全量塞入。4.3 多知识库路由涉及 Agent 权限与偏见实际业务里往往不止一个知识库比如产品知识库、技术文档库、运营手册库。如果把这些合并成一个向量库检索时容易互相干扰。更合理的做法是分库存储给 Agent 多个工具让它在规划阶段先判断该查哪个库。分库的路由逻辑可以放在 Agent 里由模型自行决策也可以在工具层写规则。我的实践是当知识库主题差异明显时让模型做路由当主题存在交叉时规则路由更可靠。比如用户问“这个功能定价多少”如果产品库和销售政策库都可能覆盖我会在工具描述中写清楚两个库的边界再让模型综合判断。边界模糊时宁可多查一个库也不能只查一个库而漏掉关键信息。5. 落地效果评测与常见问题排查5.1 评测不能只看“回答得像不像”RAG 系统的评测比一般模型评测更复杂因为效果取决于两个环节检索准不准生成对不对。我最初犯的错是把两者混在一起只看最终回答好不好结果出问题后不知道是检索召回差还是模型生成差。后来我把评测拆成两层先评检索层再评生成层。检索层评测的核心指标是召回率和排序质量。我准备一组测试问题每个问题对应知识库里的一个标准答案片段跑一遍检索链路看正确答案有没有出现在返回结果里、排在什么位置。生成层评测则关注三个点回答是否忠于检索结果忠实性、是否覆盖用户问题的全部要点完整性、引用是否正确可溯源。忠实性这一项我格外看重因为它直接对应幻觉问题——模型有没有编造知识库里没有的信息。评测集不用一开始就建得很大我建议先从 50 到 100 条问题起步覆盖各知识库核心主题再逐渐补充边缘场景。关键是记录每次迭代的评测结果否则你改了分块参数、换了检索权重效果是好是坏全靠感觉那就没法持续优化了。5.2 踩坑实录空召回、上下文溢出、答案与资料矛盾讲几个我实际遇到过、排查了很久的问题。第一个是空召回。某个特定问题在知识库里明明有对应内容但检索返回为空或相关度极低。排查步骤是先检查问题本身的表述是否与文档术语差异过大。比如知识库里写的是“退款到账周期”用户问的是“钱啥时候退回来”语义相似但用词完全不同如果嵌入模型一般召回就失败了。解决办法一是换更强的嵌入模型二是在检索前加查询改写由模型把口语化问题转写成文档术语再检索。第二个是上下文溢出。这个问题我之前提过典型症状是对话进行到后面模型开始忽略检索到的资料或者回答变得啰嗦且偏离主题。这通常是上下文窗口被占满关键资料被“挤”出去了。解决思路是优先保证最高优先级的内容最新检索结果和当前问题永远在窗口内历史对话做压缩。第三个是答案与资料矛盾。模型检索到正确的资料但回答时依然坚持它自己的“想法”。这种情况最容易出现在有争议性或模型预训练知识很强的领域。比如知识库里明确规定某流程不允许某个操作但模型觉得另一个做法更合理就自作主张给了错误答案。应对方法是在提示词中加强约束把“资料说不行就不行”作为硬性指令并在输出解析时检查回答与检索结果的一致性发现矛盾时触发重新生成。5.3 迭代优化节奏RAG 项目不是一次性搭完就结束的它更像一个需要持续维护的服务。我现在的节奏是每次修改后先跑评测集对比前后效果每两周补充新的测试问题每次用户反馈出问题都记录在案归入评测集。通过这种方式知识库的质量会随着时间不断提升而不是越跑越偏。具体的调参顺序我一般先调数据层的分块参数再调检索层的 top_k 和权重最后才考虑换模型。原因是从底层往上层调每次改动的影响更可控。如果一上来就换嵌入模型效果变化了也不知道是谁的功劳。6. 两个进阶方向帮你把 RAG 做得更顺手如果基础链路已经跑通我会建议你在两个方向上做增强。第一个方向是查询改写和意图扩展。用户的问题往往不够清晰直接把原句拿去检索效果有限。可以让 Agent 在检索之前先做一次查询分析识别关键词、补全简写、补充同义词。比如“上次说的连接问题”这种模糊指代需要结合对话上下文改写为“设备连接不稳定如何排查”再去检索。我的经验是查询改写增加的一次模型调用非常值它能明显提升召回精度。第二个方向是知识库的自动更新与版本管理。知识库内容会变但向量库里的旧数据不会自动消失。如果不做版本管理就会出现新旧文档内容冲突回答随检索结果随机摇摆。我建议给文档加版本号和生效时间检索时优先返回有效版本旧版本数据进入归档库不参与默认检索。这样既能保证回答与当前事实一致也保留了追溯历史信息的通道。这两个方向都建立在基础链路已经稳定可控的前提之上。先把检索、生成、评测这条主线打磨扎实再往这两个方向扩展会比较从容。7. 一些个人经验最后分享三点我在实操中反复验证过的经验希望能帮你少走弯路。第一日志记录比什么都重要。RAG 链路涉及的环节多任何一个环节出问题如果没有日志只能靠猜。我现在的做法是对每次查询记录原始问题、改写后的问题、检索返回的块 ID 和分数、重排后的排序、进入提示词的最终内容、模型回答、引用来源。这些日志是排查问题、优化效果的第一手素材。第二参数调整要一次只动一个变量。分块大小、重叠长度、top_k、重排模型、提示词措辞任何一个变化都可能影响最终效果。同时改多个参数即便效果变好你也不知道哪个改动起了作用。我吃过这个亏后来强迫自己一次只动一个变量效果变化立刻能归因。第三先跑通最小闭环再追求优化。我第一次搭建 RAG 时做过一个最基本的版本切块、向量化、检索、拼提示词没有重排、没有混合检索就是能跑。先让链路通起来再一项项加功能每个加完做一次评测对比这样做既不容易迷失方向也能在每一步都明确改动价值。