检索结果不准、生成内容脱离原文,这四个字在RAG项目里能劝退大多数人。
我做过几个文档问答和知识库助手,发现只要涉及“4.2”这种小节编号,往往就是团队从“能跑通”转向“能靠谱”的关键节点。上一篇还在讲管线搭好,这一篇就开始暴露问题:用户问“报销流程多久到账”,系统检索回来的却是“差旅费报销标准”;或者模型依据检索片段生成时,把“不适用于外籍员工”直接忽略,输出成“所有员工均可享受”。
不是模型不够强,也不是向量库选得不好,而是检索和生成中间的“信息保真”链条断了。这篇文章我直接把常见症状、排查思路、能落地的调整手段写透,包括分块参数怎么定、召回重排怎么做、提示词怎么约束引用,还有我自己项目中踩坑换来的经验。适合正在做知识库问答、客服助手、文档摘要这类RAG应用的人参考。
1. 先定位问题:你遇到的到底是“检索不准”还是“生成不贴”
很多人一上来就调Embedding模型,其实先做诊断更省时间。同样一个query,把检索到的top5片段单独拿出来看,如果片段内容本身和问题不相关,那是检索的锅;如果片段相关但最终回答没用上、或者改写了关键信息,那是生成的锅。这两个问题解决路径完全不同。
1.1 两种典型症状的表现差异
检索不准的表现很直接:top5里能看到大量无关片段,或者相关片段排在后面,被截断没进入上下文。比如问“合同违约金的法定上限”,返回的片段全是“合同签订流程”“违约金发票税率”这类表面关联但实际无用的内容。
生成不贴的表现更隐蔽:检索片段完全正确,但模型在整合时丢失了限定条件、数字、否定词,或者补了一段原文没有的“常识性解释”。比如原文写“该政策自2024年1月1日起实施,不适用于存量项目”,模型回答却变成“自2024年1月1日起实施”,有条件限制的信息被无意间删掉。
这两种症状经常同时出现,但处理优先级不同。我建议先把检索结果截图保存下来,用最原始的方式肉眼评估——把query喂给检索接口,不接生成模块,直接看返回片段。这一步能帮你快速判断主攻方向。
1.2 用“最小测试集”快速区分责任方
准备20到30个典型问题,覆盖数字、否定、限定条件、专有名词、多轮表述这五类。写一个简单脚本,遍历所有query,输出每个query的top5片段原文,然后人工标注“相关/部分相关/不相关”。
如果“不相关”占比超过30%,问题集中在检索链路:分块不合理、Embedding模型不匹配、检索方式单一、没有重排序。如果“相关”占比很高但最终回答仍有错误,问题集中在生成链路:提示词约束不足、上下文拼接方式有问题、解码参数不合适。
我遇到过最典型的案例:检索top1片段明明包含答案,但模型回答了一堆通用套话。原因是系统提示词写得太宽泛,模型把“结合资料”理解成“参考资料但不限于”。后面把提示词改成“仅基于给定的检索片段回答,如果片段中没有答案,直接回答不知道”,问题立刻缓解。
2. 检索不准的根源拆解:分块、Embedding、召回策略
检索准不准,本质上取决于“查询语义”和“文档片段语义”之间的匹配度。匹配度低,不是某一步的错,而是从原始文档到向量表征的整条链路都在损耗信息。
2.1 分块策略:不是所有文档都该切成512字符
很多人直接把PDF按固定字符数切成512个token的窗口,这恰恰是最容易出问题的点。固定长度分块会切断段落和章节的语义边界,比如一个完整条款被切成两半,前半部分讲适用条件、后半部分讲执行时间,查询“适用条件”就只能召回前半部分,模型看到的上下文不完整,自然答不准。
更好的做法是根据文档结构分块。表格按行列语义拆分,条款按编号层级拆分,介绍性内容按段落和主题句拆分。如果切出来的块过短,比如只有一两句话,可以把相邻的小块合并,保证每个检索单元至少包含一个完整的“语义命题”。
我在处理合同类文档时,用章节标题+条款编号作为boundary。一个条款块包含“第X条 定义”到下一个条款前的全部内容。实测下来,这种分块方式比固定512字符分块在召回命中率上高出20%左右,尤其针对“依据第几条”这类精确查询效果明显。
分块长度没有标准答案,但有一个经验区间:中文场景下每个块控制在200到800个字符比较稳妥。太短则缺乏上下文,太长则向量表征被稀释,且超过模型上下文窗口后还会带来成本问题。最好用验证集跑一版不同分块大小的对比,看检索命中率和最终回答准确率的综合变化。
2.2 Embedding模型选择:通用模型和领域模型差距很大
Embedding模型决定了文本被映射到向量空间后的语义距离。通用模型对“长尾业务词”和“专有缩写”的表征往往不够精准。比如“ROI”在通用模型里可能靠近“投资回报率”,但在业务语料里可能表示“区域运营指标”。如果词表里没有充分对齐,检索结果就会跑偏。
我的建议是先用开源通用模型跑基线,再用业务语料做二次微调。微调Embedding模型的成本不高,几百条“问题-相关片段”对就能有明显效果。也可以用领域预训练模型直接替换,但注意中文领域模型和英文术语混合场景要单独验证。
另外还要注意Embedding模型的维度。高维度(如1024/1536)表征能力更强,但存储和检索开销也大;低维度(如384/768)响应更快,适合大规模文档。不是维度越高越好,关键是匹配你的数据规模和延迟要求。
2.3 召回策略:向量检索不是万能药
只依赖向量检索的RAG,遇到关键词精确匹配、缩写、数字区间这类场景很容易翻车。比如用户搜索“IS-9527标准”,如果分块里写的是“IS 9527”“9527号国际标准”,向量距离很可能较大,纯向量召回就有风险。混合检索是常见解法:向量检索负责语义相似,关键词检索(如BM25或基于词频的算法)负责精确匹配,然后合并两个结果集。
合并召回结果时,我习惯用RRF(Reciprocal Rank Fusion)这类融合算法,而不是简单去重拼接。RRF给每个文档在多个排序列表中的名次取倒数求和,能有效提升两个检索器共同命中的文档的权重,又能保留单一检索器的强信号。
实现上不复杂。先分别得到向量top20和关键词top20,然后对两个列表里的文档按照RRF公式计算融合分,重新排序后取top5或top10。这样既不会丢掉语义关联,也能兜住精确词汇匹配。实测在包含大量产品型号和工单编号的文档集里,混合检索能把命中率从65%拉到85%以上。
3. 生成不贴原文的解决思路:让模型“死磕”检索片段
检索上来了,生成还是会漂。原因在于大模型天生倾向于“自由发挥”。想让它严格贴原文,除了在提示词上硬约束,还要从上下文组织方式、模型参数、结果校验几个角度一起下手。
3.1 提示词设计:把“参考”改成“必须基于”
很多RAG系统提示词写的是:“请根据以下资料回答用户的问题。”这个句式在模型看来,资料是参考信息,不是事实边界。我经过多轮调优,把提示词改成如下结构:
你是一个严谨的问答助手。你只能依据上下文列表中提供的文档片段来回答问题。回答时遵循以下规则:
- 如果问题可以在片段中找到明确答案,直接提取原文中的关键信息作答,不得额外添加未出现的细节。
- 如果片段中只有部分信息,明确说明已知信息和缺失信息。
- 如果片段中没有相关信息,直接回答“根据提供的资料无法回答”,禁止编造。
- 涉及数字、日期、条件、限制词时,必须原样引用并说明适用前提。
- 回答中标注引用来源序号,如[1]、[2]。
这段提示词的核心是“禁止编造+引用约束”。别小看“标注来源序号”这一句话,它能让模型在生成时更注意片段边界,减少幻觉。同时,把来源序号加到上下文中,也方便前端展示溯源,用户看到“引用自合同第X条”时信任度完全不同。
3.2 上下文组织:片段顺序和去重影响输出质量
多个检索片段拼接进上下文时,顺序有讲究。很多实现直接按检索分数从高到低拼接,但模型对上下文不同位置的信息关注度不同——更倾向于关注首尾内容。如果关键答案在第二个片段中间,可能被模型忽略。
我的做法是:把检索片段按与问题的相关性排序后,再做一次“答案预判”式的重排。具体来说,先让模型或轻量分类器快速判断每个片段与问题是否直接相关,把直接相关的片段放在上下文最前面或最后面,间接相关的放在中间。同时删掉明显重复或互相矛盾的片段,避免模型困惑。
另外一个容易被忽略的点是:片段拼接时要加入分隔符和元信息。比如在片段前面加上“【片段1 来源:合同-第4章-第2条】”,让模型知道每个片段的边界和出处。这比密密麻麻一句话接一句话要清晰得多,模型的follow能力会好不少。
3.3 解码参数与其他技巧:调低温度不代表万事大吉
把temperature调低确实能减少随机性,但并不能解决“事实错误”问题。事实错误更多来自模型对被检索片段的“不自觉补全”。更有效的组合是:
- temperature设为0.1到0.3之间,减少发散。
- top_p设置在0.8到0.9之间,保持一定多样性但不过度。
- 加上“禁止重复原文未提及的数值和结论”的负面约束。
如果使用开源模型,还可以尝试在生成后做“答案-原文一致性校验”。用一个小模型或规则脚本检查回答中的关键数字、实体是否在检索片段中出现。如果出现“答案里的数字在原文片段里找不到”的情况,就把该回答拦截,改为“资料不足,无法确认”。这种方法虽然简单,但在生产环境里能挽回大量不合理输出。
4. 重排序:被很多人跳过却效果显著的环节
检索系统返回top20,直接塞给生成模型不仅浪费上下文,还会引入噪声。重排序(Rerank)是连接检索和生成之间的“精排层”,目的就是把真正和问题相关的片段排到最前面。
4.1 为什么要单独加一层Rerank
向量检索和关键词检索都是“粗排”,它们追求的是召回率,宁可多返回也不漏掉。但生成模型只关注精准的上下文。如果top5里混入两个无关片段,模型可能会被干扰,生成偏离原文的内容。
重排序模型通常是交叉编码器结构,问题和片段一起输入,直接输出相关性分数,比双塔embedding更精准。缺点是速度较慢,不适合对超大量文档实时打分。所以典型流程是:先用向量/混合检索粗召回top20到top50,再用重排序模型精排取top5到top8,最后再交给生成模型。
我见过最夸张的一个优化案例:加了重排序后,最终回答准确率提升了将近30个百分点。原因就是top5里真正相关的片段从1个变成了3个,模型有了足够的事实支撑。
4.2 重排序模型选择与融合策略
开源生态里有一些做Sentence-Level Rerank的模型,中文效果不错的也有几个。选型时注意看训练数据是否包含中文,以及模型在公开榜单上的表现。生产环境先跑一个小样本测试,比较“直接取top5”和“重排后取top5”的准确率差异,选效果稳定的上。
重排序结果要和原有检索分数融合时,最简单的方式是:直接把重排序分数作为主排序依据,原得分只在重排分数相差极小的时候作为tie-breaker。如果重排序模型输出的是0到1的相关性概率,可以设置一个阈值,低于阈值的片段直接丢弃,减少上下文污染。
另外,重排序不仅仅适用于文本片段。如果你检索的是表格或图片,也可以用跨模态模型来排序,但复杂度会上一个台阶。先从纯文本开始,见效最快。
5. 构建评测闭环:没有评价指标,优化就是盲人摸象
检索和生成是两部分,但如果只凭感觉调参,很容易出现“这次调好了,换个问题又坏了”的循环。正确做法是建立一套可重复的评测集和指标,每次改动都跑一遍,用数据说话。
5.1 离线评测:召回率、命中率、忠实度
对于检索模块,可以标注一批“查询-相关片段”正负样本。指标用召回率(top20内包含至少一个相关片段的查询占比)和命中位置(相关片段在top5内的平均排名)。更严格的可以算MRR(倒数排名),相关片段排得越靠前,分数越高。
对于生成模块,要分别评估:
- 忠实度:生成答案中的关键信息是否都能在上文片段中找到依据。可以人工标注,也可以用自然语言推理模型自动判断。
- 完整性:问题对应的信息点在答案中是否全部覆盖。
- 避答率:遇到无法回答的问题时,系统是否如实说“不知道”,而不是强行编造。
这些指标不需要全上,先选2到3个核心的就行。重点是保证评测集的多样性,覆盖不同文档类型和问题类型。
5.2 在线反馈与Bad Case迭代
离线指标只能反映一部分问题,线上真实用户提问更复杂。我习惯定期从日志里捞Bad Case,按错误类型分组:检索召回错误、上下文信息缺失、生成遗漏限定条件、生成过度泛化。每类每周至少分析10个具体案例,形成记录表。
根据Bad Case的记录倒推优化方向。如果很多Bad Case是“关键词能匹配但语义检索没召回”,那就在混合检索里加大关键词权重。如果很多是“相关片段在top3里但答案不贴”,那重点调提示词和片段顺序。总之,单点优化不如闭环迭代,每次改动都跑一遍离线评测集,同时抽看新Bad Case,确保没有引入了新问题。
6. 一整套可落地的优化顺序参考
这个问题没有银弹,但我可以给你一套我试过、效果比较稳定的操作顺序,从低投入高回报的开始,避免一上来就微调模型。
第一步:把分块方式从固定长度改成按文档结构切分,这几乎零成本,收益却最明显。
第二步:建立50条左右的评测集,包含业务真实问题,标注相关片段和标准答案。
第三步:把纯向量检索改成混合检索,引入关键词召回和RRF融合。
第四步:加入重排序模型,粗召回top20,精排取top5。
第五步:重写生成提示词,明确禁止编造,要求引用片段序号,并调低temperature。
第六步:增加简单的一致性校验,在返回给用户前过滤掉明显与检索片段冲突的答案。
第六步做完,绝大多数“检索不准、生成不贴原文”的问题都能显著改善。之后如果还想继续提升,再考虑领域Embedding微调或微调生成模型,但收益边际就小了,投入产出比要看具体情况。
我个人实际操作中的体会是:最隐蔽的问题往往出在分块和上下文拼接方式上。很多团队花大力气换了更强的Embedding,最后发现效果提升还不如把合同条款完整切开来得明显。检索管道是一条链路,哪里信息丢失,最终都会体现在生成质量上。
最后分享一个我一直在用的小技巧:把检索片段和最终回答放在同一个日志里,可视化对比。每次生成完,把命中的前3个片段连同回答一起打到调式面板上,一眼就能看出模型用了哪个片段、有没有乱发挥。这个习惯帮你省下大量猜原因的力气。