五、多意图查询拆分与递归分解:让智能客服准确理解复杂问题
用户问"如何创建工单并设置审批流程?"——这是一个包含两个意图的复合查询。直接用单个检索去匹配知识库,很可能只命中其中一部分,另一半被忽略。本文深入解析多意图检测、查询拆分、答案合成以及递归分解这四条关键链路,看它们如何协同工作以提升复杂问题的回答质量。
一、为什么需要查询分解?
在智能客服问答系统中,用户问题往往不是单一维度的。实际运营中我们发现:
- 复合操作查询:用户在同一句话中询问多个操作步骤,如"如何创建工单并设置审批流程?"
- 对比类查询:隐含多个实体间的比较,如"标准版和专业版的系统文档功能有什么区别?"
- 条件嵌套查询:一个意图的执行前置条件是另一个意图的结果,如"我想开通供应商账号,需要先完成什么实名认证?"
传统的 RAG 流程面对这些问题时,通常只做一次检索,然后期望 LLM 自己从检索结果里找出所有答案。这存在两个致命缺陷:
- 检索召回偏移:向量检索或 BM25 检索倾向于匹配问题的"主体语义",第二个意图往往被第一个意图淹没,召回结果偏向一侧。
- 信息碎片化:即使 LLM 有能力回答,但检索结果本身就不包含所有需要的信息——LLM 的推理能力也无从发挥。
因此,我们在 Agentic RAG 系统中引入了查询分解(Query Decomposition) 子系统,核心思想是:将复杂查询拆分为多个简单子查询,分别检索,再将结果合成为一个完整答案。
二、系统架构概览
查询分解子系统在 Agent 主流程中位于Phase 1.5(初始检索质量评估之后)和Phase 1.7(最终回答生成之前)。其整体流程如下:
用户提问
│
▼
Phase 1.0-1.4: 初始检索 + 记忆 + 路由
│
▼
Phase 1.5: 检测是否需要分解
├─ 不需要 → 直接走 Phase 1.6 执行所需工具
└─ 需要(多意图)→ 调用 decompose()
│
▼
Phase 1.7: 递归评估完整性
├─ 已完整 → 进入 Phase 2 生成最终回答
└─ 不完整 → 生成追问 → 再次检索 → 递归合成
整个系统由三个核心模块组成,均在 decomposer.py 中实现:
| 模块 | 功能 | 核心方法 |
|---|---|---|
| 多意图检测 | LLM 判断是否需要拆分并生成子问题 | detect_multi_intent() |
| 答案合成 | 将多个子问题的检索结果合并 | synthesize_answers() |
| 递归分解 | 基于完整性评估持续追问和补充 | recursive_decompose() |
三、多意图检测:LLM 驱动的第一道关卡
3.1 检测逻辑
detect_multi_intent() 是分解流程的入口。它的工作方式非常直接:将用户问题交给 LLM,让 LLM 判断该问题是否需要拆分为多个子查询。
def detect_multi_intent(user_query: str, ...) -> tuple[bool, list[str]]:
"""
返回:
- needs_decomposition: 是否需要拆分
- sub_questions: 拆分后的子问题列表
流程:
1. 构造 prompt 给 LLM
2. LLM 判断 needs_decomposition
3. 如果需要,LLM 同时生成 sub_questions
4. 返回 (True, ["子问题1", "子问题2", ...])
或 (False, [])
"""
LLM 接收的 prompt 包含具体指导:
- 当一个问题包含多个独立的子任务或多个不同的查询主体时,需要拆分
- 每个子问题应当是一个独立的、可自包含的检索问题
- 不鼓励过度拆分——单一意图的问题应当直接返回 False
3.2 触发条件
并不是每个问题都需要经过分解检测。系统设置了阈值控制:只有当初始检索的综合质量评分低于 QUERY_DECOMPOSITION_THRESHOLD 时,才会触发多意图检测。这避免了不必要的 LLM 调用开销。
配置参数:
# 是否启用查询分解
QUERY_DECOMPOSITION_THRESHOLD = 0.6 # 检索质量低于此阈值时触发
QUERY_DECOMPOSITION_MAX_SUBS = 3 # 最多拆分为3个子问题
3.3 实际案例
用户问题:“如何创建工单,以及如果工单被驳回了怎么重新提交?”
LLM 检测结果:
{
"needs_decomposition": true,
"sub_questions": [
"如何创建工单",
"工单被驳回后如何重新提交"
]
}
这两个子问题分别指向不同的知识库文档区域——前者是"工单创建"操作手册,后者是"工单撤销与驳回处理"流程。如果合在一起检索,第二个意图很可能因为语义权重偏低而被忽略。
四、答案合成:将碎片化为整体
当多个子问题的检索全部完成后,synthesize_answers() 负责将分散的结果合并为一个协调一致的最终答案。
4.1 合成流程
sub_questions = ["子问题1", "子问题2", "子问题3"]
│
▼
对每个子问题分别执行 retrieve()
│
▼
将检索结果格式化 → [{子问题, 检索结果}, ...]
│
▼
构造合成 prompt → LLM 生成综合回答
│
▼
合并来源信息(citation tracking)
4.2 Prompt 设计
合成阶段使用两段式 Prompt:
- SYNTHESIZE_SYSTEM:定义 LLM 的角色——“你是一个知识库问答系统的答案合成助手”,以及合成的核心原则:必须基于所有子问题的检索结果,不能自行添加知识库中没有的信息。
- SYNTHESIZE_PROMPT:包含用户原始问题、所有子问题及其对应的检索结果片段。LLM 需要从中提炼出完整答案,并标注每部分信息的来源。
4.3 来源合并
一个容易被忽视但极其重要的细节是来源信息的合并。每个子问题的检索结果都包含各自的文档来源元数据(文档名、章节、行号等)。合成阶段需要:
- 保留每个子结果的独立来源信息
- 在最终答案中清晰标注每段回答所引用的原始文档
- 避免来源冲突:同一段信息出现在多个子结果中时,合并去重
五、递归分解:持续追问直到答案完整
多意图拆分解决的是"水平拆分"问题——把并行的多个意图拆开分别检索。但还有一种更复杂的场景:一次检索的结果本身就不完整,需要基于已有答案生成追问,进行第二次、第三次检索。
这就是 recursive_decompose() 的职责。
5.1 为什么需要递归?
在实际运营中我们发现,某些问题存在信息依赖链:
用户问:“供应商如何修改已经提交的报价单?”
第一次检索可能只找到了"报价单提交"和"报价单修改"的基本文档,但 LLM 在阅读后发现——“修改已提交的报价单"其实需要先"撤销提交”,再"修改草稿",最后"重新提交"。第一次检索的结果中缺少"撤销提交"的详细信息,于是需要针对这个缺失部分生成追问,进行第二轮检索。
5.2 递归流程
recursive_decompose(query, initial_answer):
depth = 0
while depth < RECURSIVE_MAX_DEPTH:
# 第一步:评估当前答案的完整性
evaluation = llm_call(RECURSIVE_EVAL_SYSTEM,
current_answer)
if evaluation.quality >= RECURSIVE_MIN_QUALITY:
break # 达到质量标准,结束递归
# 第二步:生成追问
followup = llm_call(RECURSIVE_FOLLOWUP_SYSTEM,
query, current_answer, evaluation)
# 第三步:检索追问
new_results = retrieve(followup)
# 第四步:合成新答案
current_answer = llm_call(RECURSIVE_SYNTHESIZE_SYSTEM,
previous_answer, new_results)
depth += 1
return final_answer
5.3 三个专门的系统 Prompt
递归分解需要三个独立的 LLM 调用,每个有专门的 System Prompt:
| Prompt | 职责 | 核心逻辑 |
|---|---|---|
RECURSIVE_EVAL_SYSTEM |
评估当前答案完整性 | 从信息覆盖度、细节深度、准确性三个维度打分,输出质量评分和缺失信息列表 |
RECURSIVE_FOLLOWUP_SYSTEM |
生成追问 | 基于缺失信息列表,生成一个可直接用于检索的追问问题 |
RECURSIVE_SYNTHESIZE_SYSTEM |
增量合成 | 将已有的答案与新检索结果融合,避免重复和矛盾 |
5.4 安全边界
递归分解不能无限进行——必须有严格的安全控制:
RECURSIVE_MAX_DEPTH = 3 # 最多递归3层
RECURSIVE_MIN_QUALITY = 0.8 # 质量评分达到0.8即停止
RECURSIVE_MAX_LLM_CALLS = 10 # 本轮总LLM调用数上限
这三个参数构成了三重保护:
- 深度保护:最多递归 3 层,防止深度遍历导致 Token 耗尽
- 质量保护:达到 0.8 分即提前退出,避免不必要的追问
- 总量保护:整个递归过程中 LLM 调用次数不超过 10 次,作为兜底
六、与 Agentic RAG 的集成
查询分解不是孤立的功能,而是深度嵌入 Agent 主流程的特定阶段。以下是在 agent.py 中的集成点:
6.1 集成位置
Phase 1: 检索与准备阶段
├─ Phase 1.0: 意图识别(多意图路由)
├─ Phase 1.1: 初始检索(五种检索策略)
├─ Phase 1.2: 记忆层检查(L1/L2/L3)
├─ Phase 1.3: Cross-encoder 精排
├─ Phase 1.4: 答案基础评估
├─ Phase 1.5: Detect Multi-Intent ← (多意图检测入口)
├─ Phase 1.6: 执行工具调用(含 decompose 检索)
└─ Phase 1.7: Recursive Decompose ← (递归分解入口)
Phase 2: 最终回答生成
6.2 策略控制
通过 RETRIEVE_STRATEGY 配置可以控制分解行为:
RETRIEVE_STRATEGY = "decompose" # 仅多意图拆分
RETRIEVE_STRATEGY = "recursive" # 仅递归分解
RETRIEVE_STRATEGY = "combined" # 先拆分后递归(推荐)
默认推荐 “combined”:先做多意图水平拆分,每个子问题单独检索;如果合成后的答案质量仍不足,再触发递归分解进行垂直追问。
6.3 与检索系统的交互
分解子系统与上层检索系统的交互通过标准接口完成:
# decompose 模式
sub_results = []
for q in sub_questions:
result = retrievers.retrieve(q) # 调用完整检索管线
sub_results.append(result)
# recursive 模式
current_result = initial_result
while need_deeper:
followup = generate_followup(current_result)
new_result = retrievers.retrieve(followup)
current_result = synthesize(current_result, new_result)
每一次子检索都走完整的检索管线(Navigator → Matcher → Fusion → Cross-encoder),而不是简单的关键词匹配。这保证了每个子问题都能获得最高质量的检索结果。
七、配置体系全览
查询分解相关的全部配置项集中在一个配置块中:
| 配置项 | 默认值 | 说明 |
|---|---|---|
RETRIEVE_STRATEGY |
"combined" |
分解策略:decompose / recursive / combined |
QUERY_DECOMPOSITION_THRESHOLD |
0.6 |
检索质量低于此值触发多意图检测 |
QUERY_DECOMPOSITION_MAX_SUBS |
3 |
最大子问题数 |
RECURSIVE_MAX_DEPTH |
3 |
递归分解最大深度 |
RECURSIVE_MIN_QUALITY |
0.8 |
递归分解目标质量 |
RECURSIVE_MAX_LLM_CALLS |
10 |
递归过程中 LLM 调用总量上限 |
调优建议
- 知识库密度高的场景(如系统C有 2000+ 操作文档):建议将
QUERY_DECOMPOSITION_THRESHOLD降至 0.5,因为更多文档意味着更大概率需要拆分查询才能精准命中 - 用户问题普遍简短的单产品线场景:可以考虑关闭 decompose(
"recursive"模式),只保留递归分解来处理查询中的隐含信息链 - Token 预算敏感场景:调低
RECURSIVE_MAX_DEPTH和RECURSIVE_MAX_LLM_CALLS,但建议至少保留 2 层递归深度 - 高精度场景:提升
RECURSIVE_MIN_QUALITY至 0.9,配合RECURSIVE_MAX_DEPTH=3确保深度覆盖
八、实践要点与常见问题
8.1 子问题之间的信息重叠
多个子问题的检索结果可能存在内容重叠(例如"创建工单"和"工单字段说明"都可能命中工单创建文档)。合成阶段通过去重和优先保留更详细来源的策略处理这一问题。
8.2 递归终止的边界条件
递归分解最常见的陷阱是无限"追问-检索-追问"循环——LLM 可能因为追求完美而持续生成追问,即使答案已经足够好。RECURSIVE_MIN_QUALITY 和 RECURSIVE_MAX_DEPTH 的双重限制正是为此设计。
8.3 对 Token 消耗的影响
需要坦诚的是,查询分解会显著增加 Token 消耗:
- 多意图检测:1 次 LLM 调用(少量的输入 + 输出 Token)
- 多意图拆分后:N 个子问题 × 各一次完整检索(但不会增加 LLM 调用)
- 答案合成:1 次 LLM 调用
- 递归分解:每轮递归 3 次 LLM 调用(评估 + 追问 + 合成)
以"combined"模式、2 个子问题、2 轮递归为例,大约额外增加:
- 5 次 LLM 调用(1 检测 + 1 合成 + 3 × 2 递归)
- 对应的输入输出 Token
这个开销是否值得?数据表明,在某个 FAQ 场景中,查询分解使综合回答准确率提升了约 22%,而 Token 消耗增加约 35%——在准确率优先的生产环境中,这个权衡是可以接受的。
九、总结
查询分解子系统为 Agentic RAG 系统提供了"把复杂问题拆碎、分别消化、再整合"的能力:
- 多意图检测(
detect_multi_intent)充当哨兵——只有当初始检索质量不足时才触发,避免无谓消耗 - 水平拆分(
decompose→synthesize_answers)解决"多个并列意图"问题——每个子问题独立检索,再通过 LLM 合成 - 垂直挖掘(
recursive_decompose)解决"信息链依赖"问题——基于已有答案评估完整性,生成追问进行深层检索 - 三重安全边界(深度、质量、调用次数)防止递归失控
这套设计将"一次检索搞定一切"的简单 RAG 模式,升级为"检测 → 拆分 → 检索 → 评估 → 追问 → 再检索 → 合成"的迭代式智能检索流程,有效应对了某个 FAQ 中大量存在的复合查询和信息依赖场景。
在下一篇文章中,我们将分析系统的多阶段评估与答案质量监控,看如何从多个维度量化回答质量,并在质量不达标时触发降级策略。
更多推荐


所有评论(0)