RAG 如何处理复杂问题?查询改写与多步检索 前面一篇文章里我们讨论了 RAG 的 Prompt 应该怎么写。Prompt 的核心作用是约束模型基于检索资料回答并在资料不足时不要自由发挥。但在真实系统里还有一个问题经常被低估用户的问题本身并不总是适合直接拿去检索。很多用户不会按照知识库里的标题、术语、字段名和文档结构来提问。他们会用口语表达会省略上下文会把多个问题揉在一句话里也会提出需要跨文档、跨步骤才能回答的问题。比如这个政策现在还能不能用跟去年有什么区别 如果我已经申请过一次还能不能再申请 这个接口线上报错是不是和权限配置有关 新版本里这个字段还需要传吗这些问题看起来只是普通提问。但对 RAG 来说它们并不简单。如果系统只是把原始问题直接丢给向量检索很可能召回不稳定甚至完全找不到真正需要的资料。这篇文章就讨论一个更接近真实场景的问题RAG 如何处理复杂问题为什么需要查询改写、多查询召回和多步检索。为什么复杂问题不能只靠一次检索最小 RAG 系统通常是这样工作的用户问题 ↓ Embedding ↓ 向量检索 ↓ 返回 TopK ↓ 模型回答这个流程适合简单问题。比如试用期员工能不能休年假如果文档里有对应制度向量检索很可能能召回相关片段。但真实用户经常会问更复杂的问题。第一类是省略上下文的问题。比如这个还能用吗如果系统不知道“这个”指的是哪份制度、哪个接口、哪个产品功能就无法检索。第二类是多条件问题。比如上海员工试用期内申请年假需要提前几天提交这里同时包含地域、员工状态、假期类型和流程要求。如果只用一句原始问题检索可能只召回“试用期年假”漏掉“上海地区规则”或“申请提前时间”。第三类是对比问题。比如新旧版本的报销审批流程有什么区别这个问题至少需要找到旧版本和新版本两类资料再做对比。一次检索可能只召回其中一边。第四类是因果或排障问题。比如订单创建接口返回 403是不是因为 token 过期这个问题可能需要查错误码、权限规则、认证机制、接口文档和日志说明。它不是一个单纯语义相似问题。第五类是多跳问题。比如负责这个系统的部门有哪些审批权限系统可能需要先查“这个系统”归属哪个部门再查该部门的审批权限。这不是一次检索能稳定完成的。这些场景说明复杂问题的难点不只是“召回质量”还包括“问题理解”。RAG 不能只问我该检索哪些文档还要先问用户这个问题到底想查什么Query Rewrite先把问题改写成适合检索的表达Query Rewrite 就是查询改写。它的目标不是改变用户问题的意思而是把用户原始表达改写成更适合检索的查询。很多用户问题是口语化的。比如还没转正能不能休年假知识库里可能写的是试用期员工年休假按入职时间折算。如果直接检索“还没转正”可能不如检索“试用期员工 年休假”稳定。所以可以改写成试用期员工 是否可以休年假 年休假 折算规则再比如用户问接口下单的时候数量还要传吗知识库里可能写的是POST /orders quantity 参数 是否必填可以改写成订单创建接口 POST /orders quantity 参数 必填规则这类改写能提高检索命中率。Query Rewrite 常见目标包括把口语表达改成正式术语。补充可能的业务关键词。消除代词和省略信息。把模糊问题改成更明确的检索表达。将上下文依赖问题改写成独立问题。比如多轮对话中用户上一轮试用期员工能不能休年假 用户这一轮那需要提前几天申请第二轮问题“那需要提前几天申请”单独看是不完整的。它应该被改写成试用期员工申请年假需要提前几天提交这就是上下文补全型 Query Rewrite。Query Rewrite 不是让模型随便扩写查询改写很有用但也有风险。它不是让模型自由发挥把用户问题扩写成一大堆可能相关的内容。改写必须保持原意。比如用户问试用期员工能不能休年假合理改写是试用期员工 年休假 是否可以申请 折算规则不合理改写是试用期员工 年假 病假 事假 调休 加班 离职结算后者加入了用户没有问的内容会导致检索范围变宽噪声变多。再比如用户问quantity 参数是不是必填如果上下文里已经知道用户在问POST /orders可以改写成POST /orders quantity 参数 是否必填但不能改写成订单接口 所有参数 是否必填这会偏离用户意图。所以 Query Rewrite 要遵守几个原则。第一不改变问题含义。第二不凭空加入未经确认的实体。第三不扩大到用户没有问的范围。第四尽量保留关键约束。第五必要时输出多个候选查询而不是把所有意图混成一个长查询。在工程上可以给改写模型一个明确 Prompt请将用户问题改写为适合知识库检索的查询。 要求 1. 保持用户原意不要扩展到未提及的问题。 2. 保留实体、时间、版本、地域、接口名、参数名等约束。 3. 如果问题依赖上下文请补全必要上下文。 4. 输出 1 到 3 条检索查询。这样比直接让模型“优化问题”更可控。Multi-query Retrieval一个问题生成多个检索角度复杂问题经常不是一个查询就能覆盖的。这时候可以使用 Multi-query Retrieval也就是多查询召回。它的思路是针对同一个用户问题生成多个不同角度的检索查询然后分别召回资料再合并结果。比如用户问新员工入职后多久可以申请电脑审批是谁负责这个问题其实包含两个点新员工什么时候可以申请电脑 电脑申请审批负责人是谁可以生成多个查询新员工 入职 电脑申请 时间 电脑设备申请 审批负责人 IT 设备申请 流程 审批分别检索后系统可能召回资料 A新员工入职设备申请流程 资料 BIT 设备审批角色说明 资料 C办公设备发放时限再经过合并、去重和 Rerank选择真正支撑答案的内容。多查询召回适合几类场景。第一用户问题包含多个子问题。比如申请条件是什么审批流程要多久第二用户问题包含多个关键词表达。比如“电脑”“设备”“办公终端”可能指向同一类文档。第三用户问题需要同时查概念和流程。比如先查“什么是专项审批”再查“专项审批怎么提交”。第四用户表达很模糊需要从多个角度召回。比如这个报错怎么处理可以分别查错误码、接口文档、排障手册。但多查询召回也不能滥用。每多一个查询就会增加检索成本和后续排序压力。如果一个简单问题生成十几个查询系统会变慢也会召回更多噪声。所以比较合理的做法是简单问题1 条查询 中等复杂问题2 到 3 条查询 复杂问题拆成子问题后分别检索查询数量应该由问题复杂度决定而不是固定越多越好。问题拆分把复合问题拆成子问题有些复杂问题不是改写一下就够了。它们需要拆分。比如用户问新版本报销制度和旧版本相比申请条件、审批流程和额度有什么变化这个问题至少包含三个对比维度申请条件变化 审批流程变化 报销额度变化还包含两个版本新版本 旧版本如果直接用原问题检索系统可能召回一堆相关制度但很难保证每个维度都覆盖。更好的方式是拆成子问题1. 新版本报销制度的申请条件是什么 2. 旧版本报销制度的申请条件是什么 3. 新旧版本报销制度的审批流程有什么区别 4. 新旧版本报销额度有什么变化然后分别检索再汇总答案。问题拆分适合对比类问题。多条件类问题。多实体类问题。多步骤流程类问题。需要分别查不同资料来源的问题。再举一个技术文档场景。用户问订单创建接口报 403可能是什么原因应该怎么排查可以拆成1. 订单创建接口 POST /orders 的鉴权要求是什么 2. 403 错误码在订单系统中表示什么 3. token 过期和权限不足分别如何排查 4. 订单创建接口常见错误处理流程是什么这样比一次检索“订单创建接口报 403 怎么排查”更稳定。因为系统会主动覆盖不同信息维度。但问题拆分也有风险。如果拆得太细后续合成答案会更复杂。如果拆错方向会导致检索偏离。所以拆分前最好先判断问题类型。不是所有问题都需要拆分。Multi-hop Retrieval一步检索不够就分步查Multi-hop Retrieval 可以理解为多步检索。它和多查询召回不完全一样。多查询召回通常是并行的生成多个查询 ↓ 同时检索 ↓ 合并结果多步检索是串行的第一步检索 ↓ 根据第一步结果生成第二步查询 ↓ 继续检索 ↓ 汇总答案举个例子。用户问负责 Alpha 系统的部门有哪些审批权限如果知识库里没有一篇文档同时写“Alpha 系统”和“审批权限”一次检索可能找不到答案。多步检索可以这样做第一步查询Alpha 系统 归属部门 负责人 结果Alpha 系统归属数据平台部。第二步查询数据平台部 审批权限 结果数据平台部可审批数据导出、权限开通和报表发布。最后回答Alpha 系统归属数据平台部因此可参考数据平台部审批权限。再看一个接口问题。用户问v2.3.1 版本里订单创建接口的 quantity 还需要传吗多步检索可以是第一步查接口POST /orders quantity 参数第二步查版本变更v2.3.1 订单创建接口 quantity 参数变更第三步合并判断当前版本参数说明 版本变更记录这类问题不是一个 Chunk 就能回答的。它需要系统先找到一个中间事实再根据中间事实继续查。这就是 Multi-hop Retrieval 的价值。Query 处理应该先做问题分类查询改写、多查询、多步检索都很有用。但它们不应该对所有问题都默认开启。一个简单问题如果被复杂处理反而会增加成本和错误率。比如用户问试用期员工能不能休年假这个问题可能只需要一次改写或一次检索。没必要拆成五个子问题。所以更合理的做法是先做问题分类。可以把问题分成几类简单事实查询 流程查询 对比查询 多条件查询 排障查询 上下文依赖查询 多跳查询 资料不足或不明确查询不同类型使用不同策略。比如简单事实查询直接检索或轻量改写 流程查询保留步骤、角色、时间等关键词 对比查询拆成多个版本或对象分别检索 多条件查询保留所有约束必要时多查询 排障查询拆成错误码、接口、配置、处理流程 上下文依赖查询先补全上下文 多跳查询分步检索 不明确查询先追问而不是强行检索这能让系统更稳。因为不同问题需要不同处理方式。Query 处理不是越复杂越好而是要和问题类型匹配。什么时候应该追问用户复杂问题处理里有一种情况很重要问题不完整时不要硬检索。比如用户问这个还能不能申请如果没有上下文系统不知道“这个”是什么。这时强行检索很可能乱召回。更好的做法是追问请问你指的是哪类申请例如年假申请、报销申请、设备申请或权限申请。再比如用户问新版本有什么变化如果不知道是什么系统、什么文档、哪个版本也应该追问请补充你想对比的系统或文档名称以及新旧版本范围。RAG 系统不一定每次都要给答案。当问题缺少关键实体、时间、版本、对象时追问往往比盲目检索更可靠。可以设定追问条件缺少核心对象。缺少版本范围。缺少比较对象。缺少用户上下文。多个解释都可能成立。检索结果分散且没有明显高置信来源。追问不是失败。追问是为了获得更准确的问题。这对于企业知识库尤其重要。因为错误答案的成本通常比多问一句更高。一个可落地的复杂问题处理流程把上面的内容合在一起一个可落地的流程可以这样设计原始用户问题 ↓ 问题分类 ↓ 判断是否需要追问 ↓ Query Rewrite ↓ 必要时问题拆分 ↓ Multi-query Retrieval ↓ 必要时 Multi-hop Retrieval ↓ 候选结果合并 ↓ 去重和 Metadata Filter ↓ Rerank ↓ 上下文压缩 ↓ Prompt 生成答案这里有几个关键点。第一不是所有问题都进入完整流程。简单问题可以走短链路。复杂问题才走长链路。第二Query Rewrite 要保留原意。不能为了召回更多内容而改变用户问题。第三问题拆分要服务答案结构。拆出来的子问题最后应该能组合成清晰答案。第四多步检索要记录中间结果。比如第一步查到了哪个实体第二步基于哪个实体继续查。第五最终答案仍然要回到 Prompt 约束。即使做了多步检索资料不足时也不能编。日志很重要复杂问题处理比普通检索更难排查。如果没有日志系统答错时很难知道问题出在哪里。至少应该记录原始问题 问题分类结果 是否触发追问 改写后的查询 拆分后的子问题 每个子问题的检索结果 多步检索的中间事实 候选合并结果 Rerank 排序结果 最终上下文 Prompt 版本 最终答案这样才能定位问题。如果答案错了可能是问题分类错了。也可能是 Query Rewrite 改偏了。也可能是子问题拆错了。也可能是某一步检索没有召回关键资料。还可能是最终 Prompt 没有处理资料冲突。复杂链路越长日志越重要。不要只记录最终答案。要记录中间过程。这也是 RAG 工程化和简单 Demo 的区别。常见误区第一个误区是所有问题都做 Query Rewrite。有些问题本身已经很清楚。比如ERR_1024 表示什么这类问题可能直接关键词检索更好。过度改写反而可能丢掉精确符号。第二个误区是多查询越多越好。查询越多召回越多噪声也越多。后续 Rerank 压力会变大系统延迟也会上升。第三个误区是拆分后忘了合并。问题拆分只是中间步骤。最终还是要回到用户的原始问题给出一个完整回答。第四个误区是不处理上下文依赖。多轮对话中用户经常会说“这个”“那它呢”“和上一个一样吗”。如果系统不补全上下文检索会很不稳定。第五个误区是不追问。问题缺关键信息时强行回答很容易错。第六个误区是把多步检索当成 Agent 万能推理。多步检索应该有清晰边界。它是在文档中逐步查证不是让模型随意规划一长串不受控操作。第七个误区是没有评估。复杂问题处理上线后要用真实问题集评估。否则很难知道 Query Rewrite 和多步检索到底提升了质量还是只是增加了复杂度。怎么评估复杂问题处理是否有效评估不能只看模型答案是否流畅。应该关注几个指标。第一改写是否保持原意。如果 Query Rewrite 改偏了后面检索再好也没用。第二关键资料是否被召回。复杂问题通常需要多个资料片段支撑。要看这些片段是否都被召回。第三子问题是否合理。拆分后的子问题应该覆盖原始问题不应该遗漏关键条件。第四多步检索中间事实是否正确。如果第一步中间事实错了第二步会沿着错误方向继续查。第五最终答案是否完整回答原问题。不能只回答其中一个子问题。第六资料不足时是否会追问或拒答。复杂问题里承认信息不足很重要。第七延迟和成本是否可接受。复杂链路会增加调用次数。如果质量提升不明显但成本明显上升就需要简化策略。可以准备一组复杂问题测试集多条件问题 对比问题 多轮上下文问题 排障问题 版本问题 跨文档问题 资料不足问题然后分别比较直接检索 Query Rewrite Multi-query Retrieval 问题拆分 Multi-hop Retrieval这样才能判断哪种策略真正有效。落地建议如果你正在做 RAG 系统可以按下面顺序推进。第一步先收集真实复杂问题。不要凭空设计复杂链路。先看用户到底怎么问。第二步做轻量问题分类。至少区分简单事实、流程、对比、排障、多轮上下文和资料不足。第三步只对需要的场景启用 Query Rewrite。尤其是口语化、上下文依赖和术语不匹配问题。第四步为多条件和对比问题设计拆分策略。拆分结果要能回到原问题。第五步对跨实体、跨文档问题引入多步检索。但要控制步数避免链路失控。第六步保留所有中间过程日志。包括改写、子问题、检索结果、Rerank 结果和最终上下文。第七步用测试集评估。不要只看单次演示效果。第八步设置追问机制。当问题缺少关键实体或版本时系统应该先追问。总结真实 RAG 系统面对的用户问题往往不是简单的一句话事实查询。用户会使用口语、省略上下文、混合多个条件也会提出需要跨文档和多步骤才能回答的问题。如果系统只是把原始问题直接送进向量检索就很容易漏召回、错召回或只回答问题的一部分。Query Rewrite 可以把用户表达改写成更适合检索的查询但必须保持原意不能随意扩写。Multi-query Retrieval 可以从多个角度召回资料适合多条件、多表达和模糊问题。问题拆分可以把复合问题拆成子问题适合对比、流程和排障场景。Multi-hop Retrieval 可以先查中间事实再基于中间事实继续查适合跨实体和跨文档问题。但复杂处理不是越多越好。系统应该先做问题分类再决定是否改写、拆分、多查询或多步检索。当问题缺少关键信息时追问比盲目检索更可靠。从工程角度看复杂问题处理的关键不是堆更多模型调用而是让问题理解、检索策略、Rerank、Prompt 和日志形成闭环。下一篇文章可以继续讨论 GraphRAG。因为当问题涉及全局关系、实体网络和跨文档结构时普通基于 Chunk 的 RAG 会遇到新的边界。