跳转到内容

树状查询分解与深度研究:递归多步检索

树状查询分解与深度研究:递归多步检索

学习目标

读完本章后,你将能够:

  • 理解树状查询分解相比单步检索的优势
  • 设计信息充分性检查与子查询生成策略
  • 实现三源融合检索(知识库 + Web + 知识图谱)
  • 在自己的 RAG 系统中引入 Deep Research 模式

前置知识


1. 为什么需要树状查询分解

标准 RAG 管线对每个用户查询执行一次检索然后生成回答。但对于复杂查询,一次检索往往不够:

用户查询:"对比 GPT-4 和 Claude 3 在代码生成、数学推理、多模态三个方面的能力差异"
单步检索问题:
- 向量数据库中没有一篇文章同时覆盖这三个方面的对比
- 需要分别检索:GPT-4 代码生成、GPT-4 数学推理、GPT-4 多模态、
Claude 3 代码生成、Claude 3 数学推理、Claude 3 多模态

树状查询分解的核心思想:

  1. 将复杂查询分解为多个子查询(树的分支)
  2. 对每个子查询递归执行检索
  3. 合并所有子查询的结果,生成综合回答

2. 核心流程

2.1 信息充分性检查

在执行检索后,用 LLM 判断当前获取的信息是否足以回答用户问题:

Prompt 示例:
问题: {question}
已检索到的信息: {retrieved_chunks}
请判断:上述信息是否足以完整回答问题?
- 如果足够,返回 {"is_sufficient": true}
- 如果不足,返回 {"is_sufficient": false, "missing_information": ["缺失信息1", "缺失信息2"]}

关键设计

  • 信息充分性检查是动态的——如果一次检索的信息已足够,就提前终止,不再递归
  • 这避免了无意义的深层检索,节省 API 成本和延迟

2.2 子查询生成

当信息不足时,LLM 根据缺失信息规划新的搜索查询:

Prompt 示例:
问题: {question}
已有信息: {retrieved_chunks}
缺失信息: {missing_information}
请生成最多 3 个搜索查询,以获取上述缺失信息。
每个查询应聚焦一个具体方面,避免过于宽泛。
返回: [{"question": "...", "query": "..."}, ...]

关键设计

  • 子查询的 question 是人类可读的自然语言问题(用于日志和回调)
  • 子查询的 query 是优化后的搜索关键词(更适合搜索引擎)

2.3 递归深度控制

树状查询需要设置最大深度,防止无限递归:

research(chunk_info, question, query, depth=3):
if depth == 0:
return # 达到最大深度,终止
results = retrieve(query)
if is_sufficient(results, question):
return # 信息足够,提前终止
sub_queries = generate_sub_queries(question, results)
for sq in sub_queries:
research(chunk_info, sq.question, sq.query, depth-1) # 递归

深度选择建议

  • depth=1:单次检索 + 一次补充(适合中等复杂度查询)
  • depth=2:最多 3 层(适合大部分场景)
  • depth=3+:深度研究模式(API 成本高,适合关键场景)

3. 三源融合检索

树状查询的每个节点可以从多个数据源获取信息:

数据源优势局限
知识库高质量、可控、可追溯覆盖范围有限
Web 检索覆盖广、实时更新质量参差不齐、需要 API 费用
知识图谱结构化、关系清晰需要先构建图谱、仅覆盖已有实体

融合策略

  1. 知识库优先:优先从知识库检索(质量最高、成本最低)
  2. Web 补充:知识库不足时通过 Web 搜索补充(如 Tavily、SearXNG)
  3. 知识图谱增强:如果查询涉及实体关系,优先从图谱获取结构化信息

去重与合并

  • chunk_id 去重(同一文档的相同内容不重复添加)
  • doc_id 聚合来源文档统计
  • 总结果数为各源结果的并集

4. 异步并发优化

子查询之间相互独立,可以并行执行:

# 伪代码
async def research(chunk_info, question, query, depth=3):
if depth == 0:
return
results = await retrieve_information(query)
update_chunk_info(chunk_info, results)
if is_sufficient(chunk_info, question):
return
sub_queries = await generate_sub_queries(question, query, results)
# 并行执行所有子查询
tasks = [
asyncio.create_task(research(chunk_info, sq.question, sq.query, depth-1))
for sq in sub_queries["questions"]
]
await asyncio.gather(*tasks, return_exceptions=True)

优化效果

  • 如果某层生成 3 个子查询,串行需要 3 次 API 调用时间,并行只需 1 次(最慢的那个)
  • 使用 return_exceptions=True 确保单个子查询失败不影响其他分支

5. 问题与规避

陷阱对策
递归爆炸(子查询指数增长)限制最大深度 + 每层最多 3 个子查询
API 成本过高信息充分性检查提前终止 + 设置总查询数上限
多源结果冲突按数据源可信度加权,知识库 > 知识图谱 > Web
子查询偏离原问题子查询生成 prompt 中要求”紧密围绕原问题”
结果合并后上下文超长按 Token 预算截断,优先保留高相关度 chunk

6. 设计取舍

树状分解 vs 线性多步

维度树状分解线性多步(ReAct)
并行度高(子查询可并行)低(每步依赖上一步)
总延迟低(并行执行)高(串行执行)
复杂度中(需要管理树结构)低(标准循环)
适用场景复杂多主题查询单主题多步骤推理

推荐:当查询涉及多个独立子主题时,使用树状分解(如”对比 A 和 B 的 X、Y、Z 三方面”);当查询需要逐步推理时,使用线性多步。

信息充分性检查 vs 固定深度

维度信息充分性检查固定深度
效率高(信息足够时提前终止)低(总是执行到最大深度)
成本低(减少不必要检索)
可靠性依赖 LLM 判断质量确定性高
适用场景通用场景成本敏感场景

推荐:默认使用信息充分性检查。如果对成本控制要求极高,可以用固定深度替代。


参考来源