数据截至 (上游 commit d89e37cdfbbe)
DeepSearch:拆子问题 + 反思补查的核心循环
这是整个项目工程含量最高、最值得读的一支。DeepSearch 适合「给个主题写报告/综述」这类宽问题(它的 __description__ 原话:"suitable for handling general and simple queries, such as given a topic and then writing a report",deep_search.py:68-70)。
3.1 它要解决的小问题
宽问题(「写一份关于 X 的报告」)一次检索 覆盖不全。两个子难点:
- 覆盖面: 得从多个角度查,而不是只查原问题。
- 何时停: 查到什么程度算「够了」?
DeepSearch 的回答:拆角度(子问题) 解决覆盖面,反思(reflection)生成补查问题 解决「还缺什么」,最大轮数 兜底解决「何时停」。
3.2 思路/直觉(先于代码)
整个 async_retrieve 是一个循环。怎么读下面这张图: 从上到下是一轮迭代内的步骤,右侧虚线是「下一轮」的回流。
原问题
│
▼
① 拆子问题(SUB_QUERY_PROMPT)──▶ [子问题 q1, q2, q3, q4]
│
▼
┌─────────── 每一轮迭代(最多 max_iter 轮)───────────┐
│ ② 对每个子问题并行检索向量库 │
│ ③ 每块命中让 LLM 判 YES/NO(rerank,只留 YES) │
│ ④ 跨子问题去重,累加进总结果 │
│ ⑤ 反思:看「原问题+已查到的」还缺什么(REFLECT_PROMPT)│
│ └─ 返回空列表 → 停;否则 gap query 成为下一轮子问题 ┄┄┘
└──────────────────────────────────────────────────────┘
│
▼
⑥ 汇总所有命中片段(SUMMARY_PROMPT)──▶ 最终答案
3.3 原理演示(示意,非源码)
下面这段把核心循环的骨架演出来,帮你建立直觉:
# 示意,非源码:DeepSearch 迭代检索的骨架
sub_queries = llm_decompose(original_query) # ① 拆角度
all_hits, all_subs = [], list(sub_queries)
next_round = sub_queries
for it in range(max_iter):
# ② 每个子问题并行查库,③ LLM 逐块 rerank
hits = parallel_search_and_rerank(next_round)
all_hits = dedup(all_hits + hits) # ④ 去重累加
if it == max_iter - 1:
break # 轮数兜底
# ⑤ 反思:还缺什么?
gap = llm_reflect(original_query, all_subs, all_hits)
if not gap:
break # 觉得够了,停
next_round = gap # 缺口问题进入下一轮
all_subs += gap
answer = llm_summarize(original_query, all_subs, all_hits) # ⑥
重点看两个「停」条件:反思返回空列表 或 轮数耗尽。