1. 为什么你的 Agent 总是“一问三不知”
很多人搭智能体的路径都差不多:先选个框架,把大模型接上,写个提示词,跑起来发现对话挺流畅,于是兴冲冲地丢给它一堆业务问题——结果要么答得驴唇不对马嘴,要么干脆编一段听起来很像那么回事的假话。这时候大部分人的第一反应是“模型不行,换个更强的”,但换完之后发现问题依旧。真正的原因往往不在模型,而在你根本没给它可查的东西。
大模型本身是一个被冻结在训练时间点上的参数集合,它脑子里装的是公开语料的统计规律,不是你公司的产品手册、不是你的项目文档、也不是你上周刚整理的那份会议纪要。你问它这些,它只能靠“猜”,猜得越像真的越危险。知识库要解决的就是这件事:把散落在文件夹、聊天记录、网页收藏、PDF 里的资料,整理成模型在推理时能真正检索到、并且能引用来源的外部记忆。
这一篇是 Agent 系列的第三篇,前两篇聊了智能体的基本循环和工具调用,这一篇专门啃知识库这块硬骨头。我会把“资料怎么进来、怎么切、怎么存、怎么被查出来、查出来之后怎么塞进上下文”这条链路完整走一遍,中间穿插我自己踩过的坑。适合已经能跑通一个基础 Agent、但被“答不准”折磨过的开发者,也适合刚接触 RAG 想搞清楚它到底怎么回事的新手。读完你至少能判断:你的场景到底该不该上知识库,该上哪一种,以及为什么你现在的方案效果差。
先给一个反直觉的结论:知识库效果差,八成不是检索算法的问题,而是资料在进入知识库之前就没被处理好。后面我会用大量篇幅讲这个“前置处理”,因为它才是决定成败的地方。
2. 先分清三种知识库:RAG、KG、结构化,别一上来就选错
热词里有个很典型的问题:“kg知识库、rag知识库和结构知识库区分以及应用场景”。这个问题问得特别好,因为很多人把“知识库”当成一个东西,实际上它至少是三种完全不同的形态,选错了后面全是白费功夫。
2.1 RAG 知识库:把文本切片后做向量检索
RAG(Retrieval-Augmented Generation,检索增强生成)是当前最主流、上手最快的方案。它的核心思路是:把文档切成一段段文本块(chunk),用嵌入模型把每块转成一个向量,存进向量数据库;用户提问时,把问题也转成向量,在库里找最相似的若干块,拼进提示词让模型基于这些块回答。
它擅长的是非结构化文本:产品说明、FAQ、技术文档、政策条款、聊天记录。你问“退货流程是什么”,它能从一堆文档里捞出相关段落。它的短板也很明显:对“精确匹配”和“多跳推理”比较弱。比如你问“A 产品的负责人是谁”,而答案需要先查到 A 属于哪个部门、再查部门负责人,这种链式关系 RAG 一次检索往往搞不定。
2.2 KG 知识库:用实体和关系表达事实
KG(Knowledge Graph,知识图谱)知识库把信息表达成“实体—关系—实体”的三元组,比如“张三—负责—A产品”“A产品—属于—X部门”。它擅长关系查询和多跳推理,问“谁负责 A 产品所在部门”可以顺着图走两步就出来。缺点是构建成本高,需要定义本体(schema)、做实体抽取和关系抽取,维护起来也比 RAG 重得多。
2.3 结构化知识库:表格和数据库
结构化知识库就是老老实实的表格、SQL 数据库、Excel。它擅长精确的数值查询和聚合,比如“上个月销售额是多少”“库存低于 10 的有哪些”。让模型直接读表格容易出错,正确做法是让 Agent 生成 SQL 去查,把结果拿回来再组织语言。
| 类型 | 数据形态 | 擅长 | 不擅长 | 构建成本 |
|---|---|---|---|---|
| RAG | 非结构化文本 | 语义检索、问答 | 多跳推理、精确聚合 | 低 |
| KG | 实体关系三元组 | 关系查询、多跳 | 模糊语义、冷启动 | 高 |
| 结构化 | 表格/数据库 | 精确数值、聚合 | 语义理解、开放问答 | 中 |
实际项目里最常见的是混合方案:结构化数据走 SQL,文档走 RAG,关键实体关系再补一张轻量图谱。但对绝大多数人来说,先把 RAG 做扎实,比急着上图谱划算得多。我见过太多团队一上来就要搞知识图谱,结果连文档切片都没切明白,最后项目烂尾。
提示:判断标准很简单——如果你的问题大多是“某段话说了什么”,选 RAG;如果是“A 和 B 是什么关系、经过几层能连上”,才考虑 KG;如果是“算个数、排个序”,直接上结构化查询。
3. 资料入库前的清洗:决定成败的隐形战场
这一节是全文最想让你重视的部分。很多人搭 RAG 的流程是:把 PDF 往解析器里一丢,切完直接入库,然后抱怨效果差。问题就出在这个“丢”字上。
3.1 原始资料的三种典型脏数据
第一种是格式噪声。PDF 解析出来经常带着页眉页脚、页码、水印文字,甚至表格被拆成乱七八糟的字符流。我处理过一份产品手册,解析后每页顶部都混进“内部资料 请勿外传”八个字,结果用户问任何问题,检索出来的块里都带着这句话,模型被干扰得很厉害。
第二种是结构丢失。一份 Markdown 或 Word 文档本来有清晰的标题层级,解析成纯文本后层级全没了,一段话脱离了它所属的章节,语义就变了。比如“该参数默认关闭”这句话,放在“安全设置”章节和放在“性能优化”章节,含义完全不同。
第三种是重复与冲突。同一个问题在旧版文档和新版文档里答案不一样,两份都进了库,检索时随机命中一份,模型给出的答案就时对时错。这种问题最隐蔽,因为单看每次回答都“有据可依”。
3.2 清洗的具体动作
我的做法是分三步走。第一步,保留结构信息。解析时尽量保留标题层级,把每个 chunk 的“所属章节路径”作为元数据存下来,比如产品手册 > 第三章 配置 > 3.2 安全设置。检索时这个路径能帮模型判断上下文。
第二步,去噪。用规则把页眉页脚、页码、重复出现的免责声明过滤掉。这一步不用太智能,正则表达式往往就够了。关键是你要先抽样看几十个解析结果,亲眼看看脏数据长什么样,而不是盲目相信解析器。
第三步,去重与版本管理。给每份文档打上版本号和生效时间,检索时优先返回最新版本。如果新旧答案冲突且都需保留,就在元数据里标注清楚,让模型知道“这是旧版,仅供参考”。
# 一个简单的去噪示例:过滤页眉页脚和页码 import re def clean_text(raw: str) -> str: # 去掉纯数字页码行 raw = re.sub(r'^\s*\d+\s*$', '', raw, flags=re.MULTILINE) # 去掉固定页眉 raw = raw.replace('内部资料 请勿外传', '') # 合并多余空行 raw = re.sub(r'\n{3,}', '\n\n', raw) return raw.strip()注意:清洗规则一定要基于你真实的数据来写,不要抄网上的通用模板。不同来源的文档脏法完全不一样,抄来的规则大概率不匹配。
3.3 一个容易被忽略的点:给资料打“可回答范围”标签
有些资料是给内部人看的,包含大量缩写和黑话;有些是对外的,措辞规范。如果混在一起,用户问一个口语化的问题,可能检索到内部黑话文档,模型解释起来就很别扭。我的做法是给每份文档打一个audience标签(internal / external),检索时根据提问场景过滤。这个动作很小,但能明显减少“答非所问”。
4. 切片策略:切得好,检索就成功了一半
切片(chunking)是 RAG 里被讨论最多、也最容易被做砸的环节。切太大,一个块里混了好几个主题,检索出来噪声多;切太小,一句话被拦腰截断,语义不完整。这里没有万能参数,只有针对你数据的合理策略。
4.1 三种主流切法及适用场景
固定长度切分:按字符数或 token 数硬切,比如每 500 字一块,块间重叠 50 字。优点是简单、快,适合格式规整、主题均匀的文本。缺点是经常在句子中间切断,遇到列表、表格更是灾难。
按结构切分:顺着文档的标题、段落、列表项来切。Markdown 按##、###切,代码按函数切,合同按条款切。这是效果最好的方式,前提是你的解析阶段保留了结构。我现在的默认策略就是结构优先,结构切出来的块如果太大,再在块内做二次切分。
语义切分:用嵌入模型判断相邻句子的语义相似度,在语义“断层”处切开。效果不错但计算成本高,一般用于对质量要求极高的场景。
4.2 重叠(overlap)到底要不要设
要设,但别设太大。重叠的目的是防止关键信息正好落在切割边界上被切断。比如一句话前半段在块 A、后半段在块 B,如果没重叠,检索到 A 也拿不到完整信息。一般重叠 10%–20% 就够了,设成 50% 会导致大量冗余,检索时同一内容反复出现,浪费上下文窗口。
4.3 给每个块补“上下文头”
这是我强烈推荐的一个技巧:在每个 chunk 前面拼一句它所属的上下文。比如一个块本身是“默认值为 30 秒”,单独看完全不知道在说什么,但如果拼上“本文档介绍 API 超时配置,以下为超时时间参数说明:默认值为 30 秒”,检索和生成的质量都会明显提升。
def build_chunk_with_context(chunk_text, section_path, doc_title): header = f"文档:{doc_title}|章节:{section_path}\n" return header + chunk_text这个 header 会一起被嵌入,所以检索时它也在参与匹配,相当于给每个块加了一个“身份说明”。成本几乎为零,收益却很实在。
4.4 表格和图片怎么处理
热词里有人问“rag知识库能存储图片嘛”。答案是:向量库存的是图片的向量表示或图片的文字描述,不是图片本身。常见做法有两种:一是用多模态模型给图片生成一段文字描述,把描述入库;二是用图文嵌入模型直接把图片转向量。前者实现简单、可解释性强,后者效果更好但依赖特定模型。表格则建议转成 Markdown 或自然语言描述再入库,直接塞原始表格字符流,模型很难理解。
5. 检索环节:为什么你搜出来的东西总是不对
资料洗干净了、切好了、入库了,接下来就是检索。这一步的坑同样不少。
5.1 纯向量检索的天然缺陷
向量检索擅长语义相似,但对精确关键词不敏感。用户问“错误码 E1024 怎么解决”,向量检索可能返回一堆讲“错误处理”的通用文档,却漏掉那条专门讲 E1024 的记录,因为“E1024”这个 token 在嵌入时被稀释了。解决办法是混合检索:向量检索 + 关键词检索(BM25 之类),两路结果融合排序。这是目前工业界比较稳的做法。
5.2 重排序(Rerank)值不值得上
值得。向量检索是“粗筛”,召回一批候选;重排序模型是“精排”,用更强的模型对候选逐一打分,把最相关的排到前面。加了重排序之后,通常能把最相关的结果从第 5、6 位提到第 1 位,对最终答案质量影响很大。代价是多一次模型调用,延迟增加几百毫秒。我的建议是:只要对准确率有要求,就上重排序。
5.3 检索数量(top-k)怎么定
top-k 太小,可能漏掉关键信息;太大,噪声多、上下文被塞满。经验值是先召回 20–50 条候选,重排序后取前 3–5 条塞进提示词。具体数字要看你块的大小:块小就多取几条,块大就少取几条。别拍脑袋定,拿一批真实问题测一下,看 top-k 取多少时答案最准。
5.4 查询改写:让用户的问题更好搜
用户提问往往很口语、很模糊,直接拿去检索效果差。可以在检索前让模型把问题改写成几个更规范的查询,分别检索再合并。比如用户问“那个登录老是失败咋整”,改写成“登录失败 原因”“登录失败 解决方法”“认证错误 排查”,召回率会明显提升。
REWRITE_PROMPT = """把用户问题改写成3个适合检索的查询,每行一个,不要编号: 用户问题:{question} """提示:查询改写会增加一次模型调用,如果对延迟敏感,可以只在检索结果置信度低时才触发。
6. 把检索结果喂给模型:上下文组装的讲究
检索出一堆块之后,怎么塞进提示词,也是有讲究的。
6.1 顺序很重要
模型对上下文里开头和结尾的内容更敏感(所谓“中间遗忘”现象)。所以最相关的块应该放在开头或结尾,不要埋在中间。我一般按相关性排序后,把最强的放最前,次强的放最后。
6.2 明确标注来源和边界
每个块前面标清楚来源,块之间用分隔符隔开,让模型知道“这是资料 A 的内容,那是资料 B 的内容”。这样模型在回答时能引用来源,也减少了把不同文档内容混为一谈的概率。
【资料1|来源:产品手册 3.2】 超时时间默认 30 秒…… 【资料2|来源:FAQ 第 12 条】 如果超时频繁发生,建议……6.3 明确告诉模型“查不到就说查不到”
这是减少幻觉的关键。提示词里必须写清楚:如果提供的资料里没有答案,就直说没有,不要自己编。我见过太多项目因为少了这句话,模型在检索为空时依然一本正经地胡说。
6.4 上下文窗口不是越大越好
有人觉得反正现在模型上下文窗口大,把检索到的几十个块全塞进去。结果模型被大量无关信息干扰,反而答不准,还费 token。精准的 3 个块,胜过杂乱的 30 个块,这是我在多个项目里反复验证的结论。
7. 实测中的几个典型故障与排查思路
理论讲完了,说几个我实际遇到过的故障,以及怎么定位的。
7.1 故障一:答案总是差一点点
现象是模型答得“沾边但不准”。排查下来发现是切片把关键定义切断了,定义的前半句在上一块、后半句在下一块,检索只命中一块。解决办法是加大重叠,或者改用按结构切分。这个故障很典型,先怀疑切片,再怀疑检索。
7.2 故障二:同一个问题两次回答不一样
这种“不稳定”通常来自重复或冲突的资料。排查方法是把检索到的块打印出来看,往往能发现新旧两份文档都被召回了。解决办法是加版本过滤,或者入库前就去重。
7.3 故障三:专有名词检索不到
公司内部的产品代号、项目简称,向量检索经常抓不住。解决办法是在清洗阶段给这些专有名词建立同义词表,检索时做查询扩展;或者干脆上混合检索,让关键词那一路兜底。
7.4 故障四:检索明明对了,模型还是答错
这时候问题在生成环节。检查提示词里资料和问题的位置关系、有没有明确要求“基于资料回答”。有时候把资料放在问题前面、并加一句“请仅根据以上资料回答”,效果就完全不一样。
| 故障现象 | 最可能的原因 | 优先排查方向 |
|---|---|---|
| 答得沾边不准 | 切片切断语义 | 切片策略、重叠 |
| 回答不稳定 | 资料重复冲突 | 去重、版本管理 |
| 专名搜不到 | 向量对关键词不敏感 | 混合检索、同义词 |
| 检索对但答错 | 提示词组装问题 | 上下文顺序、约束语 |
8. 关于 MCP 和知识库工具链的一点实践体会
热词里 MCP 出现频率很高,这里简单说下它和知识库的关系。MCP(Model Context Protocol)本质上是一套让模型和外部工具、数据源对接的协议。对知识库来说,它的价值在于把“检索”这件事标准化成一个工具:Agent 需要查资料时,通过 MCP 调用知识库服务,拿到结果再继续推理。这样知识库就不再是硬编码在提示词里的死数据,而是 Agent 可以按需调用的能力。
我自己的做法是把知识库封装成一个检索服务,通过 MCP 暴露出去,Agent 在需要时主动调用。好处是解耦——知识库更新不用动 Agent 代码,Agent 换框架也不用重做知识库。至于具体用哪个框架、哪个向量库,我的建议是别在选型上纠结太久,先用最简单的方案把链路跑通,效果不行再换。我见过太多人花两周对比向量数据库,结果连数据都没清洗干净。
提示:知识库的迭代是个长期活。上线只是开始,之后要持续收集“答错的问题”,反推是资料缺失、切片问题还是检索问题,然后针对性优化。没有一劳永逸的知识库。
最后分享一个我自己的习惯:每次调整切片或检索参数后,我都会固定用同一批 20–30 个真实问题跑一遍,记录准确率变化。没有这个基准测试集,你根本不知道改动是变好还是变坏,全靠感觉调参,最后一定是一团乱麻。这个测试集不用多复杂,把用户真实问过的问题攒起来就行,它是你优化知识库时最值钱的东西。