做一个项目做久了,你会发现一个特别有意思的现象:每次技术社区一聊 RAG,底下全是“烂大街了”“早就会了”的声音。但你再追问一句“你线上召回准确率多少、忠实度多少、多轮检索怎么解决冲突”,十个人里有八个开始含糊其辞。我做了三年知识库 RAG,从二十页的说明书到几千份的运维手册都折腾过,越来越确定一件事:烂大街的确实只是那条流水线——装个向量库、调个 embedding 接口、把文档切一切塞进去,然后检索、拼接、生成,三行代码就能跑通。但真正能把 RAG 做到生产可用、回答准确率稳定在高位的人,掰着手指头数得过来。因为流水线只是入口,真正的分水岭在这六处:切分、召回、知识组织、智能编排、冲突消解和评估体系。
这篇文章不聊 API 怎么调,也不贴那种“30 分钟搭建知识库”的保姆教程,我只想把这六处分水岭掰开揉碎讲清楚。无论你是底层用 LangChain 自己拼,还是用 Dify、LangChain4j 这类框架搭,这些坑一个都绕不过去。
1. 先聊聊“烂大街”这个错觉从哪来的
1.1 三行代码就能跑通的那个 RAG 长什么样
很多人对 RAG 的认知,来自一个非常标准的 demo:把 PDF 按固定字数切成几百个 chunk,调 embedding 接口把所有 chunk 转成向量,灌进向量数据库,用户提问的时候把问题也转成向量,用余弦相似度召回 top_k,拼进 prompt 扔给 LLM。这套流程用人话讲就是“先翻书找到候选段落,再让大模型照着候选段落答题”。不管是 Python 脚本、LangChain,还是 Dify、FastGPT,底层都是这一套。
这套东西跑通确实快,半小时都不用。但问题也恰恰藏在“快”里。我见过太多项目停在这个阶段就号称“知识库上线了”,结果领导一问“为什么这么简单的问题都答不对”,整个团队就开始互相看。原因倒不复杂:固定窗口切分把语义切碎了,单路向量检索召不回精确内容,上下文里塞了一堆无关片段,LLM 又被这些片段带偏。每一步单看都“能跑”,连起来就“跑不动”。
1.2 流水线思维的两大致命伤
第一个致命伤,是把所有问题都映射成“向量检索 -> 拼接 -> 生成”这一个套路。实际上 RAG 的真实世界长满了例外:有的查询是精确匹配(“这个接口的报错码是多少”),向量检索反而打不过关键词检索;有的查询要跨章节综合(“对比 A 和 B 两块模块的权限差异”),单次检索压根凑不齐信息;有的查询有时间属性(“最新的版本是什么”),不考虑时效就会把旧文档捞出来。固定流水线对这些场景几乎毫无还手之力。
第二个致命伤,是只关心流程“通不通”,不关心质量“行不行”。很多人跑通 demo 之后,唯一的验证方式是自己问两三个看得到的问题,或者看一下 top_k 有没有把答案捞回来(也就是所谓的 hit rate),然后就收工了。但线上真实的用户问题千奇百怪,没有一套评估指标和测试集,你连“改了一版切分之后到底变好还是变差”都说不清楚,更别提持续优化。RAG 不是搭完就结束的静态系统,它是一条需要反复校准的链路。
2. 分水岭一:文本切分——从“按字数切”到“按结构切”
2.1 固定窗口切分是第一个隐形坑
很多新手不理解:切分不就是把长文切成小块吗,有什么好纠结的?这么想的人,大概率已经被固定窗口切分坑过。所谓固定窗口,就是不看内容,只管“每 500 个字切一刀”。这种做法的最大问题,是会把一个完整的语义单元拦腰截断——表格被切开、代码块被切开、一个“第 3.2 节”的解释文字被切到下一个 chunk 里。检索的时候,用户问“表格里第三列的含义”,召回来的 chunk 里只有表头没有表体,LLM 就只能靠猜。
这就像切西瓜,你要是不看纹路乱剁,籽和汁水全散在案板上,端上桌的每一块都像“卡住喉咙的半颗籽”。固定窗口就是这个切法:你觉得“块块都一样大”,实际上“块块都不完整”。我自己处理过一份带大量分节标题的制度文档,按 512 字切完,检索“报销审批需要几级签字”时,召回的 chunk 总共四段,题目和正文横跨了三个 chunk,模型最后给出一个“推测大概是”的答案。把文档结构看清楚再切,才把这个问题解决。
2.2 顺着文档结构切:先看标题,再看语义
改进方向其实很朴素:先顺着文档原生结构找边界,再用大小窗口兜底。以 Python 生态为例,一个很实用的思路是:
- 如果文档本身是 Markdown 或者带标题的富文本,优先按“标题层级树”切——把
#、##、###当成天然的段落边界,标题下面内容太少就向上合并,太多就按二级边界继续拆。 - 表格、列表、代码块这类整体性很强的元素,要单独识别并保留完整性,宁可让一个 chunk 是“纯表格”,也不要让半个表格插进正文里再切一半。
- 对完全没有结构的 PDF 或纯文本,先做段落检测(按连续换行、缩进、编号规律),再做辅助切分。
本地环境中做这一步,工具其实不少。unstructured库可以抽取结构化元素,langchain-text-splitters里也内置了MarkdownHeaderTextSplitter和RecursiveCharacterTextSplitter,前者按标题切,后者按字符列表递归回退。但要注意,工具只是省劲,关键是你得知道想保住什么——在我这儿的原则是“一个 chunk 内尽量只有一个完整的论点和它必要的上下文”。
2.3 重叠、粒度与中文场景下的几个参数
切分参数没有银弹,但有几个经验值值得记下来。字符级chunk_size在中文场景下建议 400-800 字,overlap按 10%-20% 设置,也就是 50-100 字。overlap不是玄学,它是为了弥合“上半段提到一个概念,下半段才开始解释”这类场景。但这只是兜底,不能指望 overlap 解决所有语义断裂——真正的兜底是前面的结构切分。
另外要留个心眼:很多框架的chunk_size是按 token 算的,不是按字符算。中文一个汉字大概对应 1-1.5 个 token,如果你按英文生态的 512 token 来切,实际只有 300 多个汉字,切得很碎。所以用tiktoken或你所用模型的分词器先数一下自己的语料,再定参数,别想当然。
提示:切分完之后,一定要做一次“肉眼抽检”。随机抽 20 个 chunk,看每个 chunk 读起来是不是通顺、是不是有完整语义。自动化指标再好,也替代不了这一眼。
3. 分水岭二:召回策略——从“单路向量”到“混合检索+重排”
3.1 单路向量的天花板和两大致命弱点
切分做好之后,第二个分水岭是召回。很多人以为 embedding 模型是万能的,给文档转成向量就完事。但单路向量检索有两个天生短板。第一,它擅长语义模糊匹配,不擅长精确关键字匹配。用户问“错误码 40207 在哪个类里定义”,向量检索经常把“40207”和“40208”搞混,因为向量空间里这两个 token 的语义分布太接近了。第二,它受 embedding 模型本身质量限制极大。开源模型和顶级商业模型在复杂同义改写上的差距,直接决定了召回上限。你换个更强的 embedding 模型,hit rate 可能立刻提几个点。
当然,向量检索也有不可替代的优势:它能理解“报销流程”和“费用审批流程”是同一个意思,这是传统关键词检索做不到的。所以方向不是二选一,而是把两种能力拼起来,用混合检索打组合拳。
3.2 混合检索与 RRF 融合:朴素但可靠
混合检索的标准配置是“BM25 关键词检索 + 向量语义检索”。BM25 负责精确命中、术语匹配,向量负责语义扩展、同义改写。两路各自检索出 top 50,然后用 RRF(Reciprocal Rank Fusion)做融合排序。RRF 的核心公式非常朴素:对同一个文档,看它分别在两路结果里排第几名,把1 / (k + rank)加起来,k 一般取 60。
这个方案的优点是不需要做分数归一化——BM25 的分数和余弦相似度根本不是同一个量纲,直接相加是耍流氓,RRF 用 rank 规避了这个问题。实现也很简单,五六十行代码就能写完。我在一个运维手册项目里做了一次实验:单路向量召回 hit rate@10 是 0.61,加上 BM25 和 RRF 之后提到 0.78,提升非常明显。
3.3 查询改写与 HyDE:让“问法”更贴近“答案”
混合检索解决了一部分问题,但还有一类情况它搞不定:用户的问法本身就是模糊的。比如“这个能不能走报销”,文档里根本不会有这句话,但可能有“差旅费报销流程”“采购报销申请”之类的表述。这时候与其硬检索,不如先让 LLM 把用户问题改写成几个更具体的子查询,再去检索。这就是查询改写(query rewriting),通常用一个小模型加一条 prompt 就能实现。
比查询改写更进阶一点的是HyDE(Hypothetical Document Embeddings),核心思路是:先让 LLM 根据问题凭空生成一个“假设性的标准答案”,然后把这段假设性文本转成向量去召回,再拿召回的真正文档片段去支撑最终回答。这个做法听起来有点绕,但实测效果常常不错,因为基于完整句子的向量表达比基于短问题的向量表达更接近库中文档的语义分布。代价是多一次 LLM 调用,延迟多一点,但召回质量往往值回票价。
3.4 重排:把“看起来像”变成“真的是”
前面无论怎么混合,召回阶段选出来的其实还是“候选池”。真正决定答案质量的,是把 top 50 浓缩成 top 5 的那一下重排。重排器一般用交叉编码器(cross-encoder),把问题和文档片段拼在一起送入模型,直接输出一个相关性分数,比向量检索那种“分别编码再算距离”的方式精度高得多。
在本地可落地的开源模型里,bge-reranker-base或者bge-reranker-v2-m3是口碑不错的选择,CPU 上也能跑,一次推理大概几十毫秒到几百毫秒。实际用法是:混合检索召回 top 50,送进重排器重新打分,保留 top 3-5 塞进 prompt 上下文。很多项目做完这一步,回答质量和忠实度会肉眼可见地上升一个档位。
注意:重排不是把得分最高的问题丢进去就自动解决一切。如果切分阶段已经切碎了语义,重排只是帮你在碎块里选相对不碎的,本质还是治标不治本。所以顺序很重要:先把切分做好,再谈重排。
4. 分水岭三:知识组织——从“扁平库”到“本体/Graph”
4.1 扁平向量库看不见的“关系”
第三个分水岭藏得比较深,也是我认为最能拉开长期差距的地方:知识怎么组织。普通 RAG 是一个扁平向量库,每份文档被切开的 chunk 是孤岛,chunk 和 chunk 之间除了“都在一个库里”之外没有任何联系。但你仔细想想真实知识库,它根本不是一个平坦的文本池子——它有实体,有属性,有类别,还有关系。比如“离职率”和“员工满意度”不是两个不相干的词,它们都挂在“人力资源”这个节点下面;“A 产品依赖 B 模块”这种关系,扁平库里根本表达不出来。
这个缺点的官方叫法很贴切:知识割裂。用户问“这个功能受哪些模块影响”,如果库里有五份文档分别描述了五个模块,单路召回往往只能捞出其中一两份,答案必然残缺。你会觉得“这个 RAG 怎么这么笨”——它不是笨,是它的知识组织形式压根没给“关系”留位置。
4.2 本体 RAG:先画 schema,再装知识
本体 RAG(Ontology RAG)的思路是先定义领域里的概念、属性和关系,形成一个 schema,再往这个 schema 里填实体。用人话说,就是先画一张“知识地图”,再往地图上标地名。比如做制度问答,schema 可以是“部门 - 流程 - 审批节点 - 材料清单”,每一条制度文档都会被抽取成:哪个部门、走什么流程、需要什么材料、有哪几个审批节点。
好处是显而易见的:用户问“采购流程需要几个节点”,系统可以直接沿着流程类实体找,而不是大海捞针;用户问“和报销相关的事项有哪些”,系统能根据“报销”这个节点把周边实体都拉出来。很多团队觉得自己知识库“结构化程度低”做不了本体,其实不是——你只需要一套清晰的本体定义,配合信息抽取 prompt,就能把非结构化文档变成结构化事实。难点在于建 schema 需要懂业务的人深度参与,这也是很多技术团队迟迟不推进的原因。
4.3 GraphRAG:很香,但不是谁都能吃下
除了本体,另一个被频繁提到的是 GraphRAG。微软开源的 GraphRAG 能自动从文档里抽实体和关系建图谱,再用社区检测总结“社区摘要”,检索的时候既走原始文本又走摘要。这个方案对“跨文档综合类问题”的效果确实好,我见过一个案例,一个需要对比多个文档观点的问题,普通 RAG 答得东拼西凑,GraphRAG 能给你输出一个结构清晰的分点综述。
但代价也很实在:建图成本高、索引时间长、需要额外的图数据库和推理 token,小团队和小项目很容易被这一步拖垮。GraphRAG 不是银弹,它适合知识面广、关系密集、文档量大的项目。如果你的知识库只有 50 份文档,用户问题也偏单一,上 GraphRAG 大概率是给自己找麻烦。
4.4 给普通团队的折中方案:metadata 过滤 + 轻量图谱
那不上 GraphRAG 是不是就没办法了?也不是。比较务实的第一步是“metadata 过滤”——给每个 chunk 打标签:部门、文档类型、版本号、业务线。检索时先用这些标签做粗过滤,再做向量/混合检索。举个例子,用户来自运维部门,就先把检索范围限制到运维文档,剩下 2000 个 chunk 里再召回,精度会好看很多。
如果 metadata 不够,再上“轻量图谱”:只抽重点实体和关系,比如产品、模块、功能点之间的依赖关系,单独存成一张小表。检索时先做实体链接,找到相关实体,再沿着关系扩展出关联文档的 ID 列表,把这些 ID 加入到召回的过滤条件里。这套方案比 GraphRAG 轻得多,却能解决相当一部分“跨文档关联”问题。
5. 分水岭四:Agentic RAG——从“固定流程”到“动态编排”
5.1 固定流水线的盲区:一切问题都走同一条路
食话实说,我一度对 Agentic RAG 持保留态度,觉得“Agent”是被炒起来的词。但后来遇到几个真实场景,让我改变了看法。固定流水线最大的盲区,是它让“简单问题”和“复杂问题”付出了同样的代价,但都没得到合适的处理。
拿最简单的“路由器”举例:用户问“密码忘了怎么办”,这种问题根本不需要召回到 50 个 chunk,再重排再生成;它甚至可以直接走 FAQ 直达答案。但用户问“比较这三个方案在成本和维护上的差异”,就需要多次检索、跨文档比对,甚至要调外部工具。固定流水线对待这两种问题的策略是一样的,结果就是:简单的被过度处理,复杂的没被充分处理。
5.2 Router、多步检索与自我反思的落地思路
Agentic RAG 的核心,是把“检索几次、怎么检索”也交给 LLM 或规则去决策,而不是写死成一条链。最常见的三个形态:
- 路由(Router):先判断问题类型——事实类、流程类、对比类、闲聊类,不同类型走不同的检索策略。路由可以用一个小模型分类,也可以用规则加关键词兜底。
- 多步检索(Multi-step Retrieval):一个问题先查出一批文档,从这些文档里再提取出下一步要查的关键词或实体,继续查第二轮。适合“先定位文档、再看细节”的场景。
- 自我反思(Self-Reflective RAG):生成答案之后,再让模型回头检查“答案是否被检索内容支持”“检索内容是否足够”,如果不够就重新检索、重新生成。这个 loop 虽然多耗几次模型调用,但能显著压掉幻觉。
如果你用的是 LangGraph 这类编排框架,这些形态其实就是几个有条件的节点和循环边。在 Dify 里也可以用工作流画出来。关键是思维转变:RAG 不再是一条直线,而是一棵“先判断后走向不同分支”的路线树。
5.3 别让“智能”变成失控
不过,我更要提醒的是成本问题和失控风险。Agentic RAG 的延迟和 token 消耗是固定流水线的数倍甚至一个数量级,而且每一步“智能决策”都可能出错:路由分错类、反思误判、多步检索越查越偏。这些问题在 demo 阶段不明显,一上生产就会暴露。
我的建议是分两层走:先用规则实现 80% 的路由逻辑(能用规则就不用模型),再把真正需要模型判断的部分交给 LLM;而且一定要设置“最大检索轮数”“最大重写次数”这类硬上限,防止某个问题无限循环烧 token。简单问题求快,复杂问题求全,这是 Agentic RAG 的本质,但“快”和“全”的边界要由业务规则而不是模型自由发挥来定。
6. 分水岭五:冲突消解——让答案内部先“打一架”
6.1 冲突从哪来:同一件事,文档里居然有两种说法
如果说前四层分水岭解决的是“找得到、找得准”,那第五层解决的是“找回来的东西互相打架怎么办”。这个问题做知识库的人很容易遇到:一份文档说“发票报销时限是 30 天”,另一份说“当月发票必须当月报销”,第三份说“时限 60 天”。单独召回任何一份,答案都像是对的;三份一起召回,LLM 就开始精神分裂。
这类冲突来源很典型:版本更新(新旧文档并存)、口径不同(财务和行政各说各话)、适用场景不同(日常报销和差旅报销规则本来就不一样)。有意思的是,这个问题的本质和 CPU 流水线里的“数据冲突、控制冲突”非常像——指令流水线里,后一条指令依赖前一条的结果,设计不好就会互相覆盖;RAG 的上下文流水线里,不同来源的段落也在“抢回答权”。CPU 解决冲突靠转发、乱序执行、分支预测,RAG 解决冲突靠不上运气,而靠元数据、优先级和 prompt 约束。
6.2 从元数据到冲突消解规则
第一步是给每个 chunk 带上充分的元数据:文档标题、版本号、发布时间、来源部门、适用范围。没有这些信息,冲突消解就是空谈。有了元数据之后,可以设定几个简单的优先级规则:
- 版本优先:同一主题,取版本号或发布时间最新的。
- 口径优先:如果用户身份是财务人员,优先取财务口径的文档。
- 粒度优先:具体场景的文档优先级高于通用文档。
第二步是上下文组装层面做“冲突感知”。我常用的一种做法是:检索回来的 top 文档如果来自不同版本或不同部门,先把它们按“事实一致性”做个聚类,同一个事实不同说法的放一起,在 prompt 里给 LLM 这样一段指令——如果检索内容之间存在冲突,请分别列出不同来源的说法,并说明你采纳某一说的依据。这个思路比让 LLM 自己强行“和稀泥”要稳得多。
6.3 让 LLM 学会说“不知道”和“不确定”
冲突消解的底线是:别让 LLM 假装没看见冲突。很多人觉得 LLM 给出的答案只要“看起来流畅”就行,但知识库问答最忌讳的就是把互相矛盾的片段揉成一段“看起来都对的废话”。我在实践里会给系统加一个“忠实度检查”步骤:把生成结果拆成若干断言,逐条对照召回内容,没被支持的断言就打回重写。这一步可以放在 LLM 生成答案之后,用一次额外的模型调用完成。
我更愿意把这一步称为“让 RAG 承认自己的边界”。一个知道自己什么时候不确定、并且能说“根据文档 A 是 X,根据文档 B 是 Y,两者存在差异”的系统,远比一个假装什么都懂的聊天机器人可靠得多。
7. 分水岭六:评估体系——没有度量,就没有分水岭
7.1 hit rate 只是入场券,不是成绩单
前面说了五层分水岭,每一层改造都需要验证:改了切分之后到底有没有变好?换了重排器之后值不值?如果你只拿一两个问题自己试,那永远得不到可靠结论。这也是为什么最后一个分水岭,我认为是评估体系。很多人知道要测 hit rate——也就是“正确答案在不在召回的 top_k 里”,但 hit rate 只衡量了“有没有捞到”,没衡量“答案生成得好不好”。你可以召回率 100%,但 LLM 还是答得稀烂;也可以召回率 70%,但每一条回答都忠实有据。
所以真正可用的评估体系,至少要分成两层:检索层和生成层。检索层看召回率、MRR、NDCG 这类指标;生成层看忠实度(faithfulness)、答案相关性(answer relevance)、上下文相关性(context relevance)。打个比方,hit rate 相当于“候选人有没有被拉进面谈名单”,忠实度相当于“入职之后说的话是不是都有实锤证据”。
7.2 一套可落地的离线评估组合
不考虑商业化产品的话,最省钱的评估方式是“人工标注小测试集 + LLM-as-Judge”。先手工构造 50-100 组(问题,标准答案,涉及文档)的三元组,作为黄金测试集。然后每次改完系统,跑一遍测试集,得到一组指标。用 LLM 打分的时候要注意:让评分模型只对比“标准答案”和“系统答案”的事实重合度,不要比文风,否则会被带偏。
我自己常用的指标和达标线大致如下表,给大家一个参考:
| 指标 | 含义 | 我常用的达标线 | 说明 |
|---|---|---|---|
| Recall@10 | 真实答案是否在召回前 10 条中 | ≥ 0.85 | 检索层基础指标 |
| MRR | 真实答案排位越靠前越好 | ≥ 0.7 | 衡量排序质量 |
| 忠实度 | 生成内容是否被召回内容支持 | ≥ 0.85 | 生成层最重要指标 |
| 答案相关性 | 是否答非所问 | ≥ 0.85 | 防止跑题 |
| 上下文相关性 | 召回上下文是否干净 | ≥ 0.6 | 噪声控制 |
这个表格不是标准答案,但它给了我一个判断基线:任何改动如果让忠实度跌了,哪怕 hit rate 涨了,我也会回滚。
7.3 没有测试集的话,先别谈优化
很多项目做不好评估,是因为压根没有测试集,问“为什么改完没变化”也只能靠感觉。我的建议是测试集不贪大,但要有代表性:覆盖简单事实类、流程类、对比类、冲突类、跨文档类,还要包括几个“库里根本没有答案”的问题——后者能测出系统会不会硬编幻觉。日常维护中,每遇到一个线上答错的案例就沉淀进测试集,半年下来这就是团队最值钱的资产。
线上环境还要做采样监控。离线指标解决不了“真实用户问法千奇百怪”的问题,所以我会定期从日志里抽取问题,人工复查回答质量,或者用评分模型批量打分,把低分案例拿出来分析。没有评估,前面所有“分水岭”都是空谈——你以为你跨了过去,其实只是换了个姿势在同一个坑里躺着。
8. 一次从流水线到分水岭的改造实录
8.1 初始状态:demo 能跑,但业务不买账
空谈这么多原则,不如看一次具体改造。去年我经手一个内部运维知识库项目,语料大概 3000 份文档,包含制度、操作手册、FAQ 表格。初始版本就是标准的固定窗口切分加单路向量检索,Dify 里拖一拖就上线了。业务方用了两周,反馈非常集中:技术问题能答个大概,但流程类问题常常给错步骤,制度类问题新旧版本混着答,时间一长大家都不信它了。
当时我们拉了一个问题清单,大约 40 个高频错题。抽了三类典型问题分析:一是制度类(报销时限不一致)、二是流程类(申请权限要哪几步)、三是精确类(某个错误码对应的模块)。这三个类型暴露了三个不同层级的缺陷,正好对应前面讲的切分、召回和冲突处理。
8.2 改造过程与关键参数
第一步改切分。文档里有大量的标题和表格,我们先用unstructured把表格单独抽出来,正文按标题层级切,再合并过短的段落。chunk_size从原来的 512 字符改为 700 字符,overlap设 80。这一步做完,肉眼抽检 chunk 完整度明显上升,表格不再被切断。
第二步改召回。本地部署了一个bge-m3向量模型,加上 ES 或本地 FTS 做 BM25 关键词检索,两路各取 top 50,用 RRF(k=60)融合,然后再过bge-reranker-base重排取 top 5。代码大致长这样:
def hybrid_search(query, retriever_bm25, retriever_vec, top_k=50, k=60): hits_bm25 = retriever_bm25.retrieve(query, top_k) hits_vec = retriever_vec.retrieve(query, top_k) fused = {} for rank, doc_id in enumerate(hits_bm25): fused[doc_id] = fused.get(doc_id, 0) + 1 / (k + rank + 1) for rank, doc_id in enumerate(hits_vec): fused[doc_id] = fused.get(doc_id, 0) + 1 / (k + rank + 1) ranked = sorted(fused, key=fused.get, reverse=True) return ranked[:50] def rerank(query, doc_ids, reranker, top_n=5): pairs = [(query, doc_map[i].text) for i in doc_ids] scores = reranker.rerank(pairs) top_ids = [doc_ids[i] for i in sorted(range(len(scores)), key=lambda j: -scores[j])][:top_n] return top_ids第三步处理冲突。给所有入库存档打上版本号和生效日期,检索阶段根据问题类型做版本过滤,prompt 里明确要求“如果存在不同口径的答案,不要强行合并,要分列说明并标注来源”。这一步用到的召回 prompt 片段大概是:
如果检索到的文档之间存在冲突: 1. 分别列出不同来源的说法,并标注文档标题和版本。 2. 给出你的判断依据(例如:以最新版本为准,或以XX口径为准)。 3. 如果无法判断,明确回答“存在冲突,需人工确认”。第四步才上了一点轻量 Agentic 路由,规则很简单:FAQ 类问题直接查表;制度类问题走混合检索加版本过滤;对比类问题先做一次实体链接定位相关条款,再二次检索。没有上复杂反思循环,因为成本和延迟不划算。
8.3 改造前后的效果对比
用 60 条黄金测试集跑了一轮,关键指标变化如下:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| Recall@10 | 0.61 | 0.82 |
| 忠实度 | 0.38 | 0.74 |
| 答案相关性 | 0.52 | 0.81 |
| 平均回答耗时 | 约 1.2s | 约 1.8s |
忠实度从 0.38 提到 0.74,是这次改造最值得的地方。代价是耗时多了 0.6 秒,换来的是回答终于“有据可依”。业务方的反馈也印证了变化:原先高频错题里有 28 道被彻底解决,另外 6 道表现为“系统能说明来源不一致”,不再硬编一个错误答案。这个案例没有用到什么神秘技术,全是前面讲的分水岭工程化实践。
9. 高频问题排查与避坑清单
9.1 常见问题速查表
如果读者已经搭了自己的知识库,正在为某些问题头疼,我整理了一张高频问题排查表,可以对着查:
| 现象 | 可能原因 | 优先排查动作 |
|---|---|---|
| 答案答非所问 | 召回噪声太多或路由错误 | 看 top 5 召回内容,是否与问题主题无关;检查重排是否生效 |
| 答案内容正确但细节缺失 | 切分把细节切碎了 | 检查 chunk 完整性,看看是不是表格/列表被切断 |
| 新旧版本混答 | 没有版本过滤 | 给 chunk 打版本号,检索时过滤旧版本 |
| 同一个问题,两次回答不一样 | 上下文不稳定或 prompt 随机性 | 固定 prompt 模板,降低 temperature,检查召回是否稳定 |
| 简单问题回答太慢 | 路由不生效,走了重链路 | 加规则路由,FAQ 直答 |
| 数据库里明明有答案,却查不到 | embedding 对精确词不友好 | 加 BM25 关键词检索,走混合召回 |
| 回答“看起来对,实际编造” | 忠实度检查缺失 | 增加 faithfulness 校验,或要求答案标注引用来源 |
9.2 我踩过最深的三个坑
第一个坑是过于信任 embedding 模型。早期我觉得换一个更强的 embedding 就能解决所有召回问题,结果发现精确数字和代码错误码根本不吃这一套,最后还是靠混合检索把短板补上了。
第二个坑是盲目上 GraphRAG。看过一次 GraphRAG 演示之后,我觉得自己也要搞一个图谱,结果花了两周做实体抽取和调图库,效果提升不到 10%,还拖慢了索引速度。后来把它降级成“轻量实体关系表 + metadata 过滤”,性价比反而高得多。
第三个坑是改完系统不看指标,凭感觉调参。有一段时间我反复调top_k和overlap,今天觉得 800 好用,明天又觉得 600 好,折腾一周毫无进展。后来逼着自己先搭测试集和评估脚本,所有的调参都看指标说话,才从“炼丹”变成“工程”。
9.3 还想往上走的话,可以往这几个方向扩展
如果你把上面六处分水岭都跨过了,下一步可以考虑几个延伸方向:多模态知识库(文档里带图表、截图怎么办,那是一个全新的“切分”问题)、增量更新与知识时效管理(知识库不可能一次性建完,如何低成本地持续更新)、以及更细粒度的权限控制(不同部门的人看到的知识范围不一样)。这些话题如果大家感兴趣,之后我可以再单独开一篇聊。但无论如何,别急着追逐新概念,先把切分、召回、冲突和评估这四件事做扎实,你的 RAG 就已经甩开大多数“流水线选手”了。