RAG 查询改写与歧义消解落地

日期:2026-07-24

一、项目背景

我们当时在给一家制造企业做智能客服知识库时,采用的是私有化 RAG 流水线(文档解析 → 切片 → 向量化 → 检索重排,向量库用 Milvus,服务化层跑在 vLLM 上)。刚开始接真实用户提问,效果让人头疼:用户很少像我们写文档那样规范提问,大多是"这个怎么弄""刚才那个单子啥情况""跟上次一样处理"——口语化、缺上下文、还带歧义。

直接把原始 query 丢进向量库检索,召回率惨不忍睹。比如用户问"退货流程",我们知识库里其实有"7 天无理由退货""质量问题退货""换货流程"好几篇,原始检索只模糊命中一两篇,答出来的东西要么不全、要么答非所问。更糟的是多轮对话:用户第二句说"那这个要多久",模型根本不知道"这个"指退货还是换货,直接拿着残缺上下文去检索,越聊越偏。

我们当时拉了两周客服日志,发现约三成问答是因为 query 表达问题导致检索失准,而不是知识库没内容。结论很清楚:在检索之前,必须先做查询改写与歧义消解,把用户的"人话"翻译成检索友好的"标准问法"。

二、落地场景

这个项目里我们落地了三块能力。

第一,查询改写补全上下文。我们在网关编排层(Orchestration)串了一个"改写小模型 + 检索大模型"的链路:先由一个小模型(轻量分类/改写模型)结合对话历史把当前 query 改写成自包含的标准问法,再拿改写后的 query 去向量库检索。多轮场景里,改写模型会显式把指代("这个""刚才那个")替换成上一轮识别出的实体。

第二,歧义识别与澄清反问。我们给改写模型加了一个"歧义标记"输出:当 query 可能映射到多个知识库入口(如"退货"既可能指无理由也可能指质量问题),模型输出多个候选意图并打分,若最高与次高意图分差小于阈值,就生成一条澄清反问推回用户("您是指 7 天无理由退货,还是质量问题退货?"),而不是替用户硬猜。

第三,改写后检索增强与重排。改写后的标准 query 会同时走向量检索(Milvus 近邻)和关键词检索(BM25),两路结果在重排阶段(rerank)融合,再由生成模型产出答案。我们还把"改写前原始 query"作为兜底:若改写后检索命中反而变差,触发回退到原始 query 检索,避免改写模型自己跑偏。

三、关键技术挑战与解决思路

挑战一:多轮上下文补全容易"补错"。早期我们用朴素拼接(上一轮 query + 当前 query),结果用户说"换个说法"时模型把旧实体硬塞进来。改成让改写模型输出"已解析上下文"结构化字段(实体、意图、时间窗),当前轮只合并相关字段,无关历史显式丢弃,歧义率明显下降。

挑战二:歧义识别的阈值难定。分差阈值设太高,几乎每个问题都反问,用户嫌烦;设太低,真歧义被忽略。我们用脱敏后的客服标注集做离线评估,把阈值标定在"最高/次高意图概率比 < 1.5 即反问",并在网关层对反问频次做监控,超过比例就回调阈值,形成数据闭环。

挑战三:改写保真不跑偏。改写模型有时会"脑补"用户没说的条件,比如把"退货"自动补全成"7 天无理由退货",反而错了。我们加了一条硬约束:改写只允许"补全上下文/消歧",不允许"新增业务条件";并在评估集上用"改写保真度"指标(改写后 query 与原始意图的一致性)做回归,保真度低于阈值就回退原始 query。

挑战四:改写后重排。两路召回融合时,向量检索对语义近的片段打分高、但可能漏掉关键字面条款;BM25 补字面却不懂语义。我们的重排模型对两类结果统一编码再打分,并给"来自澄清后明确意图"的片段加权,确保用户确认过的意图优先进入上下文。

四、效果数据(可量化、脱敏)

我们在一个约 12 万条问答的脱敏评测集上对比了"直接检索"与"改写 + 消歧后检索":

  • 检索命中率(Top-5 含正确答案)从约 68% 提升至约 89%,提升约 21 个百分点;
  • 改写准确率(改写后 query 语义与用户真实意图一致)在抽样人工评估中约为 92%;
  • 转人工率:因检索答非所问导致的转人工,从约 23% 降至约 11%,降幅约一半;
  • 回答准确率(端到端,含生成)从约 71% 提升至约 88%;
  • 澄清反问占比控制在总提问的约 9%,既消解了歧义,又没过度打扰用户。

以上为脱敏示意值,但在我们那套评测看板上趋势稳定。

五、可复用经验总结

经验一:先改写再检索。把"理解用户"和"检索知识"拆成两段,改写模型专门负责把人话变标准问法,检索只管在标准空间里找,职责清晰、各自可优化。

经验二:歧义要澄清而非硬猜。当系统不确定用户到底问哪个意图时,一条温和的反问成本远低于一次错误回答带来的信任流失,尤其在客服、合规这类场景。

经验三:改写要守边界。改写只补全、不脑补;加保真度回归和原始 query 兜底,防止改写模型自己引入错误。

经验四:多路召回 + 重排比单路稳。向量懂语义、BM25 懂字面,重排融合能把两者长处都吃上,再拿澄清意图做加权,效果更扎实。

案例片段(已脱敏): 查询改写与澄清服务配置片段(节选自项目 rag_rewrite.yaml):

rewrite:
  model: qwen2-1.5b-instruct     # 轻量改写小模型,跑在 vLLM 实例
  mode: multi_turn
  inject_context_fields: [entity, intent, time_window]
  drop_irrelevant_history: true
  fidelity_guard:
    no_new_business_condition: true   # 禁止脑补新增业务条件
    fallback_to_raw_query: true       # 保真度不足时回退原始 query
ambiguity:
  clarify_when_ratio_lt: 1.5    # 最高/次高意图概率比 < 1.5 触发澄清反问
  max_clarify_ratio: 0.12       # 反问占比监控上限,超阈回调
retrieval:
  vector: { engine: milvus, top_k: 5 }
  keyword: { engine: bm25, top_k: 5 }
  rerank: { fuse: true, clarified_intent_boost: 1.3 }

案例片段(已脱敏): 一段多轮对话的改写与澄清日志:

[user ] 这个要多久?
[rewrite] 解析上下文: entity=7天无理由退货, intent=退货时效, 指代消解=上一轮"退货流程"
[rewrite] 改写query: "7天无理由退货的审核和处理时效是多久?"
[retrieval] Top-5 命中, 含《无理由退货时效说明》
[user ] 那质量问题的呢
[rewrite] intent候选: [质量问题退货(0.58), 换货(0.39)] ratio=1.49 < 1.5
[rewrite] 触发澄清反问 -> "您是指质量问题退货的时效,还是换货的时效?"