跳到主要内容

顶层全景:它大概怎么转

本章只讲「大盘」:有哪些部件、怎么接、一次提问的数据怎么流。具体机制在后面各章。

2.1 五类部件,各管一摊

DeepSearcher 的代码是「可插拔零件 + 装配中心」的结构。五类部件都有一个 base.py 抽象基类,下面挂着各家厂商的实现:

部件干什么抽象基类(在哪)
LLM拆子问题、反思、reranking、汇总——所有「思考」都靠它deepsearcher/llm/base.pyBaseLLM
Embedding把文本变成向量(查询和文档都要)deepsearcher/embedding/base.pyBaseEmbedding
VectorDB存向量、按相似度检索deepsearcher/vector_db/base.pyBaseVectorDB
Loader / Crawler把本地文件 / 网页读成文档deepsearcher/loader/.../base.py
Agent编排上面四个,跑迭代检索逻辑deepsearcher/agent/base.pyRAGAgent

关键设计:agent 不直接 new 任何厂商类——它只拿到 llm / embedding_model / vector_db 三个已经装配好的实例(见各 agent 的 __init__,如 deepsearcher/agent/deep_search.py:79-109)。换 LLM、换向量库,agent 代码一行不用动。

2.2 两条主线:离线灌库 vs. 在线提问

这是理解全局最重要的一刀。两条线在时间上完全分开,只通过「向量库」这个共享状态相连。

离线(load 阶段,一次性)
本地文件/网页
│ file_loader / web_crawler 读成 Document

split_docs_to_chunks 切块 + 句窗(见 ch.05)


embedding_model.embed_chunks 每块算向量


vector_db.insert_data ───────────▶ 【向量库】

在线(query 阶段,每次提问)
问题 ──▶ query() ──▶ default_searcher(RAGRouter)
│ 选一个 agent

DeepSearch / ChainOfRAG
│ 多轮检索(读【向量库】)+ 反思

汇总答案

离线入口:deepsearcher/offline_loading.pyload_from_local_files / load_from_website。 在线入口:deepsearcher/online_query.pyquery / retrieve / naive_rag_query

2.3 主线走一遍:一次 query() 发生了什么

query("Write a report about X") 为例,不进代码细节,只看流向:

  1. 取默认搜索器。 online_query.query 拿全局的 configuration.default_searcher,它是一个 RAGRouter(online_query.py:25-26)。

  2. 路由选 agent。 RAGRouter 把问题连同每个 agent 的 __description__ 发给 LLM,让 LLM 回一个编号,选中 DeepSearch 还是 ChainOfRAG(详见 ch.04)。

  3. 选中的 agent 跑迭代检索。 比如 DeepSearch:拆子问题 → 每个子问题在向量库里查 → LLM 逐块判断「这块有用吗」(rerank)→ 反思「还缺什么」→ 生成补查问题 → 下一轮(详见 ch.02)。

  4. 汇总成答案。 把所有命中的片段塞进 SUMMARY_PROMPT,让 LLM 写出最终报告(deep_search.py:301-308)。

  5. 返回三元组。 (答案字符串, 检索到的片段列表, 消耗的 token 数)——token 计数贯穿全程,是这个项目一个一致的副产物。

2.4 谁来装配这一切:init_config

上面的 default_searcher 不是凭空出现的。deepsearcher/configuration.py:186init_config装配中心:它用 ModuleFactory 按配置 new 出 llm / embedding / vector_db,然后把它们注入两个现成的 agent,包成一个 RAGRouter 存进全局变量 default_searcher(configuration.py:212-232)。装配细节见 ch.05。

为什么用全局变量? online_queryoffline_loadingimport deepsearcher.configuration 后取 configuration.xxx(注意是模块属性访问,不是 from-import),这样 init_config 里的 global 赋值能被它们看到。这是个简单但有效的「单例式装配」——代价是同进程只能有一套配置。