1. 别急着调Prompt,先看看RAG系统的"五段链路"
前阵子有个团队带着评测数据来找我,说他们做的大模型知识库问答系统上线之后效果一直不理想:用户问一个跟公司制度相关的问题,答非所问是常态;偶尔答对了,追问一句"依据在哪",模型又能把完全不存在的文件编号编得有模有样。他们一开始的判断是"提示词写得太弱",结果换了三轮话术,分数纹丝不动。
我接手之后做的第一件事,是让他们把"模型回答质量差"这个问题拆到一条完整的链路上看。RAG系统看起来是"检索一下、拼一下、让大模型回答",实际上至少串着五个环节:知识入库、检索召回、重排筛选、上下文组装、大模型生成。任何一个环节出问题,最后背锅的都是"生成出来的答案"。调Prompt当然有作用,但那只是五段链路里的最后一段,前面四段如果喂进来的东西是脏的、错的、不完整的,Prompt写得再漂亮也无非是把错误包装得更流畅。
我把这个系统里最常见的故障信号和对应的根因环节整理成了一个对照表,排查时基本就按这个表定位:
| 系统表现 | 大概率出问题的环节 |
|---|---|
| 检索结果总是一堆无关片段 | 知识入库的分块策略、检索算法单一 |
| 检索到的片段"沾边但不解渴" | 缺少重排序,Top结果被粗召回里的噪声占据 |
| 用户问题带指代、口语化,怎么都查不准 | 查询改写缺失或改写不到位 |
| 上下文塞满之后模型开始自由发挥 | 缺少上下文压缩与去重,组装方式太粗糙 |
| 答案流畅但引用是编造的 | 生成阶段没有任何边界约束与溯源机制 |
| 同一个问题,时好时坏无法复现 | 没有评估闭环,每次改完了靠感觉验收 |
这篇文章讲的11个策略,就是针对这五段链路逐个打补丁的实操方案。它们不是坐在实验室里推导出来的"理想架构",而是我在多个真实知识库问答项目里反复用过的组合拳。核心思路只有一个:别把"效果差"一股脑归因于模型不够聪明,先把RAG系统自己该干的活干明白。
下面先把完整的策略清单亮出来,后文会逐一展开:
- 知识库分块优化:按语义边界切,而不是按字数硬切
- 混合检索:关键词检索与向量检索并行,再融合分数
- 查询改写与扩展:先帮用户把问题问清楚
- 重排序精排:粗召回负责广撒网,精排负责选Top N
- 上下文压缩与去重:别让无关内容挤占模型注意力
- 结构化上下文:把检索段落组织成带编号的证据列表
- 引用溯源:答案必须带出处编号
- 无答案分支:证据不够时明确说"不知道"
- 动态检索决策:不是每个问题都有必要检索
- 提示词边界规则:用规则约束模型,而非花式人设
- 评估闭环与分阶段回归:没有评测口径,优化就是盲人摸象
2. 召回侧优先加固:混合检索、重排、改写、切块一个都不能少
2.1 混合检索:把"关键词能对上"和"语义能对上"的结果一起捞回来
很多RAG项目第一版只做向量检索,原因是实现简单、对语义理解友好。但上线之后就会发现一个典型场景:用户问"ABC-2024-001号合同的违约金条款是什么",向量检索很容易把语义相似的"合同违约责任"文档捞出来,反而对那个精确的合同编号不敏感。反过来,如果只用关键词检索,用户说"最近那个项目的成本有点失控,怎么降下来",关键词检索基本捞不到任何语义相关的内容。
所以第一条策略不是二选一,而是两个都要。我的做法是同时跑关键词检索和向量检索,各自取Top 50,然后对两路结果做分数归一化再融合。融合权重一般从"关键词0.3、向量0.7"起步,具体比例靠评测集调。归一化这一步特别关键:关键词分数和向量相似度分数不在一个尺度上,直接用原始分数相加等于让数值大的一方主导结果。我常用的归一化方法是min-max或者排名倒数融合,后者更省心——直接把两路结果的排序位置换算成分数,再按权重合并。
混合检索引擎做起来不复杂,但要注意两路检索的并发与超时。我的习惯是让两路并行执行,统一等最慢的一路返回,避免串行把延迟翻倍。
2.2 重排序:粗召回负责广撒网,精排才决定最终进Prompt的Top N
粗召回阶段为了召回率,通常会放进来很多"看起来沾边"的内容,直接把这几十个片段全部塞进Prompt既不现实也没必要。更好的做法是在中间加一层精排序,用交叉编码器对每一条"查询-片段"对打一个更精细的相关性分,再从粗召回结果里挑出Top 5到Top 6进Prompt。
为什么粗召回用双编码器、精排用交叉编码器?因为双编码器把查询和文档分别编码后算相似度,速度快但丢失了查询和文档之间的细粒度交互;交叉编码器把查询和文档拼在一起过模型,能捕捉到两者之间的精确匹配关系,精度明显更高,代价是每个候选对都要单独过一遍模型,慢很多。所以合理的分工是:粗召回负责从整个知识库里快速捞出50个候选,重排序再在这50个里精挑细选。
这里有一个非常容易踩的坑:重排序模型对领域敏感。通用的重排序模型在通用语料上表现不错,但你拿来做的是某个垂直行业的问答,比如合同条款、设备手册、内部制度,排序效果可能直接打对折。我遇到过一种情况,重排后排在第一位的片段反而是错误答案,真正对的证据排在第三位,排查了很久才发现是重排模型没有针对该领域微调。所以在垂直场景里,要么先收集一批"查询-文档-相关性标注"数据做微调,要么至少用人工抽检的方式验证一下重排结果是否符合直觉,再决定是否沿用通用模型。
2.3 查询改写与扩展:先帮用户把问题问清楚
用户发起的问题很少是标准查询。最常见的两类:一是带了指代,"我们2024年的营收是1.2亿,那毛利率呢","那毛利率呢"如果直接拿去检索,注定捞不到东西;二是过于口语化,"咱公司请假咋弄"这种句子直接检索,关键词匹配度很低。
查询改写就是先把用户问题转换成更适合检索的形态。具体做法可以是:把对话历史拼接进当前问题,让模型把指代关系消解清楚;也可以做查询扩展,让模型基于原问题生成3到5个不同侧重点的候选查询,分别检索后合并结果。
我一般在线上用一个小模型做改写,控制延迟和成本。改写链路设计成:"判断是否需要改写 > 改写 > 改写结果可信度检查",如果模型自己都觉得改写没把握,就退回原问题检索。这里要特别提醒:查询改写不是万能药。对已经写得很精确的问题,改写反而可能引入歧义。比如用户精确提问"编号XS-2033的设备检修记录",改写模型可能会画蛇添足扩展成"设备检修记录相关的所有内容",结果召回了一堆不相关的历史记录。所以我的原则是:优先处理指代消解和错别字,其次才是扩展,而且扩展结果必须与原始查询做一次相关性过滤。
2.4 分块策略:按语义边界切,而不是按固定字数硬切
知识库里的文档要喂给嵌入模型,受模型最大输入长度限制,必须先切成块。很多项目图省事直接按500字硬切,结果就是一段完整的技术流程被拦腰截断,表格被劈成两半,检索时经常只命中半截信息,后半截在另一个块里,最终答案自然残缺。
更好的分块策略是围绕"语义完整性"来做:优先按照标题、段落、列表项、表格结构这样的天然边界切;如果一段内容太长,再考虑在句号、分号这些语义相对完整的节点上二次切分。比如一份设备操作手册,会先按章节边界切,每个章节内部再按小节边界切,每块控制在200到500词之间,相邻块保留50到100词的重叠,避免边界处的关键信息被切断。
还有一个很有效的补充:为每个块生成若干条"假设性问题"作为索引。意思是在入库阶段,用大模型为每个块生成3到5个可能的用户问题,比如某个块讲的是"退款流程",就生成"退款需要什么材料""退款多久到账""退款申请怎么提交"等。检索时候选用用户问题的向量去匹配这些假设性问题,命中率往往比直接匹配原文更高。代价是入库成本增加,但换来的是召回效果的明显改善,性价比不错。
3. 生成侧的五件套:压缩、结构化上下文、溯源、动态检索、提示词
3.1 上下文压缩:检索出来的东西不能全都塞给模型
我见过不少项目的Prompt里塞了七八个检索片段,加起来三四千token,模型输出质量反而不如只给三个片段的时候。原因有两个:一是模型对长上下文的注意力分布并不均匀,夹在中间的信息容易被忽略;二是检索片段里原本就混着大量噪声,这些噪声会引导模型跑偏。
所以生成侧的第一件事不是优化Prompt,而是压缩进Prompt的内容。我的标准做法是:重排后先做一次**最大边际相关性(MMR)**去重,把内容高度重复的片段筛掉;然后根据重排分数和篇幅上限确定最终保留的片段数量;最后对每个片段做句级重要度筛选,只保留与查询相关性最高的句子或句子群。
这里要小心一个反面情况:压缩过头会丢掉关键数字或限定条件。比如一个片段里大部分是背景描述,只有最后一句写了"该条款仅适用于2024年之前签署的合同",如果压缩策略只看句级相关性,很可能把最后那句限定语丢掉,模型就会把适用范围扩大化。我的经验是:在做压缩的同时保留原片段中的数字、日期、专有名词和限定性短语,哪怕它们的相关性分数不是最高。压缩的目标是去噪声,不是去信息密度。
3.2 把上下文组织成"证据列表",模型才不容易跑偏
同样的内容,直接扔一堆段落进Prompt,和把这些段落组织成带编号的证据列表,模型的表现可以差一大截。原因不难理解:模型在没有编号的连续文本里很难把"哪条证据对应哪个来源"建立起来,出错了也没法追踪。
我现在习惯把所有检索片段整理成统一的格式,类似:
[1] 来源:设备运维手册-第三章-3.2节;内容:设备每月需要进行一次预防性维护,重点检查液压系统压力值,正常范围应在80-100bar之间
[2] 来源:设备运维手册-第五章-5.1节;内容:年度大修周期不得超过12个月,维修记录需存档备查
...
这样做的直接好处有三个:第一,模型在生成时可以明确引用"根据[1]"或"多个材料([2]、[3])均提到",引用行为更可控;第二,如果两个片段互相矛盾,模型更容易发现冲突并直接指出,而不是强行融合出一句错误内容;第三,后续做引用溯源时,只需要解析模型输出的编号即可,不需要再在长文本里找来源。这个改动几乎不增加任何成本,但效果非常显著,我甚至觉得它是11个策略里性价比最高的一条。
3.3 引用溯源与无答案分支:宁可说不知道,也不能瞎编
RAG产品的用户体验有一个分水岭:答案错了用户还能忍受,但模型"言之凿凿却完全是编的"用户绝对不会忍。大模型本身并没有"区分知识边界"的能力,不给它规则,它就会把检索材料里没有的信息也顺着用户的问题编出来。
我做的第一件事是在提示词里明确要求:回答必须标注引用来源编号,不允许出现没有编号支撑的内容。第二步是设置一个"无答案"分支——当重排之后所有候选片段的相关性分数都低于某个阈值,或者模型在生成时发现自己需要依赖超出材料的知识,就明确回答"当前知识库中没有找到相关信息",然后列出几个可能相关的文档标题让用户进一步确认。
这个分支看起来会让系统显得"不够聪明",但实际用户满意度反而是提升的。原因很简单:用户更信任一个会说"不知道"的助手,而不是一个把所有问题都答得无限自信的助手。
3.4 动态检索决策:不是每个问题都需要翻知识库
很多RAG系统不管用户问什么,都先做一轮检索,再喂给模型。这会在两类问题上浪费资源:一类是"你好""谢谢"这种寒暄,完全不需要检索;另一类是用户直接明说"根据你的常识回答"或者问的是普适性问题,检索反而可能把不相关的内容带进来。
动态检索决策就是在生成之前加一个判断层:根据意图或规则决定要不要检索、检索多少条。我的做法是先做意图分类,把问题分成"不需要检索"和"需要检索"两类;需要检索的问题再进一步分成"单轮查询"和"可能需要多轮查询",前者检索一次,后者可以根据中间结果再补一次检索。
这个策略带给我最大的收益其实是降低了噪声比例。不需要检索时就不检索,系统的答案相关性和响应速度同时提升。要注意的是分类层本身也可能出错,所以我的规则里有一条兜底:如果用户的问题里带"帮我找一下""根据我们公司制度""查询"等明显意图词,直接强制走检索,不依赖分类结果。
3.5 提示词工程在RAG里的真正角色:约定规则,而不是花式话术
很多人以为提示词工程是把人设写得活灵活现,但对RAG系统来说,提示词最重要的作用是划边界。我会在系统提示里固定放这几条:
- 只能依据给定的材料回答,优先引用带编号的片段;
- 材料没有覆盖的内容,必须明确说明"信息不足"或"未找到";
- 禁止编造文件编号、日期、数值或来源名称;
- 如果多个材料存在冲突,指出冲突的位置和内容,让用户自行核实;
- 回答最后列出用到的编号清单。
另外我会在提示词里放一个简短的few-shot示例,展示"好的回答"和"不好的回答"之间的差别。但示例要精简,一两个足够。整个提示词我控制在几百字以内,因为RAG系统里真正承载信息的是检索上下文,不是指令本身。如果提示词写得比上下文还长,反而会压缩模型处理材料信息的空间。
4. 组合方案怎么排优先级,54%的提升从哪来
4.1 我的组合打法:分三阶段逐步加固链路
这11个策略不建议一次性全上。原因很实际:如果同时改十个地方,效果提升了,你不知道是哪个改动起的作用;效果变差了,你也找不到罪魁祸首。我习惯分成三个阶段推进。
第一阶段做"地基加固",只改两件事:分块优化和混合检索。这两个改动主要影响召回数量和质量,动作最小、风险最低。地基打完之后跑一轮评测,确认基线水平。
第二阶段做"召回精排",加查询改写和重排序。这一阶段会让进入Prompt的片段更精准,噪声明显减少。但要注意,重排序之后原本排在前面的结果可能被打下去,如果业务依赖原来的排序逻辑,需要提前规避。
第三阶段做"生成侧约束",上上下文压缩、结构化上下文、引用溯源、无答案分支和动态检索决策。这个阶段会直接改变模型的输出风格,建议在一批有代表性的问题上做人工抽检,确认模型的"不知道"行为没有伤到正常问题。
4.2 效果提升的数据口径:立项前先定好评测集
54%这个数字不是拍脑袋拍出来的,也不是某个模型突然变聪明了,而是建立在"同一套评测集、同一个评分标准"上的前后对比。我在那个项目里用的评测集是一批覆盖典型业务的200个问题,每个问题都预先标注好了标准答案和答案来源文档。评分维度分成三项:答案忠实度(是否忠于材料)、答案完整性(关键信息有没有漏)、引用准确度(来源编号是否对得上),每项0到5分,加权合成百分制。
初始版本跑下来,这个系统的端到端可用性评分只有62分左右。经过三阶段优化之后再测,评分到95.5分,换算下来相对提升了54%。需要强调一句:这个数字只代表那个特定项目,不代表任何一个团队照着抄就能得到54%。但这个口径模型很有价值——它逼着所有人先回答一个问题:"什么叫效果变好了?" 如果这一点不定义清楚,后面做多少优化都无法评估。
所以我的建议是:优化RAG系统之前,先花两三天建一个20到50条问题的小型评测集,把关键指标跑通。哪怕起步粗糙,也比没有测评强。每个改动上线前跑一遍,用数据说话,而不是靠感觉拍板。
4.3 一组可供参考的配置参数实录
为了让这套组合方案更好落地,我把一个比较稳定的配置清单放在这里,可以作为起点,不要当成标准答案:
| 环节 | 配置参考 | 说明 |
|---|---|---|
| 分块 | 块大小200-500词,重叠50-100词,按段落/标题切 | 长文档优先按章节切,再按段落切 |
| 混合检索 | 关键词0.3权重 + 向量0.7权重,各取Top50后融合 | 权重需用评测集微调 |
| 重排序 | 对融合后Top20-30做精排,取Top5-6 | 垂直领域建议微调重排模型 |
| 上下文压缩 | 保留5-6个片段,总token控制在2000以内 | 数字、日期、限定语必须保留 |
| 无答案阈值 | 重排得分低于满分的35%时走无答案分支 | 阈值靠人工抽检校准 |
| 动态路由 | 意图分类 + 强制检索关键词兜底 | 分类模型要小而快 |
| 答案格式 | 强调引用编号,结尾列出引用清单 | 禁止无编号内容 |
这套配置在离线评测和在线灰度里的表现都比较稳定。但要提醒的是,这些参数都跟具体知识库的规模、文档类型和模型能力有关系,换一个场景就要重新调。我把它们列出来,是希望给你一个"可以抄的起点",而不是"必须套的终点"。
4.4 延迟与成本平衡:重排序虽好,也不能让它拖垮在线响应
加了重排序之后性能一定会多一块开销。交叉编码器对每个候选对都要单独前向计算,如果粗召回是50个片段,那就要算50次。实测下来,小规模的精排模型在CPU上处理50个短片段大概要增加40到100毫秒,GPU上会快一些,但也要20到50毫秒。对某些延迟敏感场景,这个开销不能忽略。
我的取舍是:在线服务只对融合后的Top20做重排,而不是对全部候选做重排,这样既能把噪声压下去,又不会让延迟翻倍。如果响应速度压力特别大,还可以对缓存命中率高的热问题直接返回缓存答案,完全跳过检索和重排。另外,混合检索的两路如果其中一路返回很慢,可以给那一路设置更短的超时时间,不让它拖慢整体链路。
成本方面,重排序增加的开销主要是推理资源,但换个角度想,如果重排序能让最终答案质量提升,减少用户反复追问的次数,总成本反而可能下降。这个账要按线上流量具体算,不能只盯着单次调用的CPU消耗。
5. 实测中的意外情况与我的取舍记录
5.1 哪些策略在某些场景下反而拖后腿
这套组合拳不是每个策略在每类场景里都有效,我挑几个实际遇到的反例说一说。
查询改写在一类场景下确实会拖后腿:用户提的是法律条文编号、合同编号、设备型号这类精确标识符。我的改写模块曾经把"根据合同CX-2022-0815第4条回答"扩展成了"根据合同相关条款回答",导致检索结果里混入了大量无关合同,重排之后真正的目标合同反而掉出了Top5。后面我做了硬编码保护:检测到"编号+年份+关键词"格式时,禁止改写,直接按原查询检索。
上下文压缩也出过问题。有一次系统把一段技术手册压缩成了"需要定期维护,注意压力值",原片段里"80-100bar"和"每月一次"被当成冗余句子丢掉了,结果模型给出的建议变成"应定期维护但未说明具体周期和压力范围",导致用户追问三次。之后我的压缩逻辑增加了对数值型和单位型短语的强制保留。
重排模型同样有过翻车记录。通用重排模型在某设备维修知识库上表现不稳定,经常把"如何更换滤芯"这类高频问题排在"滤芯型号对比"前面,而后者其实才是真正的答案所在。后来我收集了2000条人工标注的"查询-片段-是否相关"数据做了微调,排序才逐渐符合业务直觉。所以垂直领域里重排模型不要直接裸跑,至少要抽一批数据验证。
5.2 数据质量这个变量,被我低估了
优化做到后期,我发现一个扎心的事实:策略能带来的提升是边际递减的,而数据质量才是决定RAG系统上限的根本变量。同一个知识库,如果里面存在大量过期版本、互相矛盾的条款、残缺的章节,无论检索和生成侧怎么优化,用户都会在某一次查询中碰到错误信息。
有一次我们排查某类问题的错误率,定位到最后发现知识库里有新旧两版员工手册同时存在,新版说"年假10天",旧版说"年假7天",检索系统有时候召回新版,有时候召回旧版,模型没有能力判断版本差异,就只能随机抽到一个答案。这让我意识到,在上重排序、上提示词之前,先把知识库做一次标准化整理可能性价比更高:文档需要标注版本号、生效日期、适用范围;同一类的知识尽量统一格式;明显过期或冲突的文档先标记或下架。
我后来养成了一个习惯:每到一个RAG项目,先花时间摸清楚数据资产是什么形态、怎么流转、有没有责任人。如果数据本身一团糟,那11个策略只能让你从"很差"变成"不太差",想达到"好用"会很吃力。
5.3 如果只保留三个策略,我会选这三个
每次被人问"如果时间有限,哪三个策略必做",我的答案都很固定:结构化上下文、引用溯源与无答案分支、混合检索加重排。
结构化上下文和引用溯源解决的是用户最敏感的问题——模型一本正经地胡说八道。模型一旦必须按编号引用证据,并且被允许说"不知道",幻觉比例会肉眼可见地下降。混合检索加重排解决的是"答非所问"的问题,保证进到模型嘴里的材料跟用户问题有真实的关联。这三个策略覆盖了RAG体验的两条生死线:召回相关性和生成可信度。
至于动态检索、查询改写、缓存这些,都属于在有基础之上的进阶优化。先把地基打好,再去追求极致的成本和延迟,路会走得稳很多。
最后说点个人体会:RAG系统的优化没有一个固定的"做完"状态,它更像是对着一台有很多旋钮的设备做持续校准。每调一个旋钮都要知道为什么调、调完怎么验证,这比盲目堆策略重要得多。如果你正在做类似的项目,我的建议是不妨先从本文的11个策略清单里选最容易落地的两三条试起,建立一个最小评测集,用两周时间跑一轮前后对比。数据会告诉你下一步该往哪里使劲。