数据截至 (上游 commit 1c4253f6774b)
MiroThinker — 深度研究 Agent 的架构与原理(总览与阅读地图)
30 秒导读: MiroThinker 是一个开源的深度研究(deep research)Agent——你给它一个需要"查很多资料、推很多步"才能答的难问题,它会自己反复搜索、抓网页、跑代码、调用子 Agent,来回几百轮,最后把答案塞进一个
\boxed{...}里交出来。这一页带你先看清"它是什么、大盘怎么转",再给你一张读完整套文档的地图。
1. 这是什么(零基础也能懂)
一句话定义: MiroThinker 是一个为"深度研究 / 预 测"优化、在 BrowseComp 这类难基准上刷分的开源 research agent。它的价值不在"聊天",而在把一个需要几十上百步、几百次工具调用才能查清的问题真的查到底。
README 里的自我定位是"a deep research agent optimized for research and prediction",并声称在 BrowseComp 基准上拿到 88.2 分(README.md:25)。
给谁用、解决什么问题。 想象你要回答这样一个问题:
"某位科学家 1990 年代发表在某冷门期刊上的那篇论文,第二作者后来创办的公司,如今的 CEO 是谁?"
这类问题的难点不是"回答",而是长链条:得先搜到论文、定位第二作者、查到他创办的公司、再查现任 CEO——中间任何一步搜错、抓不到网页、或者把上下文撑爆,整条链就断了。MiroThinker 就是冲着这种多步、易断、需要几百次工具调用的任务设计的。
它能做什么(功能):
- 反复做网页搜索(Google / Sogou)和抓取(Jina / MarkItDown 兜底);
- 在沙箱里跑 Python(E2B);
- 调用视觉 / 音频 / 推理等专用工具(见 05-tools-mcp.md);
- 把一个子 Agent(浏览 Agent)当成一个工具去调,做分工;
- 撑到几百轮不崩:靠回滚自纠 + 上下文压缩 + 失败重试。
用起来什么样(概念级,不进代码)。 最小用法就是一行命令,指定用哪个 LLM、哪套 agent 配置:
# 概念示意,取自 README quick start 的形态
uv run python main.py llm=qwen-3 agent=single_agent_keep5
main.py 里就硬编码了一个示例任务("What is the title of today's arxiv paper in computer science?"),把配置交给流水线跑一遍(apps/miroflow-agent/main.py,amain)。刷基准则走另一条外层入口,对整个数据集批量跑(见下文"主线"与 01-config-and-assembly.md)。
一句话直觉/类比。 把它想成一个不知疲倦的研究助理:你给它一个课题,它自己列检索、翻网页、记笔记、发现走错了就撕掉重来,笔记太厚了就先总结一版再继续,最后把结论工工整整写进一个方框里交给你。
2. 顶层全景(它大概怎么转)
怎么读这张图: 从上到下是"装配 → 运行 → 收尾"。上半截(Hydra → 组件)是一次性搭好舞台;中间的 Orchestrator 是反复转的主循环;最外圈的 benchmark 重试是整场跑砸了再来一遍。
┌─────────────────────────────────────────┐
benchmark 外层重试 │ common_benchmark:对数据集批量跑 │
(格式错→带失败经验) │ 失败→用失败经验增强任务描述→再跑一遍 │
└───────────────────┬─────────────────────┘
│ 每题一次
▼
┌────────────┐ create_pipeline_ ┌──────────────────────────┐
│ Hydra 配置 │──_components──────────▶ │ 装配出三大件: │
│ (YAML) │ │ ① ToolManager(工具) │
└────────────┘ │ ② LLM Client(会话) │
│ ③ OutputFormatter(格式)│
└───────────┬──────────────┘
▼
execute_task_pipeline 建 Orchestrator
│
▼
┌───────────────────────────── Orchestrator ──────────────────────────┐
│ │
│ run_main_agent() 主 Agent while 循环 │
│ 每轮: 调 LLM → 拿到工具调用 → 执行 → 结果塞回历史 → 查上下文 │
│ │ │
│ ├─ 普通工具 ──▶ ToolManager ─▶ MCP 子进程 │
│ │ (搜索/抓取/沙箱…) │
│ │ │
│ └─ "agent-*" ──▶ run_sub_agent() 子 Agent 循环 │
│ (自己也是一圈 while,当工具用) │
│ │
│ 循环结束 ──▶ AnswerGenerator.generate_and_finalize_answer() │
│ 收尾:生成/重试最终答案,抽 \boxed{},或写失败总结 │
└────────────────────────────────────────────────────────────── ────────┘
│
▼
final_boxed_answer ( \boxed{...} )
部件一句话职责:
| 部件 | 干什么 | 文件 |
|---|---|---|
| Pipeline | 组件工厂 + 单题执行入口;create_pipeline_components 造三大件,execute_task_pipeline 建 Orchestrator 跑一题 | apps/miroflow-agent/src/core/pipeline.py |
| Orchestrator | 核心大脑:跑主 Agent while 循环、把子 Agent 当工具调、驱动收尾 | apps/miroflow-agent/src/core/orchestrator.py |
| ToolManager | 工具总管:按配置连每个 MCP 子进程、列工具定义、执行工具调用 | libs/miroflow-tools/src/miroflow_tools/manager.py |
| BaseClient | LLM 会话:发请求、组织消息历史、按 keep_tool_result 裁剪、ensure_summary_context 判上下文超限 | apps/miroflow-agent/src/llm/base_client.py |
| AnswerGenerator | 收尾:带重试地生成最终答案、必要时写失败经验总结 | apps/miroflow-agent/src/core/answer_generator.py |
| ToolExecutor | 工具调用的辅助:修参数、去重判定、结果后处理、判是否该回滚 | apps/miroflow-agent/src/core/tool_executor.py |
| settings | 把配置翻译成运行时:MCP 子进程参数、把子 Agent 暴露成 工具 | apps/miroflow-agent/src/config/settings.py |
| prompt_utils / parsing_utils | 系统提示模板 / 各种解析(工具调用、\boxed{}、失败总结、容错) | apps/miroflow-agent/src/utils/ |
3. 主线走一遍(高层,不进代码)
这节把"一个任务从进到出"顺一遍,只讲流向,细节留给各章。
第 0 步:装配舞台。 create_pipeline_components(cfg) 读 Hydra 配置,造出 main_agent_tool_manager、sub_agent_tool_managers、output_formatter 三样东西(pipeline.py:180 create_pipeline_components)。这一步只搭骨架,不干活。详见 01-config-and-assembly.md。
第 1 步:建 Orchestrator,进主循环。 execute_task_pipeline 造 LLM Client 和 Orchestrator,然后调 run_main_agent(pipeline.py:35 execute_task_pipeline)。主 Agent 的核心是一个 while turn_count < max_turns 循环(orchestrator.py:812),每一轮:
- 调 LLM 拿回复(可能含工具调用);
- 若模型给了工具调用,逐个执行;
- 把工具结果塞回消息历史;
- 检查上下文是否要压缩。
第 2 步:工具调用分两路。 循环里对每个工具调用判断(orchestrator.py:923):
- 名字以
agent-开头 → 走run_sub_agent,把 子 Agent 当工具跑一圈(它内部也是一个 while 循环,orchestrator.py:390); - 否则 → 走
main_agent_tool_manager.execute_tool_call,连到对应 MCP 子进程执行(manager.py:198)。
第 3 步:收尾抽答案。 循环因"到达最大轮数 / 上下文超限 / 模型不再要工具"而结束后,调 generate_and_finalize_answer(orchestrator.py:1170),它带重试地让模型给最终答案,再用 _extract_boxed_content 抠出 \boxed{...} 里的内容(answer_generator.py:462;抽取见 output_formatter.py:18)。这个 final_boxed_answer 就是对外的答案。详见 04-context-and-answer.md。
第 4 步(仅刷基准时):外层重试。 跑基准的外层循环发现"没抽到合规 \boxed{}"时,会把模型自己写的失败经验总结拼进任务描述,再整题重跑一遍(benchmarks/common_benchmark.py,run_single_task 的 while format_retry_count <= max_format_retries)。
4. 巧妙之处清单(指向各章)
这份代码里真正值得带走的设计,分散在各章。先给你一张"精华索引":
| 巧妙之处 | 白话它妙在哪 | 去哪一章看 |
|---|---|---|
| interactive scaling(几百轮) | 把"能跑多少轮工具交互"当成和模型大小、上下文长度并列的第三个性能维 度;循环用 max_turns + EXTRA_ATTEMPTS_BUFFER 撑到几百次调用(orchestrator.py:53) | 02-orchestrator-loop.md |
| rollback 自纠 | 模型格式错了、搜索空结果、拒答、重复查询——不是硬吞,而是弹掉最后一条消息、turn 回退一步重来(orchestrator.py:180 _handle_response_format_issues) | 03-robustness-rollback.md |
| 查询去重 | 记住每个 (工具, 查询串) 的次数,重复查就回滚,逼模型换角度(orchestrator.py:257 _check_duplicate_query) | 03-robustness-rollback.md |
| 容错修复 | 模型常把参数名写错(code 而非 code_block)、把 MCP 标签写进正文——代码主动改对参数、识别标签(tool_executor.py:68 fix_tool_call_arguments) | 03-robustness-rollback.md |
| 上下文压缩 + 失败经验重试 | 上下文快撑爆时先"退一步总结";整题失败时把经过压成结构化失败总结,喂给下一次重试(answer_generator.py:170 generate_failure_summary) | 04-context-and-answer.md |
| 把 sub-agent 当工具暴露 | 子 Agent 不是特殊分支,而是被 expose_sub_agents_as_tools 包装成一个普通工具定义,主 Agent 像调工具一样调它(settings.py:383) | 01-config-and-assembly.md、02-orchestrator-loop.md |
| 把强模型当推理工具 | "推理"本身也是一个 MCP 工具(tool-reasoning,底层接 Anthropic 模型),让主模型能把难推理外包给更强的模型(settings.py:221) | 05-tools-mcp.md |
5. 代码地图 + 阅读地图
建议阅读顺序(由浅入深):先读本页建立大盘 → 01 看"配置怎么变成运行时" → 02 看"循环怎么转,这是全项目最核心的一章" → 03、04 看两大硬骨头(鲁棒性、上下文) → 05 看工具层。
各章一句话讲什么:
- 01-config-and-assembly.md — 配置驱动的装配。 一份 Hydra YAML(
llm=? agent=? benchmark=?)如何经create_pipeline_components/create_mcp_server_parameters长出 ToolManager、LLM Client、把子 Agent 暴露成工具的整套运行时。 - 02-orchestrator-loop.md — 编排主循环。 主 Agent 的
while循环节奏、子 Agent 循环、一次工具调用从"模型请求"到"结果回填"的完整路径,以及 interactive scaling 的实现。 - 03-robustness-rollback.md — 自我纠错。 回滚计数、查询去重、
should_rollback_result、 参数名修复、拒答/格式标签识别——让几百轮循环不因单点跑偏而崩。 - 04-context-and-answer.md — 上下文管理与答案收尾。
keep_tool_result裁剪、ensure_summary_context退一步总结、context_compress_limit的决策表、失败经验重试、\boxed{}抽取。 - 05-tools-mcp.md — 工具即 MCP 服务。 每个工具是一个 stdio 子进程:搜索、抓取、E2B 沙箱、视觉/音频、以及"把强模型当推理工具"各自怎么接入。
代码地图(导航索引,可按符号名 grep 定位):
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 组件工厂 | apps/miroflow-agent/src/core/pipeline.py | create_pipeline_components |
| 单题执行入口 | apps/miroflow-agent/src/core/pipeline.py | execute_task_pipeline |
| 主 Agent 循环 | apps/miroflow-agent/src/core/orchestrator.py | Orchestrator.run_main_agent |
| 子 Agent 循环 | apps/miroflow-agent/src/core/orchestrator.py | Orchestrator.run_sub_agent |
| 回滚/格式容错 | apps/miroflow-agent/src/core/orchestrator.py | _handle_response_format_issues |
| 查询去重 | apps/miroflow-agent/src/core/orchestrator.py | _check_duplicate_query |
| 参数名修复 | apps/miroflow-agent/src/core/tool_executor.py | fix_tool_call_arguments |
| 是否回滚判定 | apps/miroflow-agent/src/core/tool_executor.py | should_rollback_result |
| 最终答案 + 失败总结 | apps/miroflow-agent/src/core/answer_generator.py | generate_and_finalize_answer / generate_failure_summary |
\boxed{} 抽取 | apps/miroflow-agent/src/io/output_formatter.py | _extract_boxed_content |
| 工具管理 / 执行 | libs/miroflow-tools/src/miroflow_tools/manager.py | ToolManager.execute_tool_call |
| MCP 子进程参数 | apps/miroflow-agent/src/config/settings.py | create_mcp_server_parameters |
| 子 Agent 当工具 | apps/miroflow-agent/src/config/settings.py | expose_sub_agents_as_tools |
| 上下文超限判定 | apps/miroflow-agent/src/llm/base_client.py(provider 实现于 src/llm/providers/) | ensure_summary_context |
| 工具结果裁剪 | apps/miroflow-agent/src/llm/base_client.py | _remove_tool_result_from_messages |
| 基准外层重试 | apps/miroflow-agent/benchmarks/common_benchmark.py | run_single_task |
本文是子库总索引(Layer 0 + Layer 1 + 阅读地图)。各机制的代码走读与图示在对应章节文件里展开;所有引用 as-of commit
370f98361553ddf787bedc5745760e04114cb161。