跳到主要内容

Local Deep Research — 架构与原理

30 秒导读: Local Deep Research(下称 LDR)是一个你自己掌控、可全本地运行的"深度研究助手"。你给一个问题,它像 ChatGPT Deep Research 那样自动上网多轮检索 + 读全文 + 交叉核对 + 写一篇带编号引用的报告;区别是模型、搜索、数据全在你机器上,查询不必发给外部服务器。技术核心是一个可插拔的"研究策略"编排固定的一条流水线:问题生成 → 多引擎并行的两阶段检索 → 跨引擎相关性过滤 → 带编号引用的 LLM 合成 → 报告,底下垫着每用户 SQLCipher 加密库和一份冻结的"设置快照"。默认策略 langgraph-agent 在单张 RTX 3090 + 本地 Qwen3.6-27B 上报出过 ~95% SimpleQA(依据:README.md:36)。


1. 这是什么(零基础也能懂)

一句话定义: LDR 是一个开源的、本地优先的 AI 研究代理——输入一个问题,输出一篇有来源、有编号引用的研究报告。

解决什么问题 / 给谁用。 假设你是记者、研究员,或在公司里查一个敏感课题:你想要"深度研究"级别的自动检索与综述,但不想把问题和资料发到某个云端 API。LDR 让你用本地的 Ollama / LM Studio 模型 + 自建或私有的搜索源,把这件事整个跑在自己机器上(依据:README.md:46README.md:520)。

它能做什么(功能)。

  • 对一个问题做多轮自动检索,而不是搜一次就答。
  • 很多种搜索源:通用网页(SearXNG / Brave / DuckDuckGo)、学术(arXiv / PubMed / Semantic Scholar)、代码、书籍,以及你自己的本地文档库
  • 抓网页全文、跨来源做相关性过滤,再让 LLM 写[1] [2] 编号引用的答案。
  • 两种产出档位:快速摘要(quick)分章节的详细报告(detailed)
  • 全部落进每用户加密数据库;还能订阅"新闻"式的周期性研究。

用起来什么样(最小真实示例)。 除了 Web 界面,LDR 也有 Python 编程接口:

# 示意,非源码;真实签名见 api/research_functions.py:166
from local_deep_research.api import quick_summary

result = quick_summary(
"2024 年有哪些开源本地 LLM 适合做 agentic search?",
search_tool="searxng", # 用哪个搜索引擎
iterations=2, # 跑几轮检索
)
print(result["summary"]) # 带 [1][2] 编号引用的摘要
print(result["sources"]) # 对应的来源清单

一句话直觉/类比。 把 LDR 想成一条工厂流水线:一个"策略"是工头,决定"这一轮搜什么、要不要再挖深、什么时候收工写报告";流水线上的工位固定——生成问题、并行检索、过滤、合成引用。换工头(策略)不用改流水线;换搜索源(引擎)也不用改工头。


2. 顶层全景(它大概怎么转)

先看一次研究是怎么流动的。 从左到右是数据流,箭头是"交给下一站";虚线框是可替换的插件点。

用户问题


┌───────────────────────────────────────────────────────────────┐
│ AdvancedSearchSystem (总协调器 · search_system.py) │
│ · 读"设置快照"决定跑几轮、用哪个策略 │
│ · 武装"出站审计"backstop,然后把问题交给策略 │
└───────────────┬───────────────────────────────────────────────┘
│ analyze_topic(query)

┌───────────────────┐ ← 可插拔:工头(默认 langgraph-agent)
│ 研究策略 Strategy │ 也可选 source-based / focused-iteration …
└─────────┬─────────┘
│ 每个问题调用 search.run(q)

┌────────────────────────────────────┐ ← 可插拔:搜索引擎(30+ 个)
│ BaseSearchEngine.run 两阶段检索 │
│ ① 取"预览"(很多条,便宜) │
│ ② LLM 过滤出相关的少数 │
│ ③ 只对相关的抓"全文"(贵) │
└─────────┬──────────────────────────┘
│ 汇总所有轮次的结果

┌──────────────────┐
│ 跨引擎相关性过滤 │ (CrossEngineFilter:把 60 条筛到 ~10 条并重新编号)
└────────┬─────────┘

┌──────────────────┐
│ 引用合成 │ (CitationHandler:LLM 写带 [N] 引用的正文)
└────────┬─────────┘

quick → 直接当答案 │ detailed → IntegratedReportGenerator 分章节多次再跑上面这条链

最终报告 + 来源列表

各部件一句话职责。

部件干什么在哪个文件(符号)
总协调器读设置、选策略、武装安全网、跑一次研究search_system.py(AdvancedSearchSystem.analyze_topic)
研究策略"工头":决定搜什么、挖多深、何时收工advanced_search_system/strategies/*(SourceBasedSearchStrategyLangGraphAgentStrategy)
策略工厂按名字造出对应策略实例search_system_factory.py(create_strategy)
搜索引擎层统一的两阶段检索接口 + 30+ 具体引擎web_search_engines/search_engine_base.py(BaseSearchEngine.run)
跨引擎过滤把多引擎多轮结果按相关性筛选、重排、重编号advanced_search_system/filters/cross_engine_filter.py(CrossEngineFilter.filter_results)
引用合成把来源喂给 LLM,产出带 [N] 编号的正文citation_handlers/standard_citation_handler.py(analyze_followup)
报告生成detailed 模式下分章节、逐章再研究再拼装report_generator.py(IntegratedReportGenerator.generate_report)
运行时底座每用户 SQLCipher 加密库 + 冻结设置快照database/encrypted_db.py(DatabaseManager)、api/settings_utils.py(create_settings_snapshot)
出站管制按用户声明的边界决定"哪些引擎/URL 允许联网"security/egress/policy.py(EgressScopeevaluate_engine)

主线走一遍(高层,不进代码)。

  1. Web worker 或编程 API 先冻结一份"设置快照"(一个普通 dict),之后整条流水线只读它,不再碰数据库(依据:api/settings_utils.py:215)。
  2. AdvancedSearchSystem.__init__ 用工厂按名字造出策略;类构造器的缺省参数是 source-based,但应用出厂默认是 langgraph-agent(依据:search_system.py:62 vs defaults/default_settings.json:1172)。
  3. analyze_topic武装一层"出站审计"安全网(防止某个环节偷偷联网),再把问题交给策略(依据:search_system.py:336)。
  4. 策略反复调用 search.run(q);每次调用内部走两阶段检索——先便宜地取一堆预览,LLM 过滤后,只对相关的少数抓全文(依据:search_engine_base.py:594)。
  5. 所有轮次的结果做一次跨引擎过滤,再交给引用合成产出带编号的正文;all_links_of_system 这一个共享列表始终是"报告引用的唯一真相源"(依据:source_based_strategy.py:46)。
  6. quick 直接把合成结果当答案;detailed 则由报告生成器分章节、每章再跑一遍上面这条链,引用编号连续累加。

3. 阅读地图(建议顺序)

先读什么取决于你想干嘛。 下面每章一句话点明"讲什么、什么时候读"。

顺序章节讲什么 · 何时读
1研究引擎主线与策略骨架(默认 source-based)总协调器 + 策略基类 + 那条"问题→检索→过滤→合成"流水线怎么闭环。想懂主线,先读这章。
2搜索引擎层:两阶段检索、引擎池与过滤BaseSearchEngine.run 的两阶段检索、30+ 引擎、限流重试、按引擎的相关性过滤。关心"检索怎么省钱又准"读这章。
3LangGraph 智能体策略(~95% SimpleQA 的那条)出厂默认策略:LLM 自己选引擎、按需下钻、并行子代理。想懂 benchmark 那条路径读这章。
4引用合成与报告生成(quick vs detailed)编号引用怎么不串号、事实核对步骤、detailed 分章节再研究的拼装。关心"输出质量与引用"读这章。
5运行时底座:加密库 · 设置快照 · LLM 提供方 · Web 编排 · API每用户 SQLCipher 库、设置快照为什么冻结、线程上下文、Web/编程两条入口。想部署或二次开发读这章。
6安全出站管制、知识库与新闻订阅出站策略(STRICT/PUBLIC_ONLY/…)、本地知识库、周期性新闻研究。关心隐私边界与私有库读这章。

两条推荐路线。

  • 只想搞懂原理: 本页 → 01 → 02 → 04。三章覆盖"主线 + 检索 + 输出"。
  • 要部署 / 改代码: 本页 → 05 → 06 → 03。先底座与安全,再看默认策略细节。

4. 巧妙之处(值得带走的设计)

这一节挑几个不显然、但很能借鉴的决策。 每条先说妙在哪,再给锚点。

① 两阶段检索:先便宜后昂贵。 检索最贵的是"抓全文"。LDR 的引擎基类先只取预览(标题+摘要,一次很多条),用 LLM 过滤出相关的少数,只对这少数抓全文。一句抽象方法 _get_previews + search_snippets_only 开关就把"广撒网"和"精抓取"分开了(依据:search_engine_base.py:594runsearch_engine_base.py:1317_get_previews)。

② 一个共享列表当"引用唯一真相源"。 all_links_of_systemAdvancedSearchSystem 和策略同一个 list 对象(构造时按引用传入)。detailed 报告分章节多次调用 analyze_topic,每次只 extend、从不重排旧结果,配合 nr_of_links 偏移量,让 [1]…[15][16]…[28] 跨章节连续不串号(依据:source_based_strategy.py:46-57source_based_strategy.py:481)。

③ 设置"快照"冻结:并发线程读同一份 dict,不碰数据库。 一次研究会开很多线程并行检索;若每个线程各自去查加密库拿设置,既慢又可能读到中途被改的值。LDR 在run 开始时冻结一份 dict,之后全程只读它,天然线程安全、天然可复现(依据:api/settings_utils.py:215search_engine_base.py:346)。

④ 出站管制:把"策略泄漏"也算进威胁模型。langgraph-agent 里,LLM 看得见工具清单。LDR 不只在真正联网时拦截(那会让"被拒工具的延迟"泄漏策略),而是在把工具交给 agent 之前就按出站边界过滤掉,并在系统提示里明说"这些引擎本会话不存在"——双管齐下堵住时序侧信道(依据:langgraph_agent_strategy.py:657-738policy.py:60EgressScope)。

⑤ 无源不合成:宁可拒答也不编引用。 若所有检索都空手而归,引用处理器拒绝调用 LLM(否则它会凭空编造 [N]),直接返回"没有来源"的明确信息(依据:source_based_strategy.py:466-475standard_citation_handler.py:55)。

⑥ 凭据擦洗贯穿每个 except。 搜索引擎的异常常带着请求 URL(里面可能有 API key)。基类用统一的 _scrub_error(正则脱敏 + 已知密钥字面量双重擦洗)包住所有落日志/落库的错误串,避免各处漏擦(依据:search_engine_base.py:1259_scrub_error)。


5. 代码地图(导航索引)

按主题跳源码。 符号名比行号抗漂移——上游更新后优先用符号 grep 定位。路径相对克隆根 src/local_deep_research/

主题文件关键符号
总协调器 / 一次研究入口search_system.pyAdvancedSearchSystemanalyze_topic_arm_egress_backstop
策略工厂(名字→实例)search_system_factory.pycreate_strategy
默认流水线策略advanced_search_system/strategies/source_based_strategy.pySourceBasedSearchStrategy.analyze_topic
智能体策略(~95% 那条)advanced_search_system/strategies/langgraph_agent_strategy.pyLangGraphAgentStrategySearchResultsCollector_build_tools
策略基类advanced_search_system/strategies/base_strategy.pyBaseSearchStrategy
搜索引擎基类 / 两阶段检索web_search_engines/search_engine_base.pyBaseSearchEngine.run_get_previews_filter_for_relevance
引擎工厂 / 引擎注册web_search_engines/search_engine_factory.pyengine_registry.pycreate_search_engineengine_groups.py
全文抓取web_search_engines/engines/full_search.pyFullSearchResults
跨引擎过滤advanced_search_system/filters/cross_engine_filter.pyCrossEngineFilter.filter_results
单引擎相关性过滤web_search_engines/relevance_filter.pyfilter_previews_for_relevance
引用合成citation_handlers/standard_citation_handler.pybase_citation_handler.pyanalyze_followup_create_documents
报告生成(detailed)report_generator.pyIntegratedReportGenerator.generate_report
加密库(每用户)database/encrypted_db.pysqlcipher_utils.pyDatabaseManageropen_user_database
设置快照api/settings_utils.pysettings/manager.pycreate_settings_snapshot
出站策略(PDP/PEP)security/egress/policy.pyEgressScopeevaluate_enginecontext_from_snapshot
Web 编排web/services/research_service.pymode == "quick" / "detailed" 分支(research_service.py:1479:1088)
编程 APIapi/research_functions.pyquick_summarygenerate_reportdetailed_research
新闻订阅news/subscription_runner.pynews/core/search_integration.pyNewsSearchCallback

诚实边界: 本页是子库总索引,只给"是什么 / 全景 / 主线 / 精华 / 跳转表"。每个机制的代码走读(两阶段检索的具体分支、agent 的子代理并行与超时、detailed 报告的章节拼装、加密库冷启动迁移锁等)在对应章节展开;上表符号已核对到 sourceCommit。凡本页未展开的实现细节,以章节正文与源码为准。