多跳问题的拆解与回填:让RAG答对「对比类」问题的工程方法
为什么问「A和B哪个适合我」时RAG总是答非所问?拆开多跳检索的三层结构,给出查询拆解、子答案回填、交叉验证与合成提示的完整实现思路。
单跳检索死在什么地方
用户问:“我们公司 50 人,主要做跨境 B2B,应该选 Coze、Dify 还是 n8n?”
这不是一个查询,是三个查询加一次综合判断。如果用这段原话去做向量检索,命中的会是”Coze 介绍""Dify 介绍""n8n 介绍”三个彼此不相关的片段,模型拿到三份产品简介,写出一段谁都没错的废话。
多跳(multi-hop)问题的失败,通常不在模型,在检索阶段就把问题结构丢了。
三层结构:拆解 → 并行 → 回填
第一层:识别问题类型并拆解
不是所有问题都需要多跳。先做分类:
| 类型 | 例子 | 是否需要拆解 |
|---|---|---|
| 事实型 | ”Dify 支持私有化部署吗” | 否 |
| 对比型 | ”A 和 B 哪个更适合我” | 是,按维度拆 |
| 条件型 | ”预算 5 万,选哪个” | 是,先查约束再匹配 |
| 递进型 | ”A 的团队做的另一个产品是什么” | 是,串行依赖 |
| 聚合型 | ”这个领域有哪些主流方案” | 部分,需要多轮补全 |
拆解的关键是按维度而不是按实体。对比型问题应该拆成”成本""部署方式""上手难度""扩展性”四个维度,每个维度去检索两个产品的信息——这样才能形成真正的对比,而不是两段简介并列。
第二层:并行检索与结构化抽取
每个子问题独立检索,但不要把检回的原文直接给最终模型。中间加一步抽取:对每个子问题,产出一个结构化结论。
{
"dimension": "部署方式",
"options": {
"Coze": "SaaS 为主,开源版功能受限",
"Dify": "支持私有化,社区版可自托管",
"n8n": "完全可自托管"
},
"confidence": 0.82,
"sources": ["doc-1231", "doc-8842"]
}
这一步的价值:把 3 份 2000 字的文档压缩成 300 字的结构化事实,误差不会在合成阶段被放大,而且可以逐维度追溯来源。
第三层:合成前先查缺口
得到结构化结果后,做一个”完整性检查”:哪些维度有选项缺失?哪些置信度低于 0.6?
缺口处理有两条路:
- 补检索:针对缺失的维度重新生成查询(“n8n 定价 2026”),再跑一轮
- 显式声明:补不到就在答案里说”关于 X,公开资料不足”
第二条比沉默和编造都重要。用户能接受”不知道”,不能接受”编的”。
容易忽略的一点:递进型问题的时间顺序
“OpenAI 的前首席科学家后来创办的公司叫什么”这类问题,两个跳之间有严格的先后依赖。并行检索会失败,因为第二个查询必须等第一个的答案。
处理方式:在拆解阶段标注依赖关系,形成一个 DAG,按拓扑序执行。实现上不需要复杂框架,一个带依赖检查的循环就够:
pending = {q for q in subqueries}
while pending:
ready = [q for q in pending if deps_satisfied(q)]
results = run_parallel(ready)
pending -= set(ready)
合成阶段的提示词要挡住三个坏习惯
最终合成时,模型最容易犯三个错:
- 平均主义:把两个产品说得一样好。要在提示里要求”必须给出明确的推荐条件和适用边界”。
- 忽略用户约束:用户说 50 人、跨境 B2B,答案却完全不提这些条件。要把约束单独列出来并要求逐条回应。
- 伪造缺失信息:某个维度没查到,就自己补一个”通常来说”。要显式允许”资料不足”。
一份可用的合成提示骨架:
以下是用户问题与结构化检索结果。
要求:
1) 先用一句话给出结论与推荐
2) 逐维度对比,每个结论后标注 [来源ID]
3) 若某维度 confidence < 0.6,标注"资料有限"
4) 最后给出"什么情况下不选这个方案"
5) 不得引入检索结果以外的事实
第 4 条特别有用——强迫模型思考反面,往往能暴露出它其实没把握。
成本与收益的权衡
多跳方案的成本是单跳的 2–4 倍(查询数 × 模型调用)。所以必须做前置判断:
- 先用一个极小的分类器(甚至规则)判断是否为多跳,单跳问题走原路
- 拆解阶段用小模型,合成阶段才用大模型
- 缓存”维度 → 结构化结果”,同一实体的重复查询可直接复用
经验数据:在对比类问题占 30% 的客服场景里,引入多跳后这类问题的满意度提升了 25 个点,整体成本上升 40%。这个交换通常值得——因为这类问题正是用户最不满意的部分。
小结
多跳不是”多检索几次”。它的核心是把问题的结构显式化:拆成维度、并行取证、结构化中间结果、查缺口、再合成。做不到中间的结构化,检索多少次都只是把噪音堆得更高。