跳到主要内容

ChainOfRAG:逐跳追事实

DeepSearcher 的第二种迭代策略,灵感来自论文 Chain-of-Retrieval Augmented Generation(chain_of_rag.py:84 注明 https://arxiv.org/pdf/2501.14342)。它的 __description__:"decompose complex queries and gradually find the fact information of sub-queries ... suitable for ... concrete factual queries and multi-hop questions"(chain_of_rag.py:73-76)。

4.1 它和 DeepSearch 的根本差别

两者都「迭代」,但迭代的形状不同:

DeepSearchChainOfRAG
每轮问几个问题多个(子问题并行)一个 follow-up
每轮要不要先答不答,只攒片段即答(intermediate answer)
下一轮靠什么反思「还缺什么」上一跳的问答喂给下一跳
适合宽问题、写报告多跳事实(A→B→C 链式推理)
形状广度(发散再收)深度(一条链往下追)

直觉:DeepSearch 像「列提纲、各角度铺开查」;ChainOfRAG 像「顺藤摸瓜——查到 A 才知道下一步该问 B」。多跳问题(「X 的作者出生在哪个国家?」需要先查作者、再查国籍)是 ChainOfRAG 的主场。

4.2 一轮迭代里发生什么

主问题 + 历史问答


① 生成一个简单 follow-up 问题(FOLLOWUP_QUERY_PROMPT)


② 检索 + 立刻生成「中间答案」(INTERMEDIATE_ANSWER_PROMPT)
│ └─ 强约束:只用文档、不许编;没有就说 "No relevant information found"

③ 让 LLM 挑出「真正支撑这个答案」的文档(GET_SUPPORTED_DOCS_PROMPT)


④ 把「中间问+中间答」记进 history,供下一跳参考


(可选)⑤ early_stopping:问 LLM「信息够了吗?」够就停(REFLECTION_PROMPT)

源码主循环在 retrieve(chain_of_rag.py:244-275)。

4.3 三个值得看的细节

① 即答即用(intermediate answer)。 _retrieve_and_answer(chain_of_rag.py:136-170)检索完立刻让 LLM 基于这批文档给一个简短答案,prompt 里硬性要求不许幻觉:

DO NOT hallucinate any information, only use the provided documents ... Respond "No relevant information found" if the documents do not contain useful information.

这个中间答案有两个用途:喂给下一跳的 follow-up 生成,以及最后汇总。

② 「支撑文档」过滤。 _get_supported_docs(chain_of_rag.py:172-200)是个精巧步骤:让 LLM 从检索结果里挑出真正支撑中间答案的文档索引,只保留这些进入最终结果池。这样最终汇总看到的是「被验证过相关」的文档,而非全部召回。注意它先判断中间答案是否含 "No relevant information found",若是则跳过这步(省一次调用):

if "No relevant information found" not in intermediate_answer:
# ...让 LLM 返回支撑文档的索引列表
supported_doc_indices = self.llm.literal_eval(chat_response.content)
supported_retrieved_results = [
retrieved_results[int(i)]
for i in supported_doc_indices
if int(i) < len(retrieved_results) # 越界保护
]

那个 if int(i) < len(...) 是防 LLM 编出不存在的索引——很实在的防御。

③ 可选的 early stopping。 默认 early_stopping=False(chain_of_rag.py:94),即跑满 max_iter(默认 4)。开启后每轮末尾用 REFLECTION_PROMPT 问 LLM「Yes/No 够了吗」,判定逻辑(chain_of_rag.py:219):

has_enough_info = self.llm.remove_think(chat_response.content).strip().lower() == "yes"

为什么默认关掉? 代码没明说(inferred),但合理推测是:多跳问题里「中途觉得够了」常常是错觉,跑满轮数更稳。

4.4 汇总:把整条链交给 LLM

query(chain_of_rag.py:277-316)用 FINAL_ANSWER_PROMPT 汇总,同时喂入「支撑文档」和「全部中间问答」。prompt 里有一句诚实的提醒(chain_of_rag.py:33):"intermediate answers are generated by an LLM and may not always be accurate"——让最终汇总对中间结论保持警惕,而不是照单全收。