DeepSearcher 解读——探秘 DeepSearch 和 DeepResearcher 的魅力🤓🤓🤓

一、架构拆解

我们可以看到,DeepSearh 的主要流程是通过将用户的 Query 分解成多个子问题,然后通过查询向量数据库与搜索引擎获取知识后生成答案,通读 Zillize 给出的 DeepSearher 的 main.py,我们不难发现,其由一下几部分构成

1. 配置初始化

graph TD A[创建Configuration实例] --> B[设置LLM配置] A --> C[设置嵌入模型配置] A --> D[设置向量数据库配置] A --> E[设置文件加载器配置] A --> F[设置网络爬虫配置] B & C & D & E & F --> G[调用init_config] G --> H[初始化全局模块实例]

2. 本地文件加载

graph TD A[调用load_from_local_files] --> B[检查文件路径] B --> C[使用文件加载器处理文档] C --> D[文档处理/分块] D --> E[使用嵌入模型转换为向量] E --> F[存储到向量数据库]

3. Web 搜索

graph TD A[调用load_from_website] --> B[检查URL] B --> C[使用网络爬虫爬取内容] C --> D[处理爬取的内容] D --> E[使用嵌入模型转换为向量] E --> F[存储到向量数据库]

4. 查询流程

graph TD A[调用query函数] --> B[选择RAG代理] B --> C[处理查询] C --> D[嵌入查询为向量] D --> E[从向量数据库检索] E --> F[准备上下文] F --> G[调用LLM生成回答]

二、Query 流程拆解

在 DeepSearcher 中,主要的查询放在了 online_query.query() 中,

python 复制代码
def query(original_query: str, max_iter: int = 3) -> Tuple[str, List[RetrievalResult], int]:
    """
    Query the knowledge base with a question and get an answer.

    This function uses the default searcher to query the knowledge base and generate
    an answer based on the retrieved information.

    Args:
        original_query: The question or query to search for.
        max_iter: Maximum number of iterations for the search process.

    Returns:
        A tuple containing:
            - The generated answer as a string
            - A list of retrieval results that were used to generate the answer
            - The number of tokens consumed during the process
    """
    default_searcher = configuration.default_searcher
    return default_searcher.query(original_query, max_iter=max_iter)

其中,我们从全局的 configuration 实例中,获取了我们默认的 DeepResearh Agent,我们可以从 deepsearcher.configuration.init_config() 中看到相关的配置

python 复制代码
default_searcher = RAGRouter(
	llm=llm,
	rag_agents=[
		DeepSearch(
			llm=llm,
			embedding_model=embedding_model,
			vector_db=vector_db,
			max_iter=config.query_settings["max_iter"],
			route_collection=True,
			text_window_splitter=True,
		),
		ChainOfRAG(
			llm=llm,
			embedding_model=embedding_model,
			vector_db=vector_db,
			max_iter=config.query_settings["max_iter"],
			route_collection=True,
			text_window_splitter=True,
		),
	],
)

那么我们便可以看到,当前一共是有两类 agent,一个是 DeepSearch,一个是 ChainOfRAG,对于这两部分,我们可以在 deep searcher.agent.seep_search.pydeep searcher.agent.chain_of_rag.py 中找到。

因此我们可以整理出当一个请求来到我们的 DeepSearcher 的时候,我们的问题会经过 RAGRouter 选择合适的 Agent 调用对应的 query() 方法,我们将大致流程整理一下,并形成了下列流程图:

stateDiagram-v2 [*] --> 接收查询 接收查询 --> 查询路由 查询路由 --> DeepSearch: 适合撰写报告等一般查询 查询路由 --> ChainOfRAG: 适合多跳式事实性查询 state DeepSearch { [*] --> 生成子查询 生成子查询 --> 向量搜索 生成子查询 --> 搜索网页 向量搜索 --> 重排序筛选 重排序筛选 --> 合并查询 搜索网页 --> 合并查询 合并查询 --> 生成补充查询 生成补充查询 --> 生成子查询 生成补充查询 --> 总结答案: 无新查询或达到迭代上限 总结答案 --> [*] } state ChainOfRAG { [*] --> 分解子查询 分解子查询 --> 检索和回答 检索和回答 --> 筛选支持文档 检索和回答 --> 搜索相关网页 筛选支持文档 --> 合并搜索结果 搜索相关网页 --> 合并搜索结果 合并搜索结果 --> 检查信息充分性 检查信息充分性 --> 分解子查询: 信息不足 检查信息充分性 --> 生成最终答案: 信息充足或达到迭代上限 生成最终答案 --> [*] } DeepSearch --> 返回结果 ChainOfRAG --> 返回结果 返回结果 --> [*]

DeepSearch 流程解析

想象一下DeepSearch是一位穿着风衣、戴着放大镜的经济学侦探,擅长搜索事件的蛛丝马迹,而 query 方法正是它调研的全过程。让我们用轻松幽默的方式一探究竟!

🔍 侦探接案

python 复制代码
def query(self, query: str, **kwargs) -> Tuple[str, List[RetrievalResult], int]:

这就像福尔摩斯接到了一个神秘来电:"喂,是侦探社吗?我想知道 DeepSeek 是不是想吞并 OpenAI!"

侦探礼貌地记下问题,并默默思考:"这需要一些调查工作..."


🏃‍♂️ 奔赴"犯罪现场"

python 复制代码
all_retrieved_results, n_token_retrieval, additional_info = self.retrieve(query, **kwargs)

侦探立刻叫来华生(retrieve方法):"华生,我们得去各个知识库找线索!"

他们带着问题冲出门去,跑遍了所有可能藏有线索的数据库角落,最后气喘吁吁地带回一堆文件和票据(token消耗)。


🤷‍♂️ 线索全无的尴尬时刻

python 复制代码
if not all_retrieved_results or len(all_retrieved_results) == 0:
    return f"No relevant information found for query '{query}'.", [], n_token_retrieval

有时候,侦探会尴尬地发现一无所获:"呃...这个案子很特殊,华生。我们没找到任何线索。或许敌人太狡猾了,或者根本就没有这个想法?"


📚 整理证据材料

python 复制代码
all_sub_queries = additional_info["all_sub_queries"]
chunk_texts = []
for chunk in all_retrieved_results:
    if self.text_window_splitter and "wider_text" in chunk.metadata:
        chunk_texts.append(chunk.metadata["wider_text"])
    else:
        chunk_texts.append(chunk.text)

侦探把所有收集到的报纸剪报、证人证词和现场照片摊在桌子上:"看看我们收集了什么!这里有 DeepSeek 最近的动态,这里有 DeepSeek 的资金流动证明,还有这份 DeepSeek 科研的最新进展..."

他戴上老花镜,按照线索的重要性把它们排列整齐。


🧠 "华生,开始头脑风暴!"

python 复制代码
 log.color_print(
    f"<think> Summarize answer from all {len(all_retrieved_results)} retrieved chunks... </think>\n"
)
summary_prompt = SUMMARY_PROMPT.format(
    question=query,
    mini_questions=all_sub_queries,
    mini_chunk_str=self._format_chunk_texts(chunk_texts),
)

侦探在办公室踱来踱去,嘴里念叨着:"我们找到了{len(all_retrieved_results)}条线索...把这些拼起来应该能看出些什么..."

他在思维宫殿中开始构建案情,把分散的线索组成一个连贯的故事。


💡 福尔摩斯的结论时刻

python 复制代码
chat_response = self.llm.chat([{"role": "user", "content": summary_prompt}])
log.color_print("\n==== FINAL ANSWER====\n")
log.color_print(chat_response.content)

"啊哈!"侦探突然拍手,眼睛闪闪发光,"我知道了!一切都说得通了!"

华生一脸困惑:"什么说得通了?"

侦探开始绘声绘色地解释 DeepSeek的老谋深算,引经据典,逻辑严密,华生目瞪口呆!


📊 案件存档

python 复制代码
return (
    chat_response.content,
    all_retrieved_results,
    n_token_retrieval + chat_response.total_tokens,
)

侦探收拾好办公桌,把案件总结、所有证据和调查费用清单(token消耗)整齐地装进文件夹:

"华生,把这个归档,案子解决了!哦对了,别忘了给客户发账单,这个月的房租还没交呢!"

ChainOfRAG 流程解析

想象一下ChainOfRAG是一位戴着眼镜、永远不会长大的名侦探柯南,擅长根据线索进行推理,而query方法就是他那著名的"真相只有一个"的推理过程!

🎯 案件启动

python 复制代码
def query(self, query: str, **kwargs) -> Tuple[str, List[RetrievalResult], int]:

柯南接到了一个复杂的案件:"柯南,我想知道为什么恐龙会灭绝?"

小侦探推了推眼镜,眼中闪过一丝精光:"看来这个案件需要层层递进的调查..."


🔍 搜集线索(多轮询问)

python 复制代码
all_retrieved_results, n_token_retrieval, additional_info = self.retrieve(query, **kwargs)

柯南立刻行动起来:他没有像福尔摩斯那样直奔图书馆,而是分头询问了多位"证人"。

每问完一个问题,他都会根据得到的答案调整下一个问题,就像玩"二十个问题"游戏一样,一步步接近真相。

"请问,陨石撞击是在什么时候发生的?" "你能告诉我当时的气候变化情况吗?" "恐龙种群在灭绝前有什么变化?"


📝 整理调查笔记

python 复制代码
intermediate_context = additional_info["intermediate_context"]

柯南拿出他的小笔记本,翻看着刚才的一系列问题和答案:"先是问了陨石撞击,然后是气候变化,接着是种群状况..."

他把所有的中间线索组织起来,像拼图一样寻找它们之间的联系。


🧠 "事件真相浮出水面!"

python 复制代码
log.color_print(
    f"<think> Summarize answer from all {len(all_retrieved_results)} retrieved chunks... </think>\n"
)
chat_response = self.llm.chat(
    [
        {
            "role": "user",
            "content": FINAL_ANSWER_PROMPT.format(
                retrieved_documents=self._format_retrieved_results(all_retrieved_results),
                intermediate_context="\n".join(intermediate_context),
                query=query,
            ),
        }
    ]
)

柯南双手抱胸,闭上眼睛,沉思片刻。在他的"推理空间"里,所有的线索开始飞舞:

"我找到了{len(all_retrieved_results)}条相关证据... 中间经过了几轮提问... 证人们的反应都指向..."

突然,柯南睁开眼睛,脸上露出自信的微笑。


💡 "真相只有一个!"

python 复制代码
log.color_print("\n==== FINAL ANSWER====\n")
log.color_print(chat_response.content)

柯南微微一笑,指着前方(其实什么都没有),用他标志性的自信语气开始最终推理:

"其实啊,恐龙灭绝是多种因素共同导致的!首先是约6600万年前的希克苏鲁伯陨石撞击事件,造成了全球性的环境灾难。这一撞击释放的能量相当于10亿颗广岛原子弹..."

小兰、博士和少年侦探团都目瞪口呆地看着他。


📊 案件存档

python 复制代码
return (
    chat_response.content,
    all_retrieved_results,
    n_token_retrieval + chat_response.total_tokens,
)

柯南合上他的笔记本,把最终报告、所有证据和调查费用(令人震惊的token数量)交给了委托人:

"案件解决了!不过别忘了,侦探可是很贵的哦!"然后他做了个胜利的手势,"下一个谜题在哪里呢?"


为什么有了 DeepSearch 了还需要 ChainOfRAG?

与DeepSearch不同,ChainOfRAG更像是一个善于提问的侦探,他知道一个好问题胜过十个半拉子答案。他不会一次性收集所有可能的线索,而是通过不断提出新的精准问题,一步步接近真相的核心。

当福尔摩斯还在疯狂收集线索时,柯南已经通过几个关键问题找到了破案的捷径!👦🔍🕶️

下面我们用一张图来总结一下当用户的 query 到达了 DeepSearcher 之后会发生什么:

sequenceDiagram actor User participant API as FastAPI participant Online as online_query participant Router as RAGRouter participant DS as DeepSearch participant CoR as ChainOfRAG participant VDB as VectorDB participant WS as WebSearch participant Web participant Embed as Embedding participant LLM as LLM User->>API: 提交查询(original_query, max_iter) API->>Online: query(original_query, max_iter) Online->>Router: query(original_query, max_iter) Router->>LLM: 确定合适的代理类型 LLM-->>Router: 选择代理结果 alt 选择DeepSearch处理 Router->>DS: query(original_query, max_iter) activate DS DS->>LLM: _generate_sub_queries(原始查询) LLM-->>DS: 子查询列表 loop 最多max_iter次迭代 DS->>DS: async_retrieve进行检索 DS->>Embed: search_res_from_vectordb DS->>WS: search_res_from_internet Embed-->>DS: 查询向量 loop 对每个选定的集合 DS->>VDB: search_data(collection, vector) DS->>Web: search_data(url) VDB-->>DS: 检索结果 Web-->>DS: 搜索结果 loop 对每个检索结果 DS->>LLM: 重排序(RERANK_PROMPT) DS->>LLM: 搜索子页面 LLM-->>DS: 是否保留文档 LLM-->>DS: 是否获取子文档 end end DS->>LLM: _generate_gap_queries(分析缺口) LLM-->>DS: 补充查询 alt 没有新的补充查询 DS->>DS: 结束迭代 end end DS->>LLM: chat(SUMMARY_PROMPT) LLM-->>DS: 生成最终答案 DS-->>Router: 答案, 检索结果, tokens deactivate DS else 选择ChainOfRAG处理 Router->>CoR: query(original_query, max_iter) activate CoR loop 最多max_iter次迭代 CoR->>LLM: _reflect_get_subquery(分解查询) LLM-->>CoR: 后续查询 CoR->>Embed: embed_query(后续查询) CoR->>WebSearch: web_search(后序查询) Embed-->>CoR: 查询向量 WebSearch-->>CoR: 查询结果 CoR->>VDB: search_data(查询向量) CoR->>Web: search_web(查询结果) VDB-->>CoR: 检索结果 Web-->>CoR: 搜索结果 CoR->>LLM: 生成中间答案 LLM-->>CoR: 中间答案 CoR->>LLM: _get_supported_docs(筛选文档) LLM-->>CoR: 支持当前答案的文档 CoR->>LLM: _check_has_enough_info(检查信息充分性) LLM-->>CoR: 是否有足够信息 alt 信息已充分 CoR->>CoR: 结束迭代 end end CoR->>LLM: 生成最终答案 LLM-->>CoR: 最终答案 CoR-->>Router: 答案, 检索结果, tokens deactivate CoR end Router-->>Online: 答案, 检索结果, tokens Online-->>API: 答案, 检索结果, tokens API-->>User: 返回结果和token消耗

三、收官之战:什么是 DeepSearch?什么是 DeepResearch?

  • DeepSearch 是一种高级的网页搜索代理系统。它通过不断循环执行"搜索 → 阅读 → 推理"的过程,直到找到最优答案或达到预设的停止条件,如令牌使用限制或失败尝试次数。在搜索阶段,DeepSearch利用搜索引擎广泛获取互联网信息;阅读阶段深入分析特定网页内容;推理阶段评估当前状态,决定是否拆解原始问题为更小的子问题,或尝试其他搜索策略。

  • DeepResearch 是在 DeepSearch 基础上构建的一个典型应用,专注于自动生成结构化、逻辑清晰的长篇研究报告。其工作流程包括:首先根据研究主题生成报告的详细目录;然后针对每个章节,调用 DeepSearch 进行深入的信息搜索和内容生成;最后,将所有章节内容整合,确保报告的连贯性和深度。

简单来讲,DeepSearch 是一个高级的AI搜索方法,强调通过多次搜索和推理获取高质量答案;而 DeepResearch 则是基于 DeepSearch 的应用,旨在自动生成深入的研究报告等内容。也就是我们今天探究的这个应用便是 DeepResearch 的体现,而其背后的原理即为 DeepSearch。

相关推荐
OpenTiny社区1 小时前
强烈推荐|新手从搭建到二开TinyEngine低代码引擎
前端·低代码·开源
Aaaaaaaaaaayou2 小时前
浅玩一下,基于 Appium 的自动化测试 AI Agent
llm·测试
FIT2CLOUD飞致云3 小时前
1Panel MCP Server发布,开启AI对话式运维新时代!
运维·开源
suke3 小时前
Qwen2.5-Omni 全能旗舰 VS 国产小钢炮 MiniCPM-V:参数、硬件、资源、优势全解析
人工智能·程序员·开源
蚝油菜花6 小时前
Math24o:SuperCLUE开源的高中奥数推理测评基准,85.71分屠榜
人工智能·开源
SecPulse7 小时前
流影---开源网络流量分析平台(四)(分析引擎部署)
运维·服务器·人工智能·网络安全·开源·流影
何贤8 小时前
🤡🤡国内开源作者现状🤡🤡 (愚人节特辑🤡 )
程序员·开源·如何当个好爸爸
不剪发的Tony老师8 小时前
JumpServer:一款企业级开源堡垒机
运维·开源
爱听歌的周童鞋8 小时前
理解llama.cpp如何进行LLM推理
llm·llama·llama.cpp·inference
特创数字科技8 小时前
深度求索:开源革命下的AI普惠之路
人工智能·开源