数据截至 (上游 commit a53b13c7022b)
05 · 深入:精华、边界、对比
这一章是「带走的东西」:哪些设计值得抄、它刻意不做什么、会在哪崩、和别的深度研究实现比有什么不同。
5.1 巧妙之处(可借鉴)
① 滚动摘要 = 用状态当记忆,省上下文。 摘要节点只读最新一轮资料(web_research_results[-1],graph.py:284),历史全部沉淀进 running_summary。这样上下文长度不随轮数线性 膨胀——本地小模型上下文窗口小,这个设计是刚需。妙在它把「记忆压缩」外包给了 LLM 自己(每轮重写摘要)。
② 不确定性收口到一个函数。 所有「模型可能不听话」的处理都锁在 generate_search_query_with_structured_output(graph.py:44),每条失败路都通向同一个兜底查询。上层图逻辑因此可以假装结构化输出永远成功。参见 03 章。
③ 搜索后端多态。 四个后端被抹平成同一结果形状(见 04 章),web_research 里只是 if/elif 选一个,下游完全无感。加第五个后端只需写一个返回统一形状的函数。
④ 配置三级优先级。 Configuration.from_runnable_config(configuration.py:63-81)按「环境变量 > UI 传入的 configurable > 类默认值」取值:对每个字段先读 os.environ.get(name.upper()),拿不到再退到 configurable,最后是 Field 默认。一行 dict 推导 + 过滤 None 就实现了(configuration.py:73-79)。这让同一份图既能用 .env 部署、又能在 Studio 里现场调参。
5.2 边界与局限(诚实)
每轮只有一条线性查询,没有并行分支。 尽管 README 说灵感来自 IterDRAG 的「子问题分解」,实现上并没有真的把问题拆成多个子问题并行检索——每一轮就一条 search_query,串行推进(见 01 章的数据流)。所以它是「迭代加深」,不是「树状展开」。
没有对搜索结果做相关性排序或质量过滤。 搜到什么就喂什么(仅按 URL 去重),来源好坏全靠搜索引擎和 LLM 的判断力。
兜底会静默降级。 结构化输出失败时退回 Tell me more about {topic}(graph.py:186),这一轮就变成泛泛搜索,且不会告知用户发生了降级。小模型上这可能悄悄拉低质量。
Tavily 每轮只取 1 条结果(graph.py:217,硬编码),来源覆盖面比其他后端窄。
循环次数比直觉多一次。 max_web_research_loops=3 实际搜 4 次(route_research 的 <= + 先搜后计数,见 01 章 §1.4)。
摘要质量强依赖本地模型。 整个「读—写—反思」都压在一个本地 LLM 上;小模型可能反思出低质量的追问,或摘要时丢关键信息。README 自己就点名 R1 1.5B/7B 有 JSON 困难(README.md:151)。
无持久化/无缓存。 没有 checkpointer、不缓存搜索结果,每次从头跑;graph = builder.compile()(graph.py:465)未传 checkpointer。
5.3 横向对比
把它放到「深度研究 agent」这一类里看,它的定位是最小、最本地的那一端:
| 维度 | Local Deep Researcher | 典型「重型」深度研究 agent |
|---|---|---|
| 编排 | LangGraph 固定 5 节点状态机 | 常见多 agent / 动态规划 |
| 查询结构 | 每轮单条线性查询 | 常并行拆多个子问题 |
| 模型 | 本地 Ollama/LMStudio | 多为云端大模型 |
| 记忆 | 单段滚动摘要 | 常有向量库/多文档存储 |
| 代码量 | 7 个文件,可一晚读完 | 通常大得多 |
它的价值不在「最强」,而在「最好懂、可完全本地、可当深度研究骨架的教学样板」。README 也提到有一个 TypeScript 移植版(README.md:190)。
同 shelf 可对照阅读: 本 shelf(ai-agent-reference / deep-research)下其他深度研究实现,重点看它们如何处理「查询分解是单条还是多路」「记忆用摘要还是向量库」「是否有反思环」这三点差异——本项目在这三点上都选了最简形态。
5.4 总代码地图(全项目导航)
| 你想看 | 文件 | 符号 |
|---|---|---|
| 整张图怎么连、循环判停 | src/ollama_deep_researcher/graph.py | builder / route_research |
| 共享状态与累加字段 | src/ollama_deep_researcher/state.py | SummaryState |
| 生成查询 / 反思节点 | src/ollama_deep_researcher/graph.py | generate_query / reflect_on_summary |
| 滚动摘要节点 | src/ollama_deep_researcher/graph.py | summarize_sources |
| 收尾去重 | src/ollama_deep_researcher/graph.py | finalize_summary |
| 结构化输出兜底 | src/ollama_deep_researcher/graph.py | generate_search_query_with_structured_output |
| LLM 装配(4 种) | src/ollama_deep_researcher/graph.py | get_llm |
| LMStudio 适配 | src/ollama_deep_researcher/lmstudio.py | ChatLMStudio |
| 搜索后端 + 去重格式化 | src/ollama_deep_researcher/utils.py | duckduckgo_search / deduplicate_and_format_sources |
| 剥离推理标记 | src/ollama_deep_researcher/utils.py | strip_thinking_tokens |
| 配置与三级优先级 | src/ollama_deep_researcher/configuration.py | Configuration / from_runnable_config |
| 五段提示词 + 两套结构化指令 | src/ollama_deep_researcher/prompts.py | query_writer_instructions 等 |
| Studio 入口注册 | langgraph.json | graphs.ollama_deep_researcher |