<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>aicooleo</title><description>记录技术、折腾与生活</description><link>https://aicooleo.cn/</link><language>zh_CN</language><item><title>基于DeepWiki梳理Agent应用开源项目核心流程</title><link>https://aicooleo.cn/blog/posts/prompt-deepwiki-agent-harness/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/prompt-deepwiki-agent-harness/</guid><description>以 Agent Harness 架构师视角拆开源 Agent 项目：还原 User → Agent → Context → LLM → Tool 的真实执行链路，并标到文件 / 类 / 函数级。</description><pubDate>Tue, 20 Oct 2026 11:40:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：这份提示词要 AI 以 Agent Harness 架构师的身份，把一个开源 Agent 项目的执行链路还原到&quot;文件 / 类 / 函数&quot;级别——它到底怎么把模型的一次思考，变成一次真正跑完的 Run。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;什么时候用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;你要自己写一个 Agent 框架 / Harness，想先看清别人怎么分层的。&lt;/li&gt;
&lt;li&gt;在几个 Agent 框架之间选型，功能表看起来都差不多，需要看真实的执行链路差异。&lt;/li&gt;
&lt;li&gt;读了源码但找不到入口：不知道一次 Run 从哪个函数开始，上下文在哪一层组装。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;提示词&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;请从 Agent Harness 架构师的视角分析这个开源项目。

重点不是介绍功能，而是帮我快速看懂它&quot;是怎么跑起来的&quot;。

请回答：

1. 它解决什么核心问题？

2. 它的核心架构是什么？找出最关键的模块。

3. 一次 Agent Run 是怎么执行的？
   从 User → Agent → Context → LLM → Tool → Observation → Context → LLM → … → Final Response，
   根据源码还原真实执行链路，并标出关键文件 / 类 / 函数。

4. 用 Mermaid 绘制核心执行流程，重点体现 Agent Loop、Context、Tool、State、Middleware 等机制。

5. 总结 3～5 个最值得学习的设计亮点，并说明它们解决了什么问题。

请基于源码分析，不要停留在 README 层面。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;和「框架流程版」的区别&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href=&quot;/posts/prompt-deepwiki-framework-analysis/&quot;&gt;框架流程版&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;本篇（Agent Harness 版）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;视角&lt;/td&gt;
&lt;td&gt;资深软件架构师&lt;/td&gt;
&lt;td&gt;Agent Harness 架构师&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;输出粒度&lt;/td&gt;
&lt;td&gt;不写类名、函数名、源码路径&lt;/td&gt;
&lt;td&gt;必须标出关键文件 / 类 / 函数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;重点&lt;/td&gt;
&lt;td&gt;组件职责、数据流、可借鉴设计&lt;/td&gt;
&lt;td&gt;Agent Loop、Context、Tool、State、Middleware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适合&lt;/td&gt;
&lt;td&gt;看懂一个平台是怎么设计的&lt;/td&gt;
&lt;td&gt;看懂一个运行时是怎么跑起来的&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;用的时候注意&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&quot;不要停留在 README&quot;这句话，光说没用。&lt;/strong&gt; 模型拿不到源码时仍然会照 README 编。要么把仓库目录树、入口文件、一次 Run 的日志一起喂进去，要么直接用 DeepWiki 的仓库页面——它已经把代码结构索引好了，省掉一半功夫。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 3 条最容易注水。&lt;/strong&gt; 让它逐跳标注，并且明确要求：哪一跳这个项目没有（比如没有独立的 Observation 阶段、State 直接挂在 Context 上），就直接说&quot;没有&quot;。缺哪一跳，往往正好是这个框架的设计取向。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次只问一个项目。&lt;/strong&gt; 两个仓库一起丢进去，它会把两边的机制揉成一份&quot;通用 Agent 架构&quot;，看起来头头是道，实际上哪边都不对。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 4 条要求画 Loop 的时候，让它把退出条件画出来&lt;/strong&gt;（最大轮次、无工具调用、显式 finish）。Agent Loop 的坑几乎都在这条边上。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;一个真实产出&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/posts/phoenix-core-flows/&quot;&gt;Phoenix 核心功能与实现流程&lt;/a&gt; 是「框架流程版」的产出样例；如果你想看 Agent Harness 版的成品，找个 Agent 框架（OpenHands、SWE-agent、AutoGen 这类）跑一遍，重点看第 3 条的链路标注得细不细——那里最能看出这份提示词有没有生效。&lt;/p&gt;
</content:encoded></item><item><title>AGENTS.md 十条协作规则</title><link>https://aicooleo.cn/blog/posts/prompt-agents-md-rules/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/prompt-agents-md-rules/</guid><description>可以直接放进 AGENTS.md / CLAUDE.md 的十条硬约束：语言、并行授权、第一性原理、根因、质疑、读码、自主权、交付闸门与记忆检索。</description><pubDate>Tue, 20 Oct 2026 11:20:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：一份可以直接贴进 &lt;code&gt;AGENTS.md&lt;/code&gt; / &lt;code&gt;CLAUDE.md&lt;/code&gt; 的十条规则，把&quot;AI 该怎么配合我干活&quot;从口头习惯变成硬约束——少废话、敢并行、先拆第一性、修根因、敢质疑、读懂再改、交付只给读者看。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;这十条在管什么&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;分组&lt;/th&gt;
&lt;th&gt;规则&lt;/th&gt;
&lt;th&gt;解决的默认毛病&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;基础行为&lt;/td&gt;
&lt;td&gt;1、10&lt;/td&gt;
&lt;td&gt;语言漂移；没有历史上下文就瞎猜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;协作方式&lt;/td&gt;
&lt;td&gt;2、7&lt;/td&gt;
&lt;td&gt;不敢用 subagent；不敢自己动手查证&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;思考质量&lt;/td&gt;
&lt;td&gt;3、4、5&lt;/td&gt;
&lt;td&gt;先套常见方案；只修症状；默认你判断正确&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;交付质量&lt;/td&gt;
&lt;td&gt;6、8、9&lt;/td&gt;
&lt;td&gt;没读代码就改；把作者视角写进成品&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;规则&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;1. 语言
必须用中文回复我。

2. Subagent 使用授权
当任务适合并行探索、独立验证、拆分实现、专项审查、长流程加速或多视角分析时，AI 可以自主判断并直接调用当前运行环境提供的 subagent / multi-agent 工具，不需要等待用户显式点名或再次确认。
使用前先拆清任务边界、输入输出、写入范围、验收标准和主线关键路径；优先把可并行、可独立验证、写入范围清晰且不会阻塞主线的工作交给 subagent。
主 agent 必须负责结果整合、冲突处理、事实核验、测试验证和最终交付，不能把未核验的 subagent 结论直接当作最终结论。

3. 第一性原理
不要先套常见方案。先拆解真正目标、已知输入、约束、不可违反条件、事实与假设、最小可行解，以及更稳健的下一层改进，最后给出可执行方案。

4. 根因解决
不要只修表面症状。分析直接问题、根本原因、现有设计为何允许问题发生、哪些临时补丁不推荐，并给出根因级解决方案。

5. 挑战用户思路
不要默认用户判断正确。把用户方案当假设审查，列出隐含假设、最容易错的假设、最可能失败场景、替代方案，并说明负责人视角会怎么选。

6. 先理解代码再改
修改前先找相关文件和调用链，解释当前逻辑如何工作，判断设计为什么这样写，找最小必要改动点，说明风险和验证测试，再开始实现。

7. 信任 AI 自主探索
给目标、验收标准、约束；允许 AI 自主阅读代码、搜索上下文、运行测试、调整实现方案；如果用户给的路径和目标冲突，以目标和验收标准为准；完成后说明改了什么、为什么、如何验证。

8. 最终读者视角交付闸门
创建或编辑任何用户可见的页面、UI、PPT、课程文档、文章或公开内容时，成品必须只面向最终读者/用户，不能把作者/开发者/制作者自己的内部过程写进成品。
默认禁止把录制安排、讲师提示、作者备注、前端实现流程、技术选型、设计过程、PPT 制作思路、内部规划等元叙事写入正文；除非用户明确要求。
交付前主动检查并清理这些作者视角内容，确保读者看到的是内容、体验、步骤、观点和结论，而不是工作台。

9. 公开 UI / 视觉稿文案闸门
创建或编辑用户会看到的页面、HTML mockup、UI 视觉稿、营销页、产品页时，正文不能出现面向设计评审、开发者或内部制作过程的解释性文案。
内部推导、设计规范、组件规则、开发边界和验收标准只能放在规范文档或备注中；公开页面只保留用户能直接感知的价值、动作、步骤和结果。

10. 记忆
需要历史背景、长期偏好或旧决策时，可以通过内置记忆或者 Nowledge Mem 先检索再下结论。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;怎么用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;放哪&lt;/strong&gt;：Claude Code 默认读 &lt;code&gt;CLAUDE.md&lt;/code&gt;，Codex 等工具读 &lt;code&gt;AGENTS.md&lt;/code&gt;。不确定就两处都放（内容保持一致），别只放一处然后奇怪为什么没生效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全局还是项目&lt;/strong&gt;：跨项目都成立的习惯（1、3、5、8）放全局；跟这个仓库相关的（6、7）放项目根，跟着仓库走。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 8、9 条按需留&lt;/strong&gt;：这两条是&quot;对外的成品&quot;专用，纯后端项目可以删，删了反而更聚焦。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 5 条要有心理准备&lt;/strong&gt;：它会明显让 AI 变啰嗦，还会反过来质疑你的方案。不想被挑战就删掉；想要它当&quot;审稿人&quot;，就留着。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 2 条是空转条款&lt;/strong&gt;：运行环境本身不提供 subagent 工具时，这条不会生效——写了没坏处，但别指望它凭空变出并行能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;一条经验&lt;/h2&gt;
&lt;p&gt;规则不是越多越好。十条以上，模型会开始忽略靠后的几条；真正被稳定执行的，通常是&lt;strong&gt;能被验证的那几条&lt;/strong&gt;——&quot;必须用中文&quot;一眼可查，&quot;修根因&quot;就没法自动判定。所以规则写完之后，最好顺手给每条配一个可检查的判据（比如&quot;改完后列出改了什么、怎么验证&quot;），命中率会高很多。&lt;/p&gt;
</content:encoded></item><item><title>费曼教学法：把复杂概念讲到我听懂</title><link>https://aicooleo.cn/blog/posts/prompt-feynman-teaching/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/prompt-feynman-teaching/</guid><description>一段几十字的提问模板，让 AI 用费曼教学法把 XX 讲到你真听懂：先拆最基础的细节，再用类比和日常经验接住。</description><pubDate>Tue, 20 Oct 2026 11:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：把 AI 设定成&quot;懂行又会教的朋友&quot;，用费曼教学法把 &lt;code&gt;XX&lt;/code&gt; 讲到你真听懂——先讲最基础的细节，再用类比和日常经验接住，最后短句收尾。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;什么时候用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;学一个新领域，官方文档和教材都看不进去，需要有人从&quot;这东西到底在干嘛&quot;讲起。&lt;/li&gt;
&lt;li&gt;面试或分享前突击一个概念，只要 5 分钟能说清楚的那一版。&lt;/li&gt;
&lt;li&gt;读论文卡在某一段，需要有人把那段的前提假设补出来。&lt;/li&gt;
&lt;li&gt;反过来用：让 AI 当学生，你讲给它听，它挑你讲不清的地方——这才是费曼法的原味。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;提示词&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;你是我的好朋友，也是一个 XX 专家，你能向我传授 XX 知识。
你的教学方法受到理查德·费曼教学法的启发：通过清晰而引人入胜的解释，让复杂的问题变得通俗易懂；把信息分解成更简单的部分，使用类比，并把概念与日常经验联系起来，以加深理解。

放轻松，我的智商可能不是很高，你需要从最简单的细节讲起；我的耐心不是很多，需要你简短而清晰地告诉我答案。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;怎么改&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;想要的效果&lt;/th&gt;
&lt;th&gt;加一句&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;更短&lt;/td&gt;
&lt;td&gt;每段不超过 5 句，先给结论，再展开一句理由&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;更准&lt;/td&gt;
&lt;td&gt;每讲完一个概念，用一个生活类比复述一遍，再问我一个问题检查我有没有听懂&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;更系统&lt;/td&gt;
&lt;td&gt;先给我一张&quot;这个概念由哪几个部分组成&quot;的清单，再逐个讲&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查漏补缺&lt;/td&gt;
&lt;td&gt;讲完后列出三个初学者最容易搞错的地方，并说明错在哪&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;XX&lt;/code&gt; 记得两处都换掉（&quot;XX 专家&quot;和&quot;传授 XX 知识&quot;），只换一处模型会拿不准你到底想学什么。&lt;/p&gt;
&lt;h2&gt;一个容易踩的坑&lt;/h2&gt;
&lt;p&gt;&quot;简短而清晰&quot;经常被模型理解成&quot;少讲&quot;，结果给一堆结论、没有推导。真正有效的不是要求它变短，而是&lt;strong&gt;给它结构&lt;/strong&gt;：一句话结论 → 一个类比 → 一个反例 → 一个小练习。结构定死了，长度自然会收住，信息密度反而更高。&lt;/p&gt;
&lt;p&gt;真按费曼法走，最值钱的一步是&quot;讲给别人听&quot;。所以更狠的用法是：先让它讲，然后你复述一遍，让它只指出你复述里错的和漏的——这一步比读十遍都管用。&lt;/p&gt;
</content:encoded></item><item><title>Phoenix 核心功能与实现流程</title><link>https://aicooleo.cn/blog/posts/phoenix-core-flows/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/phoenix-core-flows/</guid><description>从 OTLP 双通道摄入、异步批量落库，到 Evaluation、数据集实验与 PXI，拆解 Phoenix 这条 LLM 可观测链路是怎么跑起来的。</description><pubDate>Tue, 20 Oct 2026 10:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：Phoenix 是 Arize 开源的 LLM 应用可观测性平台——用 OpenTelemetry 的 OTLP 协议把 LLM 应用里每一次检索、工具调用、模型推理采成 Trace，再在这份 Trace 上做评估、实验和 Prompt 迭代。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;LLM 应用的故障大多不抛异常，只表现为&quot;答得不对&quot;。要定位这种问题，你得看到这次回答用了哪几段检索文档、真正发给模型的 prompt 长什么样、token 花在哪、哪一步慢——这些恰好是 Trace 天然能表达的东西。Phoenix 的全部能力，都长在这份 Trace 上。&lt;/p&gt;
&lt;p&gt;下面按&quot;是什么 → 整体架构 → 核心流程 → 底层原理 → 值得借鉴&quot;的顺序拆，重点在&lt;strong&gt;第 4 章&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;1. 项目简介&lt;/h2&gt;
&lt;p&gt;Phoenix 是面向 LLM 应用的可观测性（Observability）平台，解决&quot;LLM 应用是黑盒&quot;的问题：一次请求会在检索、工具调用、模型推理之间反复跳转，出问题时既不知道错在哪一步，也不知道 prompt 实际长什么样。&lt;/p&gt;
&lt;p&gt;围绕一次请求的生命周期，它提供三段能力：&lt;strong&gt;记录&lt;/strong&gt;（追踪）、&lt;strong&gt;判断&lt;/strong&gt;（评估）、&lt;strong&gt;比较&lt;/strong&gt;（数据集与实验）。&lt;/p&gt;
&lt;p&gt;典型场景：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;用它做什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;开发期调试 Agent / RAG&lt;/td&gt;
&lt;td&gt;看一次回答到底检索了什么、prompt 拼成什么样&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;生产监控&lt;/td&gt;
&lt;td&gt;盯 p95 延迟、token 成本、错误率&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt 回归&lt;/td&gt;
&lt;td&gt;把线上 bad case 变成 Dataset，改完 Prompt 跑实验对比&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;质量评估&lt;/td&gt;
&lt;td&gt;LLM-as-judge 批量打分，按分数筛出问题 case&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;与同类方案相比，Phoenix 最明显的两个特点是：&lt;strong&gt;完全自托管、开源免费&lt;/strong&gt;（本地一条命令或 Docker 起一个实例，数据不出内网），以及&lt;strong&gt;基于 OpenTelemetry 开放标准&lt;/strong&gt;——埋点代码不被锁定，同一套 instrumentation 可以导出到任何支持 OTLP 的后端。&lt;/p&gt;
&lt;h2&gt;2. 核心功能概览&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;核心功能&lt;/th&gt;
&lt;th&gt;功能说明&lt;/th&gt;
&lt;th&gt;典型使用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tracing（链路追踪）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;通过 OTLP 接收并存储 Span / Trace，可视化 LLM 调用链&lt;/td&gt;
&lt;td&gt;调试 RAG 检索链路、定位慢调用与异常&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Evaluation（评估）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LLM-as-judge 对相关性、幻觉、毒性等维度打分&lt;/td&gt;
&lt;td&gt;上线前质量基线、线上持续评估&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Datasets &amp;amp; Experiments&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;版本化数据集 + 对比实验，跟踪 Prompt / 模型变更效果&lt;/td&gt;
&lt;td&gt;Prompt 回归测试、模型 A/B 对比&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Playground &amp;amp; Prompt Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prompt 模板版本管理、参数调优、重放真实 Trace&lt;/td&gt;
&lt;td&gt;基于线上 bad case 迭代 Prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metrics Dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;自动聚合延迟、token、成本、错误率&lt;/td&gt;
&lt;td&gt;生产环境监控与告警&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PXI / Remote MCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;内置 AI Agent 调查 Trace、驱动产品操作；&lt;code&gt;/mcp&lt;/code&gt; 端点供 Claude Code / Cursor 查询&lt;/td&gt;
&lt;td&gt;自动化根因分析、编码 Agent 集成&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这六项里，Tracing 是地基：后五项的数据全部来自 Span，区别只是&quot;读出来做什么&quot;——评估是给 Span 打分，实验是拿 Dataset 跑出新 Span，仪表盘是聚合 Span，PXI 是让 Agent 用自然语言查 Span。&lt;/p&gt;
&lt;p&gt;Playground 与 Prompt 管理本质上是&quot;读 Trace / 跑 Dataset&quot;的组合封装，本文不单独展开，下面聚焦五条最能体现架构取舍的链路。&lt;/p&gt;
&lt;h2&gt;3. 整体架构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TB
    UserApp[&quot;用户 LLM 应用&amp;lt;br/&amp;gt;OTel SDK / OpenInference 探针&quot;]
    GRPC[&quot;OTLP gRPC Server&amp;lt;br/&amp;gt;端口 4317&quot;]
    HTTP[&quot;FastAPI HTTP Server&amp;lt;br/&amp;gt;端口 6006&quot;]
    Bulk[&quot;BulkInserter&amp;lt;br/&amp;gt;异步批量写入&quot;]
    DB[(&quot;数据库&amp;lt;br/&amp;gt;SQLite / PostgreSQL&quot;)]
    API[&quot;GraphQL + REST v1 API&quot;]
    UI[&quot;React SPA 前端&quot;]
    Agent[&quot;PXI Agent / MCP&quot;]

    UserApp --&amp;gt;|&quot;OTLP spans（gRPC）&quot;| GRPC
    UserApp --&amp;gt;|&quot;OTLP spans（HTTP）&quot;| HTTP
    GRPC --&amp;gt; Bulk
    HTTP --&amp;gt;|&quot;OTLP/HTTP 摄入&quot;| Bulk
    Bulk --&amp;gt;|&quot;批量写入&quot;| DB
    DB --&amp;gt;|&quot;查询读取&quot;| API
    API --&amp;gt;|&quot;GraphQL&quot;| UI
    API --&amp;gt;|&quot;内部工具&quot;| Agent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务端同时跑两个入口：gRPC 服务器（4317）专门接收 OTLP Trace，生产环境用它压缩率高、开销低；FastAPI（6006）一手承担 OTLP/HTTP 摄入（本地开发、受限网络场景更方便），一手提供 REST / GraphQL API，并托管前端静态资源。两条摄入通道的终点是同一个 &lt;code&gt;BulkInserter&lt;/code&gt;：它把 Span 放进内存队列，异步批量落库，避免高并发下每条 Span 一个事务。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;组件&lt;/th&gt;
&lt;th&gt;职责&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OTLP gRPC Server（4317）&lt;/td&gt;
&lt;td&gt;接收生产环境的 OTLP Trace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FastAPI HTTP Server（6006）&lt;/td&gt;
&lt;td&gt;OTLP/HTTP 摄入、REST v1 / GraphQL API、静态前端托管&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BulkInserter&lt;/td&gt;
&lt;td&gt;队列缓冲 + 批量写入，解耦摄入与落库&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数据库（SQLite / PostgreSQL）&lt;/td&gt;
&lt;td&gt;Span / Trace、Dataset、Experiment、Annotation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphQL + REST API&lt;/td&gt;
&lt;td&gt;前端与客户端 SDK 的读写入口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;React SPA&lt;/td&gt;
&lt;td&gt;Trace 树、仪表盘、Playground、PXI Chat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PXI Agent / Remote MCP&lt;/td&gt;
&lt;td&gt;服务端 Agent 与外部 MCP 客户端入口&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;存储层默认 SQLite（本地零依赖），生产可切 PostgreSQL。查询侧不做预聚合：Span 的属性在写入时就已规范化，读的时候按需聚合——这一点在第 4.4 节还会展开。&lt;/p&gt;
&lt;h2&gt;4. 核心功能实现流程&lt;/h2&gt;
&lt;h3&gt;4.1 Tracing（OTLP 链路追踪摄入）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;① 功能作用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;记录请求在 LLM 应用内部的传播路径（检索 → 工具调用 → 生成），形成一棵可追溯的 Trace 树。这是 Phoenix 最基础的能力，其他所有功能都建立在 Trace 数据之上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 核心执行流程&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户应用调用 &lt;code&gt;phoenix.otel.register()&lt;/code&gt; 创建 TracerProvider，并用 &lt;code&gt;openinference-instrumentation-*&lt;/code&gt; 自动探针化 OpenAI / LangChain 等 SDK，或手动用 decorator 创建 Span。&lt;/li&gt;
&lt;li&gt;应用执行 LLM 调用时，OpenTelemetry SDK 生成 Span，并写入 OpenInference 语义属性（span kind、token 计数、prompt 模板、检索文档与分数）。&lt;/li&gt;
&lt;li&gt;SpanProcessor（推荐 BatchSpanProcessor）把 Span 攒成批次，经 OTLP 导出——gRPC 到 4317，或 HTTP 到 6006。&lt;/li&gt;
&lt;li&gt;服务端解析 OTLP payload，把 Span 归一化成内部结构并入队 &lt;code&gt;BulkInserter&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BulkInserter&lt;/code&gt; 异步批量写入数据库（SQLAlchemy ORM，SQLite / PostgreSQL 皆可），事务边界按批而不是按条。&lt;/li&gt;
&lt;li&gt;React 前端通过 GraphQL 查出某个项目下的 Trace 树，渲染时间线、属性、异常与 token 统计。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;③ 执行流程&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant App as 用户应用（OTel SDK）
    participant Col as Phoenix OTLP 接入层
    participant BI as BulkInserter 队列
    participant DB as 数据库
    participant UI as React UI（GraphQL）

    App-&amp;gt;&amp;gt;App: 执行 LLM 调用，生成 Span
    Note over App: Span 携带 OpenInference 语义属性
    App-&amp;gt;&amp;gt;Col: BatchSpanProcessor 导出 OTLP batch（4317 / 6006）
    Col-&amp;gt;&amp;gt;Col: 解析 payload，归一化 Span 结构
    Col-&amp;gt;&amp;gt;BI: 入队
    BI-&amp;gt;&amp;gt;DB: 批量 INSERT Span / Trace
    UI-&amp;gt;&amp;gt;DB: GraphQL 查询 Trace 树
    DB--&amp;gt;&amp;gt;UI: Span 列表 + 属性
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;④ 核心实现原理&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OTLP 双通道摄入&lt;/strong&gt;：gRPC（4317）与 HTTP（6006）共用同一套解析与落库逻辑，只在前端接入层分叉。前者适合生产（长连接、二进制编码、压缩率更高），后者适合本地与受限网络（防火墙放行一个 HTTP 端口即可）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenInference 语义约定&lt;/strong&gt;：Span 的属性不是随手写的日志字段，而是一套约定——span kind 区分 LLM / Tool / Retriever / Chain，&lt;code&gt;llm.token_count&lt;/code&gt;、&lt;code&gt;llm.prompts&lt;/code&gt;、&lt;code&gt;retrieval.documents&lt;/code&gt; 等属性让摄入端可以结构化解析。这正是&quot;能按模型聚合 token 成本&quot;&quot;能展开某条检索的文档与分数&quot;的前提：存的是结构化字段，不是原始文本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异步批量写入&lt;/strong&gt;：&lt;code&gt;BulkInserter&lt;/code&gt; 用队列把&quot;接收&quot;和&quot;落库&quot;解耦。摄入延迟敏感、落库吞吐导向，两者用队列隔开后，写入峰值被吸收成批量事务，单条 Span 写失败也不会阻塞接收链路。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;⑤ 实际例子&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个 LangChain RAG 应用收到提问&quot;这份合同里违约金怎么算&quot;：探针自动创建 Retriever Span（记录召回的 4 段文档与各自相似度分数）和 LLM Span（记录完整 prompt、temperature、输入输出 token 数），外层再挂一个 Chain Span 表示整次请求。导出后 Phoenix 入库，前端展示成一棵三层调用树。点开 LLM Span，你能看到实际发给模型的 prompt 里拼进了哪几段文档——答案错了，问题出在检索还是生成，一眼就能分辨。&lt;/p&gt;
&lt;h3&gt;4.2 Evaluation（LLM-as-judge 评估）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;① 功能作用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用 LLM 或程序化 evaluator 给 Span / Trace 打分（相关性、faithfulness、幻觉、毒性），结果作为 Annotation 附回 Trace。它回答的是&quot;这条回答到底好不好&quot;，把主观判断变成可筛选、可聚合的数值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 核心执行流程&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;通过 &lt;code&gt;arize-phoenix-evals&lt;/code&gt; 选择内置或自定义 evaluator（模板 prompt + LLM provider adapter）。&lt;/li&gt;
&lt;li&gt;从 Phoenix 拉取一批 Span（REST 接口或 DataFrame 导出）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;input_mapping&lt;/code&gt; 把嵌套的 Span 数据绑定到 evaluator 期望的字段（比如把召回文档映射成 &lt;code&gt;context&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;并发调用 judge LLM 批量评分，返回 label / score / explanation。&lt;/li&gt;
&lt;li&gt;评估结果作为 Span Annotation 写回 Phoenix，在 UI 与指标里呈现。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;③ 执行流程&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TB
    Spans[&quot;Phoenix 中的 Span&quot;] --&amp;gt;|&quot;导出&quot;| Eval[&quot;phoenix-evals 评估器&quot;]
    Map[&quot;input_mapping 字段绑定&quot;] --&amp;gt; Eval
    Eval --&amp;gt;|&quot;并发调用&quot;| Judge[&quot;Judge LLM&quot;]
    Judge --&amp;gt;|&quot;原始输出&quot;| Score[&quot;评估结果&amp;lt;br/&amp;gt;label / score / explanation&quot;]
    Score --&amp;gt;|&quot;作为 Annotation 写回&quot;| Server[&quot;Phoenix Server&quot;]
    Server --&amp;gt; UI[&quot;UI 与指标展示&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;④ 核心实现原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;核心是 &lt;strong&gt;Annotation 数据模型&lt;/strong&gt;：评估结果不与 Trace 混存，而是作为独立实体标注在 Span / Trace / Session 上。同一条 Span 可以同时挂人审结论、LLM 评分、用户点赞，互不覆盖，也能按来源区分。另一个机制是 &lt;strong&gt;adapter 层&lt;/strong&gt;：evaluator 只描述&quot;评什么、怎么评&quot;，provider 差异（OpenAI / Anthropic / 本地模型）与并发批处理由 adapter 屏蔽，换 judge 模型不用改写评估逻辑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑤ 实际例子&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对客服 RAG 应用当天 500 条 Trace 跑 hallucination evaluator：每条响应连同召回文档一起送进 judge prompt，产出 &lt;code&gt;faithful&lt;/code&gt; / &lt;code&gt;hallucinated&lt;/code&gt; 标签写回 Phoenix。然后在 UI 里按标签过滤，把被判为幻觉的 case 逐条排查——其中不少是检索召回了过期的旧版条款，问题不在模型，而在检索库。&lt;/p&gt;
&lt;h3&gt;4.3 Datasets &amp;amp; Experiments&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;① 功能作用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;把样例（input / 期望输出）存成版本化 Dataset，在其上运行 Experiment（换 Prompt、换模型、换代码），对比输出与评估分数。它解决的是&quot;改完之后有没有变好、有没有回归&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 核心执行流程&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用 phoenix-client 从 DataFrame / CSV / 线上 Trace 创建 Dataset，每个 example 含 input、reference output、metadata。&lt;/li&gt;
&lt;li&gt;Dataset 的修改产生新版本，实现内容版本控制。&lt;/li&gt;
&lt;li&gt;定义 task 函数（你的应用逻辑）与 evaluators，启动 Experiment。&lt;/li&gt;
&lt;li&gt;框架对每个 example 并发执行 task，执行过程自动打 Trace 到 Phoenix。&lt;/li&gt;
&lt;li&gt;结果、评估分数、Trace 关联存入 Experiment 记录，UI 并排对比多次实验。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;③ 执行流程&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant Dev as 开发者
    participant Task as task 函数
    participant API as REST API
    participant DB as 数据库

    Dev-&amp;gt;&amp;gt;API: 上传 Dataset（examples）
    API-&amp;gt;&amp;gt;DB: 保存 Dataset 版本
    Dev-&amp;gt;&amp;gt;Task: 启动 Experiment（dataset、task、evaluators）
    loop 每个 example 并发执行
        Task-&amp;gt;&amp;gt;Task: 执行应用逻辑（自动打 Trace）
        Task-&amp;gt;&amp;gt;API: Trace 上报
        Task--&amp;gt;&amp;gt;Dev: 返回 output
    end
    Dev-&amp;gt;&amp;gt;API: 提交结果与评估分数
    API-&amp;gt;&amp;gt;DB: 保存 Experiment Run + Annotation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;④ 核心实现原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;两个机制值得记住：&lt;strong&gt;Dataset 版本化&lt;/strong&gt;让实验可复现——Experiment 绑定的是某个确定的 Dataset 版本，而不是&quot;当时那份数据&quot;，否则一周后回看实验结论就站不住脚。**实验自追踪（tracing the experiment itself）**则让 Experiment 跑在自家 OTel 管线上：每条 example 的执行本身就是一条 Trace，某条输出错了，可以直接点进去看那次执行内部的检索与 prompt。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑤ 实际例子&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;把线上 50 条 bad case 导出成 Dataset，改完 Prompt 跑 v2 实验：50 条并发执行，每条都产生一条 Trace，judge 打分随结果一起入库。UI 并排对比 v1 / v2，看到 v2 修好了 38 条且没有新增回归；剩下 12 条点进 Trace，发现都是检索没召回到对应条款——说明下一轮该调的是切分策略，而不是继续改 Prompt。&lt;/p&gt;
&lt;h3&gt;4.4 Metrics Dashboard&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;① 功能作用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对摄入的 Trace 自动聚合指标：延迟分位、token 用量、成本估算、错误率。不需要手工定义指标或写查询，打开就有。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 核心执行流程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Span 摄入时携带 token 计数、模型名、延迟、错误状态等 OpenInference 属性 → 规范化入库 → 查询时对项目内 Trace 做聚合 → GraphQL 返回时间序列与分布 → React 仪表盘渲染预置面板（trace 量、延迟分位、USD 成本、按模型的 token 排行、Annotation 均分等）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 执行流程&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    Span[&quot;Span 摄入&amp;lt;br/&amp;gt;token / model / latency / error&quot;] --&amp;gt; Store[(&quot;Span 表&quot;)]
    Store --&amp;gt;|&quot;查询侧按项目聚合&quot;| GQL[&quot;GraphQL 聚合查询&quot;]
    GQL --&amp;gt; Dash[&quot;仪表盘预置面板&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;④ 核心实现原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;指标&lt;strong&gt;不在摄入时预聚合，而是查询侧实时聚合&lt;/strong&gt;。好处是不用维护物化视图或流式计算任务，新增一个维度只是改查询；代价是要求写入时的属性必须规范——token 计数、模型名、错误标记必须是结构化字段且口径统一，否则聚合会算错或算空。这也是 OpenInference 语义约定在数据链路上真正兑现价值的地方。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑤ 实际例子&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;某个周一仪表盘显示 p95 延迟从 1.8s 涨到 4.2s，同时某模型的 token 成本翻倍。按模型过滤 Span 后发现：新上的 Prompt 把 few-shot 示例从 2 个加到 6 个，输出没变长、输入长了一倍——延迟和成本其实是同一个原因。&lt;/p&gt;
&lt;h3&gt;4.5 PXI / Remote MCP（AI Agent 集成）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;① 功能作用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;PXI 是内置在 Phoenix 里的 AI 工程 Agent，用工具调用调查 Trace、迭代 Prompt、操作页面；&lt;code&gt;/mcp&lt;/code&gt; 端点则让 Claude Code、Cursor 等 MCP 客户端直接查询 Phoenix 里的 Trace、Dataset 与 Experiment。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 核心执行流程&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户在浏览器 Chat UI 提问（比如&quot;这条调用为什么失败&quot;）。&lt;/li&gt;
&lt;li&gt;服务端 Agent 组装 system prompt、skills 与当前页面可用的工具集，调用用户自己配置的 LLM（用户自带 API Key）。&lt;/li&gt;
&lt;li&gt;LLM 返回 tool call：查数据类走服务端沙箱 bash，通过 &lt;code&gt;phoenix-gql&lt;/code&gt; 执行 GraphQL 查询；页面操作类则下发到浏览器执行。&lt;/li&gt;
&lt;li&gt;工具结果回填给 LLM，循环直到得出结论，最终流式返回 UI。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;③ 执行流程&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    User((&quot;用户&quot;)) &amp;lt;--&amp;gt; ChatUI[&quot;浏览器 Chat UI&quot;]
    ChatUI &amp;lt;--&amp;gt; Agent[&quot;PXI Agent（服务端）&quot;]
    Agent --&amp;gt;|&quot;LLM 调用&quot;| LLM[&quot;LLM Provider&amp;lt;br/&amp;gt;用户自带 API Key&quot;]
    LLM --&amp;gt;|&quot;tool call&quot;| Agent
    Agent --&amp;gt; Bash[&quot;沙箱 bash&amp;lt;br/&amp;gt;phoenix-gql&quot;]
    Bash --&amp;gt; GQL[&quot;GraphQL API&quot;]
    Agent --&amp;gt;|&quot;页面操作指令&quot;| ChatUI
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;④ 核心实现原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;能力按上下文门控&lt;/strong&gt;：Agent 只暴露当前页面真正可执行的工具——在 Trace 详情页它能查这条 Trace，在 Dataset 页它才拿到 Dataset 相关工具。&lt;strong&gt;权限复用&lt;/strong&gt;：服务端沙箱 bash 里的 &lt;code&gt;phoenix-gql&lt;/code&gt; 用的是与登录用户相同的权限面，不为 Agent 单开一条高权限通道，沙箱本身也没有对外网络出口。两条合起来，既避免 Agent&quot;什么都能干&quot;带来的安全面扩张，也让模型不必在几十个工具里做选择。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑤ 实际例子&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在一条失败的 Trace 页面问 PXI&quot;为什么这条调用失败&quot;：Agent 加载 trace-debugging skill，用 GraphQL 查出该 Trace 的 Span 树，发现 LLM Span 状态是 &lt;code&gt;error&lt;/code&gt;、异常信息是限流（429），且重试在 3 次后放弃。回答按可能性排序给出根因假设与下一步动作，而不是把原始 Span 一股脑倒给你。&lt;/p&gt;
&lt;h2&gt;5. 值得借鉴的设计&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;站在开放标准上做差异化。&lt;/strong&gt; 摄入层完全采用 OTLP + OpenTelemetry，自定义价值只放在 OpenInference 语义约定（LLM 特有属性）上。结果是用户的 instrumentation 代码不被锁定，随时可以换后端。自建类似系统时，协议层优先复用行业标准，把创新压在&quot;这个领域特有的语义&quot;上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;摄入与落库解耦。&lt;/strong&gt; 双协议接收统一汇入异步 &lt;code&gt;BulkInserter&lt;/code&gt; 批量写入。&quot;队列 + 批量插入&quot;是遥测系统吸收写入峰值的通用解，代价是要接受秒级可见性延迟和少量丢失风险。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Annotation 作为一等公民。&lt;/strong&gt; 人审、LLM 评分、用户反馈统一建模为可附在 Span / Trace / Session 上的标注实体，而不是去改 Span 本身。扩展元数据时&quot;追加标注&quot;比&quot;修改事实数据&quot;干净得多：数据不可变、来源可追溯、多来源不打架。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;元可观测（self-tracing）。&lt;/strong&gt; Experiment 的运行本身走自家 OTel 管线，实验输出能直接回放成 Trace。这个设计让&quot;工具自己也在被工具观测&quot;，调试实验平台本身不需要另建一套日志。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent 能力上下文门控 + 权限复用。&lt;/strong&gt; PXI 的工具集随页面上下文动态收窄，沙箱查询复用登录用户的权限面。做产品内 Agent 时，这是兼顾安全与体验的实用范式：不给 Agent 更多权限，只给它更少但更准的工具。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;6. 总结&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;核心功能&lt;/th&gt;
&lt;th&gt;核心执行流程&lt;/th&gt;
&lt;th&gt;关键实现机制&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tracing&lt;/td&gt;
&lt;td&gt;OTel 探针 → OTLP（gRPC 4317 / HTTP 6006）→ BulkInserter → DB → GraphQL / UI&lt;/td&gt;
&lt;td&gt;OTLP 双通道、OpenInference 语义、异步批量写入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evaluation&lt;/td&gt;
&lt;td&gt;拉取 Span → input_mapping 绑定字段 → judge LLM 并发评分 → 写回 Annotation&lt;/td&gt;
&lt;td&gt;evals adapter 层、Annotation 数据模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Datasets &amp;amp; Experiments&lt;/td&gt;
&lt;td&gt;Dataset 版本化 → 并发跑 task → 自追踪 → 结果与分数入库对比&lt;/td&gt;
&lt;td&gt;数据版本控制、元可观测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metrics&lt;/td&gt;
&lt;td&gt;Span 属性规范化 → 查询侧实时聚合 → 仪表盘&lt;/td&gt;
&lt;td&gt;实时聚合、预置面板&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PXI / MCP&lt;/td&gt;
&lt;td&gt;Chat → Agent 编排 → tool call（服务端 GraphQL 沙箱 / 浏览器页面操作）→ 流式返回&lt;/td&gt;
&lt;td&gt;上下文门控、权限复用、沙箱执行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;一句话收尾&lt;/strong&gt;：Phoenix 没有发明新的可观测性范式，它做的是把 LLM 应用特有的语义（prompt、召回文档、token）钉进 OpenTelemetry 这套成熟标准里，然后在 Trace 之上长出一个评估与实验的工作台。这也是它最值得抄的地方——&lt;strong&gt;先用标准解决&quot;数据怎么进来&quot;，再把精力花在&quot;这个领域特有的数据长什么样&quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;口径说明&lt;/strong&gt;：Tracing 与 PXI / MCP 两条链路的组件划分与执行顺序，来自官方文档与公开的架构说明；Evaluation、Datasets &amp;amp; Experiments 的服务端实现细节（如 online evals 的调度策略、Playground 的流式执行）可查资料覆盖较少，文中按官方文档描述的功能行为整理，未逐文件核对源码，粒度上做了保守处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;这套梳理是怎么写出来的&lt;/strong&gt;：用的是《&lt;a href=&quot;/posts/prompt-deepwiki-framework-analysis/&quot;&gt;基于DeepWiki梳理开源框架核心流程&lt;/a&gt;》这份提示词——固定六个章节，强制把&quot;功能怎么运转&quot;讲到从输入到输出的每一步。想拿它拆别的开源项目，直接复制那一篇。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>基于DeepWiki梳理开源框架核心流程</title><link>https://aicooleo.cn/blog/posts/prompt-deepwiki-framework-analysis/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/prompt-deepwiki-framework-analysis/</guid><description>一套可直接复制的提示词：让 Agent 基于 README、官方文档与源码，把陌生开源项目拆成架构、核心流程、底层原理与可借鉴设计。</description><pubDate>Tue, 20 Oct 2026 09:30:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：这是一套&quot;让 Agent 替你把一个陌生开源项目读明白&quot;的提示词——固定六个章节，强制它讲清架构、核心功能从输入到输出的完整流程、底层机制，以及哪些设计值得抄。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;什么时候用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;初学一个开源项目&lt;/strong&gt;：README 和文档翻完了，还是不知道它内部怎么跑——先让它把整体架构和一次请求的完整链路讲清楚，再决定读不读源码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开源方案选型&lt;/strong&gt;：几个候选的功能列表看起来都差不多，需要对比真实的核心流程、组件边界和实现代价，而不是比谁的特性写得长。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;写笔记 / 做内部分享&lt;/strong&gt;：需要一份口径统一、带架构图和流程图的框架拆解，而不是一堆散落的链接。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;它面向的是&lt;strong&gt;公开的开源仓库&lt;/strong&gt;：模型要靠 README、官方文档、DeepWiki 页面和公开源码来还原流程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前置条件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;项目有可读的 README 与官方文档；如果配了 DeepWiki 页面（&lt;code&gt;deepwiki.com/&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;&lt;/code&gt;），效果最好，模型能拿到结构化的仓库摘要。&lt;/li&gt;
&lt;li&gt;环境能渲染 Mermaid（GitHub、GitLab、语雀、Notion、Obsidian 都行）；不能渲染的话，让它把图改成表格或分层文字。&lt;/li&gt;
&lt;li&gt;预期产出 2000～4000 字。太长会注水，太短讲不清流程。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;提示词&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 基于DeepWiki梳理开源框架核心流程

&amp;gt; 开源项目核心功能与实现流程分析

你是一名资深软件架构师，擅长将复杂开源项目的设计原理和核心实现流程讲解清楚。

请基于当前 GitHub 项目的 README、官方文档及必要的源码，生成一份《开源项目核心功能与实现流程》Markdown 文档。

**重点关注（可选）**：自动识别项目最重要的核心功能。

## 一、分析目标

我是一名有实际开发经验的工程师，希望快速了解一个开源项目的核心设计，而不是逐行研究源码。

请重点回答以下问题：

- 这个项目是什么？解决了什么问题？适合什么场景？
- 它有哪些核心功能？每个功能是用来做什么的？
- 整体架构是什么？主要由哪些组件组成，它们如何协作？
- 核心功能如何实现？从输入到输出，内部经历了哪些关键步骤？
- 底层采用了哪些关键技术或机制？这些技术在流程中发挥什么作用？
- 有哪些值得借鉴的设计？如果自己实现类似功能，可以参考什么思路？

最重要的是第 4 点：**讲清楚功能背后的完整执行流程**。

## 二、输出结构

### 1. 项目简介

用通俗、简洁的语言说明：

- 项目定位及解决的核心问题。
- 典型应用场景。
- 与同类方案相比的主要特点（有依据时）。

不要展开项目历史、安装教程等无关内容。

### 2. 核心功能概览

识别项目最重要的 3～6 个核心功能，用表格说明：

| 核心功能 | 功能说明 | 典型使用场景 |
| --- | --- | --- |

只保留重要功能，不需要罗列所有特性。

### 3. 整体架构

使用 Mermaid flowchart 绘制一张简洁的整体架构图。

要求：

- 只展示主要组件，不要绘制类、函数、文件。
- 标注组件职责和主要数据流向。
- 解释各组件之间如何协作。
- 尽量控制在 5～10 个主要组件以内。

### 4. 核心功能实现流程（最重要）

针对每个核心功能，按照以下结构分析：

#### 4.X 功能名称

**① 功能作用**

用 2～3 句话说明这个功能解决什么问题，以及在什么情况下使用。

**② 核心执行流程**

按照实际执行顺序，用 5～8 个步骤描述：

- 用户或外部系统如何触发该功能。
- 请求或数据如何进入系统。
- 哪些核心组件参与处理。
- 内部通过什么机制完成关键操作。
- 数据如何在组件之间传递。
- 必要时如何存储、查询或更新。
- 最终如何生成结果并返回。

根据实际功能灵活调整，不必强行凑齐所有步骤。

**③ Mermaid 流程图或时序图**

绘制一张清晰的流程图，帮助直观理解执行链路。

要求：

- 以核心组件和数据流为主。
- 不包含具体类名、函数名。
- 必要时区分同步和异步处理。
- 不展示无关的实现细节。

**④ 核心实现原理**

解释该流程最关键的 1～3 个技术机制。

重点回答：

- 底层依赖什么技术或协议？
- 关键数据是怎么处理的？
- 为什么能够实现这个功能？

例如分析链路追踪系统时，可能需要解释 Span、Trace、上下文传播、数据采集与存储等机制。

不要泛泛地介绍技术概念，要结合当前项目的实际实现讲解。

**⑤ 举一个实际例子**

用一个简单、贴近真实业务的例子，将整个流程串联起来，让读者能够理解这个功能实际运行时发生了什么。

### 5. 值得借鉴的设计

总结项目最值得学习的 3～5 个设计思路。

重点说明：

- 采用了什么设计。
- 解决了什么问题。
- 如果自己实现类似系统，可以如何借鉴。

只讨论有实际依据的设计，不进行空泛评价。

### 6. 总结

最后用一张表格汇总：

| 核心功能 | 核心执行流程 | 关键实现机制 |
| --- | --- | --- |

帮助读者快速回顾项目最重要的内容。

## 三、输出要求

- 全部使用中文，技术术语保留英文原名。
- 输出一份结构清晰的 Markdown 文档。
- 重点讲解「功能是什么 → 如何运转 → 底层原理 → 实际例子」。
- 不需要介绍具体类名、函数名、源码路径或详细代码。
- 可以通过源码验证实现机制，但不要把源码分析过程写进文档。
- 多使用 Mermaid 流程图、时序图帮助理解。
- 流程必须符合项目真实实现，不能凭空编造。
- 如果某个实现细节无法确认，明确说明。
- 避免重复解释同一个技术概念。
- 假设读者具有工程开发经验，不需要解释基础编程概念。
- 语言简洁直观，避免学术化、过度抽象的表达。
- 优先保证核心流程的完整性和准确性，不追求文档篇幅。
- 每条核心流程尽量控制在一个主要图表和若干段简洁解释内。

**最终目标**：让我用较短时间理解这个开源项目有什么用、整体如何设计、核心功能如何运转，以及有哪些值得借鉴的技术思路，而不需要深入阅读源码。

请开始分析并输出 Markdown 文档。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;用的时候注意几点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;先跑一遍&quot;项目简介&quot;，再补流程。&lt;/strong&gt; 模型容易一上来就写细节，导致第 1、2 章又空又飘。可以先让它只输出第 1～3 章，确认定位和架构没错，再让它展开第 4 章。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&quot;不写类名函数名&quot;是这条提示词最有效的约束。&lt;/strong&gt; 一旦解除，模型会退化成&quot;源码走读&quot;，满屏 &lt;code&gt;xxx.py 第 120 行&lt;/code&gt;，对建立整体认知没帮助。真需要代码，让它另开一节附录。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;强制&quot;无法确认就说无法确认&quot;。&lt;/strong&gt; 开源项目的文档常常落后于代码，模型会下意识编流程。留出这句免责，比事后逐条核实省时间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;图表要它自己校验。&lt;/strong&gt; 让模型在输出后自查一遍 Mermaid 语法（节点 id 不要用中文、边标签用引号包起来），能省掉一半渲染失败。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拿真实业务串一遍。&lt;/strong&gt; 第 ⑤ 条的&quot;实际例子&quot;是最容易被写成套话的地方，可以直接指定场景：比如&quot;用一个客服 RAG 应用举例&quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;一个真实产出&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/posts/phoenix-core-flows/&quot;&gt;Phoenix 核心功能与实现流程&lt;/a&gt; 就是用这套提示词梳理出来的：一个 LLM 可观测平台，六个章节、五张图，重点展开 OTLP 双通道摄入、Annotation 评估、数据集实验和 PXI Agent 四条链路。可以拿去对照效果。&lt;/p&gt;
</content:encoded></item><item><title>多 Agent 官方文档中文精讲（LangChain · Multi-agent）</title><link>https://aicooleo.cn/blog/posts/multiagent-overview/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-overview/</guid><description>把 LangChain 官方文档 Multi-agent 板块的 10 篇英文文档（6 篇概念 + 4 篇实战教程），整理成 12 篇中文图文笔记 + 2 篇实测附录 + 12 张官方配图。目标很具体：读完能回答&quot;我这个需求该用哪种多 A…</description><pubDate>Tue, 01 Sep 2026 23:41:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：把 LangChain 官方文档 &lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent&quot;&gt;Multi-agent&lt;/a&gt; 板块的 &lt;strong&gt;10 篇英文文档&lt;/strong&gt;（6 篇概念 + 4 篇实战教程），整理成 &lt;strong&gt;12 篇中文图文笔记 + 2 篇实测附录 + 12 张官方配图&lt;/strong&gt;。目标很具体：&lt;strong&gt;读完能回答&quot;我这个需求该用哪种多 Agent 模式、为什么、代价多少&quot;&lt;/strong&gt;，而不是背下五个名词。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;怎么读（推荐顺序）&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;第一步：建立判断力（半天）
  01 为什么需要多 Agent  ──▶  02 五种模式总览与选型  ──▶  03 性能对比（调用次数与 Token）
                                    │
第二步：按需深入某一模式（每篇 20~30 分钟）              │
  04 Subagents  05 Handoffs  06 Skills  07 Router  08 Custom workflow
                                    │
第三步：看别人怎么落地（想抄作业时读）                   ▼
  09 个人助理（Subagents） 10 客服工单（Handoffs） 11 SQL 助手（Skills） 12 多源知识库（Router）
                                    │
第四步：换成 Deep Agents 落地（API 与工程实现）           ▼
  deepagents/01 静态子智能体  ──▶  deepagents/03 异步子智能体  ──▶  deepagents/02 动态子智能体
                                    │
第五步（可选）：读实测附录，把&quot;官方数字/机制&quot;钉死
  13 为什么是 4 次调用（抓包）  14 手写 Tool 包装 vs Deep Agents 内置 subagents（源码 + 抓包）
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;只想快速选型&lt;/strong&gt;：读 &lt;code&gt;01&lt;/code&gt; → &lt;code&gt;02&lt;/code&gt; → &lt;code&gt;03&lt;/code&gt; 就够，三篇加起来大约一小时。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;已经知道要用哪种模式&lt;/strong&gt;：直接跳到 &lt;code&gt;04&lt;/code&gt;–&lt;code&gt;08&lt;/code&gt; 里对应的那篇，每篇都是&quot;说人话 + 代码 + 决策表&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;准备动手写&lt;/strong&gt;：&lt;code&gt;09&lt;/code&gt;–&lt;code&gt;12&lt;/code&gt; 是官方教程的提炼版（架构思路 + 关键代码 + 踩坑点），完整长代码指向原文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;决定用 Deep Agents 落地&lt;/strong&gt;：进 &lt;a href=&quot;/posts/deepagents-overview/&quot;&gt;&lt;code&gt;deepagents/&lt;/code&gt;&lt;/a&gt; 子板块——那里是同一套模式的 &lt;strong&gt;API、默认值与坑&lt;/strong&gt;（01 静态子智能体 → 03 异步 → 02 动态编排）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不信&quot;官方说 N 次调用&quot;&lt;/strong&gt;：看 &lt;code&gt;13&lt;/code&gt;、&lt;code&gt;14&lt;/code&gt; 两篇实测附录，里面有原始 HTTP 报文与源码证据。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;文件索引&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;讲什么&lt;/th&gt;
&lt;th&gt;对应原文&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-01-why/&quot;&gt;01 为什么需要多 Agent&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;把&quot;我要多 Agent&quot;翻译成三种真实诉求；什么时候&lt;strong&gt;不该&lt;/strong&gt;上多 Agent&lt;/td&gt;
&lt;td&gt;Multi-agent（Why multi-agent?）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-02-patterns/&quot;&gt;02 五种模式总览与选型&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;五种模式认脸 + 四维能力打分 + 中文决策树（含 4 张官方示意图）&lt;/td&gt;
&lt;td&gt;Multi-agent（Patterns / Choosing）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03 性能对比：调用次数与 Token&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;三个场景的完整数字账（一次性 / 重复请求 / 多领域），含 8 张官方配图 + &lt;strong&gt;重复请求场景的 4 张两轮时序图&lt;/strong&gt;（谁被重复付钱）&lt;/td&gt;
&lt;td&gt;Multi-agent（Performance comparison）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;04 Subagents 子智能体&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;主管把子智能体当工具；同步/异步、两种工具模式、三处上下文阀门&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/subagents&quot;&gt;subagents&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;05 Handoffs 交接&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;状态驱动行为切换；单 Agent + 中间件 vs 多 Agent 子图&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/handoffs&quot;&gt;handoffs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-06-skills/&quot;&gt;06 Skills 技能&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;一个 Agent + 按需加载的&quot;说明书&quot;；渐进式披露的 token 账&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/skills&quot;&gt;skills&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;07 Router 路由&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;先分诊、并行分发（&lt;code&gt;Command&lt;/code&gt; / &lt;code&gt;Send&lt;/code&gt;）、再汇总；无状态 vs 有状态&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/router&quot;&gt;router&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-08-custom-workflow/&quot;&gt;08 Custom workflow 自定义工作流&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;用 LangGraph 自己画图；混合确定性逻辑与 Agent；RAG 三类节点实例&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/custom-workflow&quot;&gt;custom-workflow&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-09-case-subagents/&quot;&gt;09 实战：用 Subagents 搭个人助理&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;主管 + 日历/邮件子智能体；human-in-the-loop 审核&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/subagents-personal-assistant&quot;&gt;subagents-personal-assistant&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-10-case-handoffs/&quot;&gt;10 实战：用 Handoffs 搭客服工单流转&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;保修 → 分类 → 方案的状态机式流转；退回上一步&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/handoffs-customer-support&quot;&gt;handoffs-customer-support&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-11-case-skills/&quot;&gt;11 实战：用 Skills 搭 SQL 助手&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;按需加载表结构与业务规则；用 state 加只读约束&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/skills-sql-assistant&quot;&gt;skills-sql-assistant&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-12-case-router/&quot;&gt;12 实战：用 Router 搭多源知识库&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;GitHub / Notion / Slack 并行检索 + &lt;code&gt;Send&lt;/code&gt; 扇出 + reducer 汇总&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/router-knowledge-base&quot;&gt;router-knowledge-base&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-13-appendix-4-calls/&quot;&gt;13 附录：Subagents 为什么是 4 次调用（抓包实录）&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;DeepSeek + mitmproxy 真跑一遍：4 次调用各自看到什么、原始报文、第 4 次买到什么&lt;/td&gt;
&lt;td&gt;&lt;code&gt;03&lt;/code&gt; 篇的实测附录（脚本与抓包在 &lt;a href=&quot;/posts/multiagent-demo/&quot;&gt;&lt;code&gt;demo/&lt;/code&gt;&lt;/a&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-14-appendix-tool-wrap/&quot;&gt;14 附录：手写 Tool 包装 vs Deep Agents 内置 subagents&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;源码 + 抓包对照：&lt;code&gt;create_deep_agent(subagents=...)&lt;/code&gt; 到底变成了什么（一个 &lt;code&gt;task&lt;/code&gt; 工具 + 注册表），和手写 &lt;code&gt;@tool&lt;/code&gt; 包装的 9 项差异&lt;/td&gt;
&lt;td&gt;&lt;code&gt;04&lt;/code&gt; 篇「单一派发工具」的实测对照&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-15-appendix-dup-rate/&quot;&gt;15 附录：重复请求占比怎么统计&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;这个数&lt;strong&gt;没有现成字段&lt;/strong&gt;：先定义&quot;什么叫重复&quot;，三种取数口径（业务主键 / 域标签 / 离线分类）、可套用的 SQL 模板、该报告的 5 个数、换算成钱与延迟、6 个坑&lt;/td&gt;
&lt;td&gt;&lt;code&gt;03&lt;/code&gt; 篇&quot;量三件事&quot;的方法论补充&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-16-task-split/&quot;&gt;16 主 Agent 的任务拆分与串并行决策&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;直接回答两件事：&lt;strong&gt;拆分怎么做&lt;/strong&gt;（两级拆分 + 子任务契约 + 模型/代码分工）、&lt;strong&gt;串并行怎么决定&lt;/strong&gt;（数据依赖为主判据 + 五步判定流程 + 六条判据 + 完整例子 + 落地要点与四个观测指标）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;04&lt;/code&gt;/&lt;code&gt;07&lt;/code&gt;/&lt;code&gt;deepagents/02&lt;/code&gt; 的落地视角总结&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;说明：官方总览页内容量大（含 12 张配图），本笔记&lt;strong&gt;拆成 01 / 02 / 03 三篇&lt;/strong&gt;以便阅读；对应关系见上表。&lt;code&gt;13&lt;/code&gt;–&lt;code&gt;16&lt;/code&gt; 不是官方文档：&lt;code&gt;13&lt;/code&gt;/&lt;code&gt;14&lt;/code&gt; 是实测附录（验证调用次数、对照工具模式），&lt;code&gt;15&lt;/code&gt; 是方法论附录（重复请求占比怎么量），&lt;code&gt;16&lt;/code&gt; 是任务拆分与串并行的落地说明。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;子板块：Deep Agents 多 Agent 文档（&lt;code&gt;deepagents/&lt;/code&gt;）&lt;/h3&gt;
&lt;p&gt;上面 01–12 讲的是 LangChain 官方 &lt;strong&gt;Multi-agent 板块&lt;/strong&gt;（模式与选型）；&lt;strong&gt;Deep Agents 也有一套多 Agent 文档，讲的是同一套模式在它里面的 API 与工程实现&lt;/strong&gt;。三篇笔记收在子目录里，编号独立从 01 开始：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;一句话&lt;/th&gt;
&lt;th&gt;对应原文&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/deepagents-overview/&quot;&gt;deepagents/README&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;子板块导读：与上层笔记的分工、三者怎么选、&lt;strong&gt;本机 &lt;code&gt;deepagents 0.7.5&lt;/code&gt; 可用性矩阵&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;deepagents/01 Subagents&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;静态子智能体：&lt;code&gt;SubAgent&lt;/code&gt; 全字段（含 &lt;code&gt;tools&lt;/code&gt;/&lt;code&gt;skills&lt;/code&gt;/&lt;code&gt;permissions&lt;/code&gt; 的继承 vs 覆盖）、默认 &lt;code&gt;general-purpose&lt;/code&gt;、fork、流式、追踪、结构化输出&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/subagents&quot;&gt;deepagents/subagents&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/deepagents-02-dynamic/&quot;&gt;deepagents/02 Dynamic subagents&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;动态编排：用代码（解释器里的 &lt;code&gt;task()&lt;/code&gt;）代替模型一次次派活——分类分发 / 扇出汇总 / 对抗验证 / 生成筛选 / 锦标赛 / 循环到达标&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/dynamic-subagents&quot;&gt;deepagents/dynamic-subagents&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/deepagents-03-async/&quot;&gt;deepagents/03 Async subagents&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;后台子智能体：启动 / 轮询 / 中途干预 / 取消（五个工具）、生命周期、ASGI vs HTTP 传输、部署拓扑、排障&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/async-subagents&quot;&gt;deepagents/async-subagents&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;一句话分工：&lt;strong&gt;上层教你怎么选模式，子板块教你在 Deep Agents 里怎么用（字段、默认值、坑、版本门控）。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;术语中英对照（全文统一）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;英文&lt;/th&gt;
&lt;th&gt;本笔记译法&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Multi-agent&lt;/td&gt;
&lt;td&gt;多 Agent / 多智能体&lt;/td&gt;
&lt;td&gt;保持&quot;Agent&quot;不译，符合国内习惯&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supervisor&lt;/td&gt;
&lt;td&gt;主管（Agent）&lt;/td&gt;
&lt;td&gt;Subagents 模式里的中心 Agent；也译&quot;监督者&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subagent&lt;/td&gt;
&lt;td&gt;子智能体 / 子 Agent&lt;/td&gt;
&lt;td&gt;被包装成工具、供主管调用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handoff&lt;/td&gt;
&lt;td&gt;交接&lt;/td&gt;
&lt;td&gt;用工具调用转移控制权（OpenAI 提出的术语）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skill&lt;/td&gt;
&lt;td&gt;技能&lt;/td&gt;
&lt;td&gt;提示词驱动的专长，按需加载&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Router&lt;/td&gt;
&lt;td&gt;路由 / 分诊&lt;/td&gt;
&lt;td&gt;一次分类步骤 + 分发&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom workflow&lt;/td&gt;
&lt;td&gt;自定义工作流&lt;/td&gt;
&lt;td&gt;用 LangGraph 自己画图&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vertical&lt;/td&gt;
&lt;td&gt;垂直领域&lt;/td&gt;
&lt;td&gt;各自需要独立 Agent 的独立知识域&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context engineering&lt;/td&gt;
&lt;td&gt;上下文工程&lt;/td&gt;
&lt;td&gt;决定&quot;每个 Agent 看到什么&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Progressive disclosure&lt;/td&gt;
&lt;td&gt;渐进式披露&lt;/td&gt;
&lt;td&gt;用到才加载，省上下文&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stateless / Stateful&lt;/td&gt;
&lt;td&gt;无状态 / 有状态&lt;/td&gt;
&lt;td&gt;调用之间是否保留记忆&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reducer&lt;/td&gt;
&lt;td&gt;归并函数&lt;/td&gt;
&lt;td&gt;并行分支结果如何合并（LangGraph）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Send&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;并行分发（扇出）&lt;/td&gt;
&lt;td&gt;LangGraph API，运行时动态生成分支&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Command&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;状态更新 / 跳转指令&lt;/td&gt;
&lt;td&gt;LangGraph API，&lt;code&gt;update&lt;/code&gt; 改状态、&lt;code&gt;goto&lt;/code&gt; 跳节点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool&lt;/td&gt;
&lt;td&gt;工具&lt;/td&gt;
&lt;td&gt;保持&quot;工具&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Middleware&lt;/td&gt;
&lt;td&gt;中间件&lt;/td&gt;
&lt;td&gt;拦截并改写模型调用配置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checkpoint / Checkpointer&lt;/td&gt;
&lt;td&gt;检查点 / 检查点存储器&lt;/td&gt;
&lt;td&gt;让状态跨回合、跨进程存活&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interrupt&lt;/td&gt;
&lt;td&gt;中断&lt;/td&gt;
&lt;td&gt;暂停执行、向用户取信息或要审批&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human-in-the-loop&lt;/td&gt;
&lt;td&gt;人工介入&lt;/td&gt;
&lt;td&gt;人在关键节点确认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model calls&lt;/td&gt;
&lt;td&gt;模型调用次数&lt;/td&gt;
&lt;td&gt;&lt;code&gt;03&lt;/code&gt; 篇的核心指标之一&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens processed&lt;/td&gt;
&lt;td&gt;处理的 Token 总量&lt;/td&gt;
&lt;td&gt;&lt;code&gt;03&lt;/code&gt; 篇的另一个核心指标&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;官方配图清单（&lt;code&gt;images/&lt;/code&gt;，共 12 张）&lt;/h2&gt;
&lt;p&gt;图片均为&lt;strong&gt;官方原图&lt;/strong&gt;（图内标签是英文），笔记里配了中文图注与&quot;这张图在说什么&quot;的解读。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;出处&lt;/th&gt;
&lt;th&gt;用在&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pattern-subagents.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;总览页「Visual overview」&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-02-patterns/&quot;&gt;02&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pattern-handoffs.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-02-patterns/&quot;&gt;02&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pattern-skills.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-02-patterns/&quot;&gt;02&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pattern-router.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-02-patterns/&quot;&gt;02&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;oneshot-subagents.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;总览页「One-shot request」&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;oneshot-handoffs.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;oneshot-skills.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;oneshot-router.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multidomain-subagents.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;总览页「Multi-domain」&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multidomain-handoffs.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multidomain-skills.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multidomain-router.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03&lt;/a&gt;、&lt;a href=&quot;/posts/multiagent-12-case-router/&quot;&gt;12&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;除这些官方位图外，笔记里的流程图/时序图都是&lt;strong&gt;可编辑的 Mermaid&lt;/strong&gt;（Obsidian、GitHub、VS Code 都能直接渲染）或 ASCII 图。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;来源清单（10 篇原文）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;原文标题&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Multi-agent（总览）&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/langchain/multi-agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Subagents&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/langchain/multi-agent/subagents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Handoffs&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/langchain/multi-agent/handoffs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Skills&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/langchain/multi-agent/skills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Router&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/langchain/multi-agent/router&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Custom workflow&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/langchain/multi-agent/custom-workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Build a personal assistant with subagents&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/langchain/multi-agent/subagents-personal-assistant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Build customer support with handoffs&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/langchain/multi-agent/handoffs-customer-support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Build a SQL assistant with on-demand skills&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/langchain/multi-agent/skills-sql-assistant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Build a multi-source knowledge base with routing&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/langchain/multi-agent/router-knowledge-base&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;延伸阅读（同源、不在本板块路径下）&lt;/strong&gt;：Deep Agents 内置了 subagents / skills / planning / 虚拟文件系统 / 上下文管理，想少写编排代码可以直接用它——&lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/overview&quot;&gt;Deep Agents 概览&lt;/a&gt; ｜ &lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/subagents&quot;&gt;Subagents&lt;/a&gt; ｜ &lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/skills&quot;&gt;Skills&lt;/a&gt;。另外 &lt;code&gt;langgraph-supervisor&lt;/code&gt; 包&lt;strong&gt;已不再维护&lt;/strong&gt;，官方给了&lt;a href=&quot;https://docs.langchain.com/oss/python/migrate/langgraph-supervisor&quot;&gt;迁移指南&lt;/a&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;加工说明（哪些是原文、哪些是笔记加的）&lt;/h2&gt;
&lt;p&gt;为了不误导读者，这里把改动说清楚：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;结构&lt;/strong&gt;：概念篇保留原文全部小节（Key characteristics / When to use / Basic implementation / 各设计决策 / Context engineering / Checkpointing 等）并逐节讲解；官方总览页拆成 &lt;code&gt;01&lt;/code&gt;/&lt;code&gt;02&lt;/code&gt;/&lt;code&gt;03&lt;/code&gt; 三篇。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码&lt;/strong&gt;：概念篇的短示例&lt;strong&gt;整段保留&lt;/strong&gt;，只把注释译成中文、删掉文档站特有的渲染标记（围栏上的主题参数、代码高亮指令注释等）；实战篇（&lt;code&gt;09&lt;/code&gt;–&lt;code&gt;12&lt;/code&gt;）采用&lt;strong&gt;提炼式精讲&lt;/strong&gt;——每步给 10–30 行关键代码骨架，上千行的完整示例只给原文链接，不整段复制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;补充解读&lt;/strong&gt;：凡标注 &lt;strong&gt;🧭 补充解读（非原文）&lt;/strong&gt; 的段落，都是本笔记结合工程经验加的（类比、取舍建议、成本直觉、与 Deep Agents 的关系等），&lt;strong&gt;不是官方说法&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原文瑕疵&lt;/strong&gt;：发现原文笔误或前后不一致时，保留原样并标注 &lt;strong&gt;⚠️ 原文如此&lt;/strong&gt;，不改写官方代码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;图片&lt;/strong&gt;：官方位图为原图直存（图内英文标签未改），中文图注用于补齐语义；自绘的流程图/时序图使用 Mermaid（节点标签中文化，关键术语中英并列）与 ASCII 图。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;未验证的部分&lt;/strong&gt;：笔记&lt;strong&gt;未在本机实际运行&lt;/strong&gt;任何示例代码，因此&lt;strong&gt;不承诺可直接跑通&lt;/strong&gt;；模型名（如 &lt;code&gt;gpt-5.5&lt;/code&gt;、&lt;code&gt;gemini-3.6-flash&lt;/code&gt;）沿用原文，落地时请替换为你可用的模型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;以原文为准&lt;/strong&gt;：文档会持续更新，遇到冲突请以官方文档为最终依据。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;学完自测（能答上来就算过关）&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&quot;我要上多 Agent&quot;这句话背后，通常对应哪三种真实诉求？&lt;/li&gt;
&lt;li&gt;五种模式里，哪一个支持&quot;用户直接跟专家对话&quot;？哪一个&lt;strong&gt;不&lt;/strong&gt;支持多跳？&lt;/li&gt;
&lt;li&gt;一次性请求里，Subagents 比 Handoffs 多出来的那一次调用花在哪？值不值？&lt;/li&gt;
&lt;li&gt;为什么多领域任务里 Skills 只调 3 次却最贵？这个结论对你的选型意味着什么？&lt;/li&gt;
&lt;li&gt;Subagents 的&quot;三处上下文阀门&quot;分别是什么？各自影响谁？&lt;/li&gt;
&lt;li&gt;Handoffs 的交接工具里，为什么必须补一条 &lt;code&gt;ToolMessage&lt;/code&gt;？&lt;/li&gt;
&lt;li&gt;Router 用 &lt;code&gt;Send&lt;/code&gt; 扇出后，结果为什么必须靠 reducer 合并？&lt;/li&gt;
&lt;li&gt;什么情况下应该放弃&quot;让模型决定路由&quot;，改用 Custom workflow？&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;答案都在对应篇章里；&lt;code&gt;03&lt;/code&gt; 篇的数字表建议反复看两遍——选型的一半功力在那张表上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;相邻主题&lt;/strong&gt;：Agent 应用开发 ｜ &lt;strong&gt;相关主题&lt;/strong&gt;：多 Agent 架构与业务流程梳理&lt;/p&gt;
</content:encoded></item><item><title>demo/ — Subagents 抓包实验（手写版 + Deep Agents 版）</title><link>https://aicooleo.cn/blog/posts/multiagent-demo/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-demo/</guid><description>配套笔记：</description><pubDate>Tue, 01 Sep 2026 21:19:00 GMT</pubDate><content:encoded>&lt;p&gt;配套笔记：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/multiagent-13-appendix-4-calls/&quot;&gt;&lt;code&gt;../13-附录-Subagents为什么是4次调用-抓包实录.md&lt;/code&gt;&lt;/a&gt; —— 手写 &lt;code&gt;@tool&lt;/code&gt; 包装版，4 次模型调用的报文解读&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/multiagent-14-appendix-tool-wrap/&quot;&gt;&lt;code&gt;../14-附录-手写Tool包装vsDeepAgents内置subagents.md&lt;/code&gt;&lt;/a&gt; —— 手写版 vs Deep Agents &lt;code&gt;subagents=&lt;/code&gt; 的逐项对照&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;文件清单&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;demo.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;手写版最小 Subagents 系统：&lt;strong&gt;主管&lt;/strong&gt;（1 个工具 &lt;code&gt;schedule_event&lt;/code&gt;）+ &lt;strong&gt;日历子智能体&lt;/strong&gt;（1 个工具 &lt;code&gt;create_calendar_event&lt;/code&gt;，桩函数）。跑一次用户请求会产生 4 次模型调用。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deepagents_demo.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deep Agents 版：&lt;code&gt;create_deep_agent(tools=[save_note], subagents=[research_subagent])&lt;/code&gt;。用来对照&quot;&lt;code&gt;subagents=&lt;/code&gt; 参数最终变成了什么&quot;。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;capture.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;mitmdump addon：把 &lt;code&gt;api.deepseek.com&lt;/code&gt; 的请求/响应正文逐条落成 JSONL（输出路径可用 &lt;code&gt;CAPTURE_OUT&lt;/code&gt; 指定）。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;summarize.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;把抓包 JSONL 汇总成&quot;第 N 次调用：请求了什么上下文 → 产出了什么&quot;的可读清单（兼容 Responses / Chat 两种协议）。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;captured.jsonl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;手写版实测抓包（4 条 HTTP 往返，16 KB，只含正文，&lt;strong&gt;不含请求头与密钥&lt;/strong&gt;）。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;captured_deepagents.jsonl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deep Agents 版实测抓包（4 条 HTTP 往返，约 107 KB——因为 &lt;code&gt;task&lt;/code&gt; + 7 个文件系统工具的 schema 都要随每次请求发送）。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;跑一遍&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 1) 模型配置（不要写进代码/仓库）
export CLOUD_MODEL_BASE_URL=https://api.deepseek.com
export CLOUD_MODEL_API_KEY=&amp;lt;你的 key&amp;gt;
export CLOUD_MODEL_NAME=deepseek-v4-flash-vision-exp
export CLOUD_MODEL_DEFAULT_REF=deepseek
export CLOUD_MODEL_PROVIDER=deepseek
export CLOUD_MODEL_PROTOCOL=RESPONSES      # 或 CHAT，两条路都验证过

# 2) 直接跑（不抓包）
python demo.py                 # 手写版
python deepagents_demo.py      # Deep Agents 版

# 3) 抓包跑（另开一个终端起代理）
python -m venv /tmp/mitmenv &amp;amp;&amp;amp; /tmp/mitmenv/bin/pip install mitmproxy
/tmp/mitmenv/bin/mitmdump -q -s capture.py -p 8888 --set confdir=/tmp/subagents-demo/.mitmproxy
HTTPS_PROXY=http://127.0.0.1:8888 SSL_CERT_FILE=/tmp/subagents-demo/.mitmproxy/mitmproxy-ca-cert.pem python demo.py
# Deep Agents 版换个输出文件，避免覆盖上一次抓包：
CAPTURE_OUT=$PWD/captured_deepagents.jsonl /tmp/mitmenv/bin/mitmdump -q -s capture.py -p 8888 --set confdir=/tmp/subagents-demo/.mitmproxy
HTTPS_PROXY=http://127.0.0.1:8888 SSL_CERT_FILE=/tmp/subagents-demo/.mitmproxy/mitmproxy-ca-cert.pem python deepagents_demo.py

# 4) 看结果
python summarize.py captured.jsonl
python summarize.py captured_deepagents.jsonl
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实测结果速览（2026-09-01）&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;手写版&lt;/strong&gt;（&lt;code&gt;demo.py&lt;/code&gt;，详见 &lt;code&gt;13&lt;/code&gt; 篇）：4 次调用 / 2227 in + 720 out tokens / 端到端约 5 秒。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;谁在调模型&lt;/th&gt;
&lt;th&gt;上下文&lt;/th&gt;
&lt;th&gt;产出&lt;/th&gt;
&lt;th&gt;in / out tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;主管&lt;/td&gt;
&lt;td&gt;system + 用户原话&lt;/td&gt;
&lt;td&gt;&lt;code&gt;function_call: schedule_event&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;347 / 75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;日历子智能体&lt;/td&gt;
&lt;td&gt;自己的 system + 转交的请求&lt;/td&gt;
&lt;td&gt;&lt;code&gt;function_call: create_calendar_event&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;398 / 584&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;日历子智能体&lt;/td&gt;
&lt;td&gt;上者 + 工具结果&lt;/td&gt;
&lt;td&gt;文本（回给主管）&lt;/td&gt;
&lt;td&gt;1020 / 30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;主管&lt;/td&gt;
&lt;td&gt;system + 用户原话 + 1 的调用 + 3 的结果&lt;/td&gt;
&lt;td&gt;文本（给用户）&lt;/td&gt;
&lt;td&gt;462 / 31&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Deep Agents 版&lt;/strong&gt;（&lt;code&gt;deepagents_demo.py&lt;/code&gt;，详见 &lt;code&gt;14&lt;/code&gt; 篇）：形状同构，但首轮上下文大得多。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;谁在调模型&lt;/th&gt;
&lt;th&gt;上下文&lt;/th&gt;
&lt;th&gt;产出&lt;/th&gt;
&lt;th&gt;in / out tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;主管&lt;/td&gt;
&lt;td&gt;system + 用户原话 + &lt;strong&gt;9 个工具的 schema&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;task(description, subagent_type=&quot;research-agent&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2622 / 466&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;调研子智能体&lt;/td&gt;
&lt;td&gt;只有自己的 system + 主管给的任务描述&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ls(path=&quot;/&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2536 / 841&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;调研子智能体&lt;/td&gt;
&lt;td&gt;上者 + &lt;code&gt;ls&lt;/code&gt; 结果&lt;/td&gt;
&lt;td&gt;调研结论（回给主管）&lt;/td&gt;
&lt;td&gt;3392 / 1473&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;主管&lt;/td&gt;
&lt;td&gt;system + 用户原话 + &lt;code&gt;task&lt;/code&gt; 调用 + 工具结果&lt;/td&gt;
&lt;td&gt;面向用户的回答&lt;/td&gt;
&lt;td&gt;4561 / 933&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;Deep Agents 的派活次数&lt;strong&gt;由模型决定&lt;/strong&gt;：同一份代码另一次运行里，主管连续调了 5 次 &lt;code&gt;task&lt;/code&gt;（12 条消息）。所以&quot;Deep Agents 一次请求等于几次模型调用&quot;没有固定答案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;环境说明&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;解释器：Python 3.11 + &lt;code&gt;langchain&lt;/code&gt; 1.x + &lt;code&gt;langchain-openai&lt;/code&gt; 1.x（文档里的 &lt;code&gt;create_agent&lt;/code&gt; / &lt;code&gt;@tool&lt;/code&gt; API）；Deep Agents 版另需 &lt;code&gt;deepagents&lt;/code&gt; 0.7.5。&lt;/li&gt;
&lt;li&gt;模型：请求名 &lt;code&gt;deepseek-v4-flash-vision-exp&lt;/code&gt;，接口回报的实际模型是 &lt;code&gt;deepseek-flash&lt;/code&gt;（&lt;code&gt;/v1/models&lt;/code&gt; 里只有 &lt;code&gt;deepseek-flash&lt;/code&gt;、&lt;code&gt;deepseek-v4-pro&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;协议：&lt;code&gt;/v1/responses&lt;/code&gt;（Responses API）与 &lt;code&gt;/v1/chat/completions&lt;/code&gt; 都能跑通工具调用；本仓库抓的是 Responses 协议。&lt;/li&gt;
&lt;li&gt;子智能体里的 &lt;code&gt;create_calendar_event&lt;/code&gt; 是&lt;strong&gt;桩函数&lt;/strong&gt;（不写真实日历），只为让&quot;子智能体自己也要调工具&quot;这一步真实发生。&lt;/li&gt;
&lt;li&gt;Deep Agents 版里 &lt;code&gt;research-agent&lt;/code&gt; &lt;strong&gt;没有&lt;/strong&gt;显式指定 &lt;code&gt;tools&lt;/code&gt;，所以它继承了主 Agent 的全部工具（文件系统工具 + &lt;code&gt;save_note&lt;/code&gt;）——这正是 &lt;code&gt;14&lt;/code&gt; 篇要提醒的默认值之一。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>03 Async subagents：后台跑的子智能体（启动、轮询、中途干预）</title><link>https://aicooleo.cn/blog/posts/deepagents-03-async/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/deepagents-03-async/</guid><description>同步子智能体像打电话——对方说完你才能走开；异步子智能体像派个同事去办事，先给你一张工单号，你该干嘛干嘛，随时能问一句&quot;到哪了&quot;，也能改需求，实在不行还能叫他先停下。</description><pubDate>Tue, 01 Sep 2026 16:22:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：同步子智能体像打电话——对方说完你才能走开；异步子智能体像派个同事去办事，先给你一张工单号，你该干嘛干嘛，随时能问一句&quot;到哪了&quot;，也能改需求，实在不行还能叫他先停下。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;这篇在系列里的位置&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;04 Subagents 模式原理&lt;/a&gt; 的&lt;a href=&quot;/posts/multiagent-04-subagents/#%E5%86%B3%E7%AD%96%E4%B8%80%E5%90%8C%E6%AD%A5%E8%BF%98%E6%98%AF%E5%BC%82%E6%AD%A5&quot;&gt;「决策一：同步还是异步」&lt;/a&gt; 讲的是&quot;同步 vs 异步&quot;这个&lt;strong&gt;概念&lt;/strong&gt;，以及通用的后台作业三工具模式（start / status / get result）；&lt;a href=&quot;/posts/multiagent-14-appendix-tool-wrap/&quot;&gt;14 底层机制与抓包&lt;/a&gt; 把手写 Tool 包装和内置 subagents 的差别拆开了；本目录 &lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 静态子智能体&lt;/a&gt; 讲静态（同步）子智能体的配置面，&lt;a href=&quot;/posts/deepagents-02-dynamic/&quot;&gt;02 动态子智能体&lt;/a&gt; 讲运行时动态生成子智能体。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;本篇负责的那一层&lt;/strong&gt;：如果 04 篇回答的是&quot;&lt;strong&gt;为什么要异步&lt;/strong&gt;&quot;，这篇回答&quot;&lt;strong&gt;Deep Agents 里到底怎么异步&lt;/strong&gt;&quot;——后台作业、长任务、并行工作流、中途干预（steering）与取消、传输方式（transport）与部署拓扑。概念在 04，API 在这儿。&lt;/p&gt;
&lt;h2&gt;它解决什么问题&lt;/h2&gt;
&lt;p&gt;原文的定位很直接：异步子智能体让&lt;strong&gt;主管（supervisor）启动一个立刻返回的后台任务&lt;/strong&gt;，于是主管可以在子智能体并发干活的同时继续和用户对话。主管随时能查进度、补指令、取消任务。&lt;/p&gt;
&lt;p&gt;判断标准就一句：&lt;strong&gt;主管需要结果才能往下走 → 用同步&lt;/strong&gt;（&lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 篇&lt;/a&gt; 的静态子智能体、&lt;a href=&quot;/posts/deepagents-02-dynamic/&quot;&gt;02 篇&lt;/a&gt; 的动态子智能体都是同步的）；&lt;strong&gt;用户不该干等、活儿又长 → 用异步&lt;/strong&gt;。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;情形&lt;/th&gt;
&lt;th&gt;结论&lt;/th&gt;
&lt;th&gt;原因&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;长任务（几分钟到几十分钟的调研、批量生成、长数据拉取）&lt;/td&gt;
&lt;td&gt;用异步&lt;/td&gt;
&lt;td&gt;同步会把对话冻住那么久&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;多个互不依赖的活儿&lt;/td&gt;
&lt;td&gt;用异步&lt;/td&gt;
&lt;td&gt;一起甩出去，真正并行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;要中途改方向 / 要能取消&lt;/td&gt;
&lt;td&gt;用异步&lt;/td&gt;
&lt;td&gt;只有异步有 &lt;code&gt;update&lt;/code&gt; / &lt;code&gt;cancel&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;状态要跨多轮对话活着&lt;/td&gt;
&lt;td&gt;用异步&lt;/td&gt;
&lt;td&gt;task ID 存在主管 graph 的专用通道里&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;一分钟内能跑完的小任务&lt;/td&gt;
&lt;td&gt;别用&lt;/td&gt;
&lt;td&gt;启动 + 轮询的额外往返比直接等还慢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;下一步动作依赖这个结果&lt;/td&gt;
&lt;td&gt;别用&lt;/td&gt;
&lt;td&gt;主管拿不到结果只能空转，异步变成忙等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;本地只想 &lt;code&gt;python xx.py&lt;/code&gt; 跑通&lt;/td&gt;
&lt;td&gt;别用&lt;/td&gt;
&lt;td&gt;调用要打到 Agent Protocol 服务器上，本机不一定有&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;注意&lt;/strong&gt;：这张&quot;用 / 别用&quot;表是按原文的对比表反推的判断口径，原文没有单独写&quot;什么时候不要用&quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;同步 vs 异步：原文那张对照表&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;同步子智能体 Sync&lt;/th&gt;
&lt;th&gt;异步子智能体 Async&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;执行模型 / 并发性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;主管阻塞，直到子智能体做完；并行但阻塞&lt;/td&gt;
&lt;td&gt;立刻返回作业 ID 并继续；并行且非阻塞&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;任务中更新&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;做不到&lt;/td&gt;
&lt;td&gt;用 &lt;code&gt;update_async_task&lt;/code&gt; 追加指令&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;取消&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;做不到&lt;/td&gt;
&lt;td&gt;用 &lt;code&gt;cancel_async_task&lt;/code&gt; 取消运行中的任务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;有状态性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;无状态，两次调用之间不留东西&lt;/td&gt;
&lt;td&gt;有状态，自己一条 thread 跨交互保持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;最适合&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;主管必须先等到结果才能继续的任务&lt;/td&gt;
&lt;td&gt;长跑、复杂、要在聊天里交互式管理的任务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;整体机制&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;graph TB
    U([&quot;用户 User&quot;]) --&amp;gt; S[&quot;主管 Supervisor&quot;]
    S --&amp;gt;|&quot;start_async_task 启动&quot;| J[&quot;作业 Job&amp;lt;br/&amp;gt;task_id 就是 thread_id&quot;]
    S --&amp;gt;|&quot;立刻回话，不阻塞&quot;| U
    J --&amp;gt;|&quot;Transport 传输方式&quot;| T{&quot;有没有 url?&quot;}
    T --&amp;gt;|&quot;没有 url → ASGI 传输&quot;| A[&quot;异步子智能体 A: researcher&amp;lt;br/&amp;gt;同进程里的独立 thread&quot;]
    T --&amp;gt;|&quot;有 url → HTTP 传输&quot;| B[&quot;异步子智能体 B: coder&amp;lt;br/&amp;gt;远端 Agent Protocol 服务器&quot;]
    S --&amp;gt;|&quot;check / update / cancel / list&quot;| T
    A -.-&amp;gt;|&quot;状态与结果&quot;| S
    B -.-&amp;gt;|&quot;状态与结果&quot;| S
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;① 主管（Supervisor）是&lt;strong&gt;唯一&lt;/strong&gt;和用户对话的人；异步子智能体（Async Subagent）不直接对用户说话，只对主管负责。&lt;/p&gt;
&lt;p&gt;② 主管启动一个作业（Job）时拿到的是&lt;strong&gt;一个 task_id 而不是结果&lt;/strong&gt;——task_id 就是服务器上的 thread_id，&quot;作业&quot;本质是一条独立会话线程。&lt;/p&gt;
&lt;p&gt;③ 主管后续所有动作（查状态、补指令、取消、列表）都经过传输方式（Transport）这一层：&lt;code&gt;url&lt;/code&gt; 空 → ASGI 传输走进程内调用，&lt;code&gt;url&lt;/code&gt; 有值 → HTTP 传输走网络。&lt;strong&gt;换传输方式不改业务代码，只改配置。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;时间轴：一次真实交互长什么样&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;时间往下走（主管视角）
T0  用户 → 主管: &quot;把这份合同审一下，顺便查下对手公司背景&quot;
T1  主管 → start_async_task(&quot;审合同&quot;, &quot;legal_reviewer&quot;)
                  └─ 立刻返回 task_id: 7f3a9c（= 服务器上的 thread_id）
T2  主管 → 用户: &quot;已开始审查（任务 ID 7f3a9c），你先忙别的&quot;  ← 交回控制权
T3  用户 → 主管: &quot;那先聊点别的&quot;   ← 同步子智能体永远到不了这一步
T4  用户 → 主管: &quot;审得怎么样了？&quot; → check_async_task(&quot;7f3a9c&quot;) → running
T5  用户 → 主管: &quot;方向改一下，重点看违约金条款&quot;
    主管 → update_async_task(&quot;7f3a9c&quot;, &quot;重点看违约金&quot;)   ← 中途干预
         （旧 run 被打断，带完整历史 + 新指令重启，task_id 不变）
T6  用户 → 主管: &quot;算了别审了&quot; → cancel_async_task(&quot;7f3a9c&quot;) → cancelled
T7  用户 → 主管: &quot;刚发的背景调研呢？&quot;
    主管 → check_async_task(&quot;b21e04&quot;) → success + result  ← 拿结果
T8  主管 → 用户: 把结果整理成人话汇报
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;和 04 篇的&quot;同步子智能体&quot;对照着看&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href=&quot;/posts/multiagent-04-subagents/#%E5%86%B3%E7%AD%96%E4%B8%80%E5%90%8C%E6%AD%A5%E8%BF%98%E6%98%AF%E5%BC%82%E6%AD%A5&quot;&gt;04 篇的同步子智能体&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;本篇的异步子智能体&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;一次调用的形态&lt;/td&gt;
&lt;td&gt;主管调 &lt;code&gt;task&lt;/code&gt;，&lt;strong&gt;等&lt;/strong&gt;返回值&lt;/td&gt;
&lt;td&gt;调 &lt;code&gt;start_async_task&lt;/code&gt;，拿 task_id &lt;strong&gt;立刻走&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;用户在等待期间&lt;/td&gt;
&lt;td&gt;什么都看不到，对话被冻住&lt;/td&gt;
&lt;td&gt;可以继续聊，也能再派新任务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;进度怎么来&lt;/td&gt;
&lt;td&gt;没有进度，只有最终结果&lt;/td&gt;
&lt;td&gt;&lt;code&gt;check_async_task&lt;/code&gt; 拉当前状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;改需求 / 收手&lt;/td&gt;
&lt;td&gt;只能撤了重开&lt;/td&gt;
&lt;td&gt;&lt;code&gt;update_async_task&lt;/code&gt; / &lt;code&gt;cancel_async_task&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;层次&lt;/td&gt;
&lt;td&gt;通用取舍（概念层）&lt;/td&gt;
&lt;td&gt;概念在 Deep Agents 的落地 API（实现层）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;04 篇那个&quot;三工具模式&quot;（start / status / get result）就是这套东西的极简版；Deep Agents 把它做成了 5 个工具 + 一个中间件 + 一个专用状态通道。&lt;/p&gt;
&lt;h2&gt;怎么配（Configure async subagents）&lt;/h2&gt;
&lt;p&gt;异步子智能体就是一份 &lt;code&gt;AsyncSubAgent&lt;/code&gt; 规格列表，每条指向一个 Agent Protocol 服务器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from deepagents import AsyncSubAgent, create_deep_agent

async_subagents = [
    AsyncSubAgent(              # 不写 url → ASGI 传输（和主管同一个部署里）
        name=&quot;researcher&quot;,
        description=&quot;Research agent for information gathering and synthesis&quot;,
        graph_id=&quot;researcher&quot;,
    ),
    AsyncSubAgent(              # 写了 url → HTTP 传输（远端部署）
        name=&quot;coder&quot;,
        description=&quot;Coding agent for code generation and review&quot;,
        graph_id=&quot;coder&quot;,
        # url=&quot;https://coder-deployment.langsmith.dev&quot;
    ),
]

agent = create_deep_agent(
    model=&quot;google_genai:gemini-3.6-flash&quot;, subagents=async_subagents
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键点：&lt;strong&gt;同一个 &lt;code&gt;subagents=&lt;/code&gt; 参数&lt;/strong&gt;，靠有没有 &lt;code&gt;url&lt;/code&gt; 决定这条规格走哪种传输方式，靠对象类型区分同步 / 异步。&lt;/p&gt;
&lt;p&gt;官方给这个例子准备了公开 trace：https://smith.langchain.com/public/0727618d-02b5-4e99-b7c7-79d6f37f234d/r&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;AsyncSubAgent&lt;/code&gt; 的字段&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;必填。唯一标识，主管启动任务时用它指名道姓。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;必填。这个子智能体干什么，主管靠它决定派谁去。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;graph_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;必填。Agent Protocol 服务器上的 graph ID（或 assistant ID）。LangGraph 部署里必须和 &lt;code&gt;langgraph.json&lt;/code&gt; 里注册的名字对上。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可选。&lt;strong&gt;不写 → ASGI 传输（进程内）；写了 → HTTP 传输打远端。&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;headers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict[str, str]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可选。给远端服务器带的自定义请求头，自建 Agent Protocol 服务器做认证时用。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;同部署时的 &lt;code&gt;langgraph.json&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;LangGraph 部署里，被同部署（co-deployed）的图都要注册在同一个 &lt;code&gt;langgraph.json&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;graphs&quot;: {
    &quot;supervisor&quot;: &quot;./src/supervisor.py:graph&quot;,
    &quot;researcher&quot;: &quot;./src/researcher.py:graph&quot;,
    &quot;coder&quot;: &quot;./src/coder.py:graph&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这不是可选项：ASGI 传输要求两边在同一份注册表里，否则主管按 &lt;code&gt;graph_id&lt;/code&gt; 找不到图。&lt;/p&gt;
&lt;h3&gt;和静态 &lt;code&gt;SubAgent&lt;/code&gt; 的差异（&lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 篇&lt;/a&gt; 对照）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;静态 &lt;code&gt;SubAgent&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;异步 &lt;code&gt;AsyncSubAgent&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;跑在哪&lt;/td&gt;
&lt;td&gt;主管进程内（内联子图）&lt;/td&gt;
&lt;td&gt;独立部署上的 Agent Protocol 服务器（或同进程 ASGI）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;主管怎么调&lt;/td&gt;
&lt;td&gt;&lt;code&gt;task&lt;/code&gt; 工具，同步阻塞&lt;/td&gt;
&lt;td&gt;5 个 &lt;code&gt;*_async_task&lt;/code&gt; 工具，非阻塞&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;必填字段&lt;/td&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;、&lt;code&gt;description&lt;/code&gt;、提示词/工具&lt;/td&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;、&lt;code&gt;description&lt;/code&gt;、&lt;code&gt;graph_id&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;提示词在哪配&lt;/td&gt;
&lt;td&gt;规格里直接写 &lt;code&gt;system_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;规格里没有这个字段&lt;/strong&gt;，提示词在远端那张图上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;状态&lt;/td&gt;
&lt;td&gt;无状态，一次调用一次结果&lt;/td&gt;
&lt;td&gt;有状态，自己一条 thread 跨交互保持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;中途改 / 取消&lt;/td&gt;
&lt;td&gt;都不支持&lt;/td&gt;
&lt;td&gt;&lt;code&gt;update_async_task&lt;/code&gt; / &lt;code&gt;cancel_async_task&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;前置条件&lt;/td&gt;
&lt;td&gt;无需额外环境&lt;/td&gt;
&lt;td&gt;需要一个&lt;strong&gt;能跑的 agent server / 部署环境&lt;/strong&gt;（这一篇最容易踩的坑）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;主管手里多了哪些工具&lt;/h2&gt;
&lt;p&gt;配置了异步子智能体后，Deep Agents stack 会自动包含 &lt;code&gt;AsyncSubAgentMiddleware&lt;/code&gt;，主管因此多出 5 个工具：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;工具&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;返回&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;start_async_task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;启动一个新的后台任务&lt;/td&gt;
&lt;td&gt;任务 ID（立刻返回）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check_async_task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;取任务的当前状态与结果&lt;/td&gt;
&lt;td&gt;状态 + 结果（若已完成）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;update_async_task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;给运行中的任务发新指令&lt;/td&gt;
&lt;td&gt;确认 + 更新后的状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cancel_async_task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;停掉运行中的任务&lt;/td&gt;
&lt;td&gt;确认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_async_tasks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;列出所有被跟踪的任务及其实时状态&lt;/td&gt;
&lt;td&gt;所有任务的摘要&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;主管的 LLM 像调其它工具一样调它们；&lt;strong&gt;建 thread、管 run、持久化状态都由 middleware 自动处理&lt;/strong&gt;（原文）——你不需要自己写&quot;任务注册表&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;入参长什么样&lt;/strong&gt;（原文只给了工具名和用途，这里按本机 0.7.5 源码的 schema 补充）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;工具&lt;/th&gt;
&lt;th&gt;入参&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;start_async_task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;（任务描述）、&lt;code&gt;subagent_type&lt;/code&gt;（必须是已注册的异步子智能体名）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check_async_task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;task_id&lt;/code&gt;（原样传 &lt;code&gt;start_async_task&lt;/code&gt; 返回的那个字符串）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;update_async_task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;task_id&lt;/code&gt;、&lt;code&gt;message&lt;/code&gt;（追加的指令或上下文）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cancel_async_task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;task_id&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_async_tasks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;status_filter&lt;/code&gt;：&lt;code&gt;running&lt;/code&gt; / &lt;code&gt;success&lt;/code&gt; / &lt;code&gt;error&lt;/code&gt; / &lt;code&gt;cancelled&lt;/code&gt; / &lt;code&gt;all&lt;/code&gt;（默认 &lt;code&gt;all&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;生命周期状态机&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;stateDiagram-v2
    [*] --&amp;gt; running: start_async_task 新建 thread + run，返回 task_id
    running --&amp;gt; running: update_async_task 打断旧 run，带历史 + 新指令重启
    running --&amp;gt; success: run 成功，check 时从 thread state 取最终输出
    running --&amp;gt; error: run 失败，返回错误信息
    running --&amp;gt; cancelled: cancel_async_task 调服务器 runs.cancel()
    success --&amp;gt; [*]
    error --&amp;gt; [*]
    cancelled --&amp;gt; [*]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;终态（terminal）就是&lt;strong&gt;不会再变&lt;/strong&gt;的状态。原文明确列了 &lt;code&gt;success&lt;/code&gt;、&lt;code&gt;error&lt;/code&gt;、&lt;code&gt;cancelled&lt;/code&gt;；本机 0.7.5 源码里这个集合是 &lt;code&gt;{cancelled, success, error, timeout, interrupted}&lt;/code&gt;——多出来的 &lt;code&gt;timeout&lt;/code&gt; / &lt;code&gt;interrupted&lt;/code&gt; 属于 SDK 侧状态，原文未提及。&lt;/p&gt;
&lt;h3&gt;五个动作在服务器上各自做了什么（原文逐个说明）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Launch（启动）&lt;/strong&gt;：在服务器上新建一条 thread，用任务描述作为输入起一个 run，&lt;strong&gt;把 thread ID 当 task ID 返回&lt;/strong&gt;。主管把 ID 报给用户，然后&lt;strong&gt;不去轮询等完成&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check（查询）&lt;/strong&gt;：拉当前 run 状态。run 成功了就再取一次 thread state 抽出子智能体的最终输出；还在跑就如实告诉用户还在跑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update（更新）&lt;/strong&gt;：在同一条 thread 上用 &lt;strong&gt;interrupt 多任务策略&lt;/strong&gt;起一个新 run。旧 run 被打断，子智能体带&lt;strong&gt;完整对话历史 + 新指令&lt;/strong&gt;重启，&lt;strong&gt;task ID 不变&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cancel（取消）&lt;/strong&gt;：在服务器上调 &lt;code&gt;runs.cancel()&lt;/code&gt;，并把任务标记为 &lt;code&gt;&quot;cancelled&quot;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;List（列表）&lt;/strong&gt;：遍历所有被跟踪任务；非终态任务&lt;strong&gt;并发&lt;/strong&gt;去服务器拉实时状态，终态任务直接返回缓存值（省一次网络往返）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;看第 3 条：&lt;code&gt;update&lt;/code&gt; 不是&quot;追加一条消息就完了&quot;，而是&lt;strong&gt;打断重启&lt;/strong&gt;。这解释了为什么干预后子智能体还记得前面聊过什么（历史在），也解释了为什么干预本身有成本（一次新 run）。&lt;/p&gt;
&lt;h2&gt;状态管理（Understand state management）&lt;/h2&gt;
&lt;p&gt;任务元数据存在主管 graph 上一个&lt;strong&gt;专用状态通道 &lt;code&gt;async_tasks&lt;/code&gt;&lt;/strong&gt; 里，&lt;strong&gt;和消息历史分开&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;为什么必须分开？因为 deep agents 在上下文窗口塞满时会&lt;strong&gt;压缩消息历史&lt;/strong&gt;（compaction / summarization）。如果 task ID 只存在于某条工具消息里，压缩之后就被摘要掉了，主管从此失忆——用户再问&quot;上次那个调研呢&quot;，主管答不上来，任务也取消不掉。专用通道保证&lt;strong&gt;无论压缩多少轮，主管都能通过 &lt;code&gt;list_async_tasks&lt;/code&gt; 想起自己派过哪些活&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;每个被跟踪的任务记录这些字段（原文）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;task ID / thread ID&lt;/td&gt;
&lt;td&gt;任务标识；本机源码里 task ID &lt;strong&gt;就等于&lt;/strong&gt; thread ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;agent name / run ID&lt;/td&gt;
&lt;td&gt;哪个异步子智能体在跑；该 thread 上当前这次执行的 run ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;status&lt;/td&gt;
&lt;td&gt;当前状态（&lt;code&gt;running&lt;/code&gt; / &lt;code&gt;success&lt;/code&gt; / &lt;code&gt;error&lt;/code&gt; / &lt;code&gt;cancelled&lt;/code&gt; 等）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;任务创建时间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;last_checked_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;最后一次通过 SDK 查状态的时间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;last_updated_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;状态发生变化、或通过 update 发了跟消息的时间&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;后两个时间戳分工挺讲究：&lt;code&gt;last_checked_at&lt;/code&gt; 记录&quot;你查过&quot;，&lt;code&gt;last_updated_at&lt;/code&gt; 记录&quot;它变过&quot;。想判断&quot;这个状态是不是老古董&quot;，看这两个字段就够了。&lt;/p&gt;
&lt;p&gt;和主管的关系可以概括成三句话：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态挂在主管的 graph 上&lt;/strong&gt;，不在子智能体那边——子智能体只管自己的 thread 和 run。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主管是唯一的读写方&lt;/strong&gt;：启动、查询、更新、取消都改这条通道，子智能体压根不知道有这张表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态随主管会话持久化&lt;/strong&gt;（配了 checkpointer 就落库），所以主管重启或换轮次后依然能接管之前派的活。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;选传输方式（Transport）&lt;/h2&gt;
&lt;h3&gt;ASGI 传输（同进程部署）&lt;/h3&gt;
&lt;p&gt;规格里&lt;strong&gt;不写 &lt;code&gt;url&lt;/code&gt;&lt;/strong&gt; 就用 ASGI 传输：LangGraph SDK 的调用被路由成&lt;strong&gt;进程内函数调用&lt;/strong&gt;，不走 HTTP。LangGraph 部署下要求两张图注册在同一个 &lt;code&gt;langgraph.json&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;原文给的结论：省掉网络延迟；不需要额外认证配置；&lt;strong&gt;子智能体仍是独立的一条 thread，有自己的状态&lt;/strong&gt;（不是&quot;变成一次函数调用就没状态了&quot;）；&lt;strong&gt;这是推荐的默认选择&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;HTTP 传输（远端）&lt;/h3&gt;
&lt;p&gt;加个 &lt;code&gt;url&lt;/code&gt; 字段就切到 HTTP 传输，SDK 调用走网络打到远端 Agent Protocol 服务器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from deepagents import AsyncSubAgent

AsyncSubAgent(
    name=&quot;researcher&quot;,
    description=&quot;Research agent&quot;,
    graph_id=&quot;researcher&quot;,
    url=&quot;https://my-research-deployment.langsmith.dev&quot;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;认证方面（原文）：LangGraph 部署由 SDK 用环境变量 &lt;code&gt;LANGSMITH_API_KEY&lt;/code&gt;（或 &lt;code&gt;LANGGRAPH_API_KEY&lt;/code&gt;）自动处理；自建的 Agent Protocol 服务器可能用别的认证机制。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;什么时候值得付这份网络和运维成本&lt;/strong&gt;：子智能体要独立扩缩容、资源画像不同（比如要 GPU）、或者干脆是别的团队维护的。&lt;/p&gt;
&lt;h3&gt;两种传输方式对照&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ASGI 传输&lt;/th&gt;
&lt;th&gt;HTTP 传输&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;触发条件 / 调用路径&lt;/td&gt;
&lt;td&gt;不写 &lt;code&gt;url&lt;/code&gt;；进程内函数调用&lt;/td&gt;
&lt;td&gt;写了 &lt;code&gt;url&lt;/code&gt;；网络请求&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;延迟&lt;/td&gt;
&lt;td&gt;无网络开销&lt;/td&gt;
&lt;td&gt;有一次网络往返&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;认证&lt;/td&gt;
&lt;td&gt;不需要额外配&lt;/td&gt;
&lt;td&gt;SDK 读 &lt;code&gt;LANGSMITH_API_KEY&lt;/code&gt; / &lt;code&gt;LANGGRAPH_API_KEY&lt;/code&gt;，或自建认证&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;前置要求&lt;/td&gt;
&lt;td&gt;同部署 + 同一份 &lt;code&gt;langgraph.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;远端有可用的 Agent Protocol 服务器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适用&lt;/td&gt;
&lt;td&gt;默认起步、本地和单部署&lt;/td&gt;
&lt;td&gt;独立扩缩容、异构资源、跨团队&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;警告（本机 0.7.5 源码观察，原文未说明）&lt;/strong&gt;：ASGI 传输（&lt;code&gt;url=None&lt;/code&gt;）&lt;strong&gt;只在异步调用路径上可用&lt;/strong&gt;。本机源码里，同步客户端拿到没有 &lt;code&gt;url&lt;/code&gt; 的规格会直接报错：&lt;code&gt;ASGI transport (url=None) requires async invocation&lt;/code&gt;。所以纯异步子智能体 + ASGI 的组合，要用 &lt;code&gt;ainvoke&lt;/code&gt; / &lt;code&gt;astream&lt;/code&gt; 这类异步入口跑主管，别用同步 &lt;code&gt;invoke&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示（本机 0.7.5 源码观察，原文未说明）&lt;/strong&gt;：HTTP 传输的请求头默认会补上 &lt;code&gt;x-auth-scheme: langsmith&lt;/code&gt;（除非你在 &lt;code&gt;headers&lt;/code&gt; 里自己给了）。自建服务器一般忽略它，真要覆盖就用 &lt;code&gt;headers&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;选部署拓扑（Deployment topology）&lt;/h2&gt;
&lt;h3&gt;单部署（Single deployment）&lt;/h3&gt;
&lt;p&gt;所有 agent 同部署、同服务器，用 ASGI 传输；LangGraph 部署下所有图注册进一份 &lt;code&gt;langgraph.json&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;单部署（推荐起步）
  一个服务器 / 一份 langgraph.json
    主管 supervisor
      ├─ ASGI 传输 ─▶ researcher   （同进程，独立 thread + 独立状态）
      └─ ASGI 传输 ─▶ coder        （同进程，独立 thread + 独立状态）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个服务器管到底，零网络延迟；缺点是大家抢同一份资源，谁把 worker 占满大家一起排队。&lt;/p&gt;
&lt;h3&gt;拆分部署（Split deployment）&lt;/h3&gt;
&lt;p&gt;主管一台服务器，子智能体另一台，用 HTTP 传输。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;拆分部署
  服务器 A：主管  ──HTTP + 认证──▶  服务器 B：researcher / coder
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;适合：子智能体需要不同的计算画像（CPU 密集 / GPU / 内存大户），或者要独立扩缩容。&lt;/p&gt;
&lt;h3&gt;混合（Hybrid）&lt;/h3&gt;
&lt;p&gt;一部分子智能体同部署走 ASGI，另一部分远端走 HTTP：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from deepagents import AsyncSubAgent

async_subagents = [
    AsyncSubAgent(          # 不写 url → ASGI（同部署）
        name=&quot;researcher&quot;, description=&quot;Research agent&quot;,
        graph_id=&quot;researcher&quot;,
    ),
    AsyncSubAgent(          # 写了 url → HTTP（远端）
        name=&quot;coder&quot;, description=&quot;Coding agent&quot;,
        graph_id=&quot;coder&quot;, url=&quot;https://coder-deployment.langsmith.dev&quot;,
    ),
]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;混合部署
  用户 ─▶ 主管（服务器 A）
            ├─ ASGI ─▶ researcher   同进程：轻量、频繁调用
            └─ HTTP ─▶ coder        远端：重资源、独立扩缩容
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;官方混合部署示例的公开 trace：https://smith.langchain.com/public/dddcb1d1-92ac-468b-aa34-5d9070661aa1/r&lt;/p&gt;
&lt;p&gt;原文的推荐路线很清楚：&lt;strong&gt;从单部署开始&lt;/strong&gt;（一个服务器管到底、零网络延迟），等真出现资源或组织边界了再拆：拆分部署适合子智能体资源画像差异大、要独立扩缩容的场景（代价是多一套部署 + 认证 + 网络延迟）；混合适合大部分子智能体轻量、个别重的场景（代价是配置更碎）。&lt;/p&gt;
&lt;h2&gt;本机可用性实测（deepagents 0.7.5）&lt;/h2&gt;
&lt;p&gt;结论先给：&lt;strong&gt;这一篇的能力在本机可用，API 已经导出&lt;/strong&gt;——但&lt;strong&gt;能不能真跑起来取决于有没有能跑的 agent server / 部署环境&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;已核实的事实（本机 &lt;code&gt;deepagents 0.7.5&lt;/code&gt;）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;from deepagents import AsyncSubAgent, AsyncSubAgentMiddleware&lt;/code&gt; &lt;strong&gt;可用&lt;/strong&gt;，两个符号都已导出。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AsyncSubAgent&lt;/code&gt; 的字段是 &lt;code&gt;name&lt;/code&gt; / &lt;code&gt;description&lt;/code&gt; / &lt;code&gt;graph_id&lt;/code&gt; / &lt;code&gt;url&lt;/code&gt; / &lt;code&gt;headers&lt;/code&gt;，和原文表格一致。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create_deep_agent(model=..., subagents=[AsyncSubAgent(...)])&lt;/code&gt; &lt;strong&gt;能构造出 agent&lt;/strong&gt;；工具里确实有 &lt;code&gt;start_async_task&lt;/code&gt;、&lt;code&gt;check_async_task&lt;/code&gt;、&lt;code&gt;update_async_task&lt;/code&gt;、&lt;code&gt;cancel_async_task&lt;/code&gt;、&lt;code&gt;list_async_tasks&lt;/code&gt; 五个；状态通道名 &lt;code&gt;async_tasks&lt;/code&gt; 与中间件 &lt;code&gt;AsyncSubAgentMiddleware&lt;/code&gt; 都在本机包里。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;from deepagents import AsyncSubAgent

spec = AsyncSubAgent(name=&quot;researcher&quot;, description=&quot;Research agent&quot;,
                     graph_id=&quot;researcher&quot;)  # 不写 url → ASGI 传输
print(spec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但请注意边界：&lt;strong&gt;构造 agent 不等于跑得通任务&lt;/strong&gt;。任务真正执行要靠 Agent Protocol 服务器——同进程 ASGI 传输需要同一个部署（同一份 &lt;code&gt;langgraph.json&lt;/code&gt;，且走异步调用入口），远端 HTTP 传输需要一台真实的远端服务。本地单纯 &lt;code&gt;python xx.py&lt;/code&gt; 不一定具备这个环境；原文整篇也是围绕部署环境展开的（&lt;code&gt;langgraph.json&lt;/code&gt;、&lt;code&gt;langgraph dev&lt;/code&gt;、LangSmith Deployments）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;注意（原文未说明）&lt;/strong&gt;：本机实测里只传一个 &lt;code&gt;AsyncSubAgent&lt;/code&gt; 时，构造出的 agent 里&lt;strong&gt;同时还有同步的 &lt;code&gt;task&lt;/code&gt; 工具&lt;/strong&gt;——默认的 general-purpose 同步子智能体仍会被挂上。两者并存不冲突，但排查&quot;主管为什么走了同步路径&quot;时要记得这一点。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;最佳实践&lt;/h2&gt;
&lt;h3&gt;本地开发时给工作进程池（worker pool）定容量&lt;/h3&gt;
&lt;p&gt;用 &lt;code&gt;langgraph dev&lt;/code&gt; 在本地跑时，要把工作进程池调大以容纳并发的子智能体 run。&lt;strong&gt;每个活跃 run 占一个 worker 槽位&lt;/strong&gt;，算账很简单：1 个主管 + 3 个并发子智能体任务 = &lt;strong&gt;需要 4 个槽位&lt;/strong&gt;；槽位给少了不会报错，而是&lt;strong&gt;排队&lt;/strong&gt;（launch 一直等着），这是最容易被误判成&quot;卡死&quot;的现象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;langgraph dev --n-jobs-per-worker 10
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;子智能体的 &lt;code&gt;description&lt;/code&gt; 怎么写&lt;/h3&gt;
&lt;p&gt;主管就是靠 &lt;code&gt;description&lt;/code&gt; 决定派谁去的，所以要&lt;strong&gt;具体、以动作开头&lt;/strong&gt;。原文给了一好一坏两个例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AsyncSubAgent(  # 好例子：说清手段 + 什么场景该找我
    name=&quot;researcher&quot;,
    description=&quot;Conducts in-depth research using web search. Use for questions requiring multiple searches and synthesis.&quot;,
    graph_id=&quot;researcher&quot;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;反面例子：&lt;code&gt;AsyncSubAgent(name=&quot;helper&quot;, description=&quot;helps with stuff&quot;, graph_id=&quot;helper&quot;)&lt;/code&gt;——等于没说，主管只能瞎猜，然后派错人。&lt;/p&gt;
&lt;h3&gt;用会话线程 ID（thread ID）做追踪&lt;/h3&gt;
&lt;p&gt;LangGraph 部署下，每个异步子智能体 run 都是&lt;strong&gt;标准的 LangGraph run&lt;/strong&gt;，在 LangSmith 里完整可见。主管的 trace 里能看到 &lt;code&gt;launch&lt;/code&gt; / &lt;code&gt;check&lt;/code&gt; / &lt;code&gt;update&lt;/code&gt; / &lt;code&gt;cancel&lt;/code&gt; / &lt;code&gt;list&lt;/code&gt; 这些工具调用，每个子智能体 run 是&lt;strong&gt;另一条独立 trace&lt;/strong&gt;，两边靠 &lt;strong&gt;thread ID 串起来&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;所以排障姿势是：用 thread ID（= task ID）把&quot;主管的编排 trace&quot;和&quot;子智能体的执行 trace&quot;对上——先看主管什么时候派的活，再看子智能体那边跑到哪一步。&lt;/p&gt;
&lt;h2&gt;排障清单&lt;/h2&gt;
&lt;p&gt;原文的 Troubleshooting 给了四个问题，每个都是&quot;模型行为&quot;层面的坑。&lt;/p&gt;
&lt;h3&gt;主管启动后立刻轮询&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;症状&lt;/strong&gt;：主管刚 &lt;code&gt;launch&lt;/code&gt; 完就在循环里调 &lt;code&gt;check&lt;/code&gt;，把异步硬生生用成了阻塞。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原因&lt;/strong&gt;：模型的本能是&quot;我派了活就该盯着&quot;，于是自己把非阻塞变成了忙等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理&lt;/strong&gt;：middleware 会注入系统提示规则来阻止这种行为；如果还在轮询，就在主管的 &lt;code&gt;system_prompt&lt;/code&gt; 里再加一条硬约束：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;from deepagents import create_deep_agent

agent = create_deep_agent(
    model=&quot;google_genai:gemini-3.6-flash&quot;,
    system_prompt=&quot;&quot;&quot;...your instructions...

    After launching an async subagent, ALWAYS return control to the user.
    Never call check_async_task immediately after launch.&quot;&quot;&quot;,
    subagents=async_subagents,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;官方这个问题的公开 trace：https://smith.langchain.com/public/181f8fc2-b3aa-4c4b-b23d-d4204db98849/r&lt;/p&gt;
&lt;h3&gt;主管报的是旧状态&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;症状&lt;/strong&gt;：主管直接引用对话历史里早先那次的状态，而不去重新 &lt;code&gt;check&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原因&lt;/strong&gt;：对话历史里的状态天然过期，模型却当成了当前值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理&lt;/strong&gt;：middleware 提示里已写明&quot;对话历史里的任务状态永远是旧的&quot;；如果还犯，就在系统提示里明确要求&quot;报状态前必须先调 &lt;code&gt;check&lt;/code&gt; 或 &lt;code&gt;list&lt;/code&gt;&quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;任务 ID 查不到&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;症状&lt;/strong&gt;：&lt;code&gt;check&lt;/code&gt; 或 &lt;code&gt;cancel&lt;/code&gt; 失败，因为主管把 task ID 截断或改写格式了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原因&lt;/strong&gt;：模型倾向于&quot;省略中间那段&quot;或&quot;美化一下&quot;，而 task ID 必须逐字精确。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理&lt;/strong&gt;：middleware 提示要求始终使用完整 task ID；如果还截断，原文说这通常是&lt;strong&gt;模型相关的毛病&lt;/strong&gt;——换个模型，或在系统提示里加&quot;永远显示完整 task_id，绝不截断或缩写&quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;子智能体只排队、不执行&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;症状&lt;/strong&gt;：启动一个子智能体后长时间挂着，或者很久才开始跑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原因&lt;/strong&gt;：工作进程池被占满了，新 run 没有槽位。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理&lt;/strong&gt;：用 &lt;code&gt;--n-jobs-per-worker&lt;/code&gt; 把池子调大，参考&lt;a href=&quot;#%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%97%B6%E7%BB%99%E5%B7%A5%E4%BD%9C%E8%BF%9B%E7%A8%8B%E6%B1%A0worker-pool%E5%AE%9A%E5%AE%B9%E9%87%8F&quot;&gt;工作进程池容量算法&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;四个问题排一下优先级：&lt;strong&gt;先怀疑模型行为（前三个），再怀疑资源（第四个）&lt;/strong&gt;。前三个的修法都在提示词层，第四个在启动参数层。&lt;/p&gt;
&lt;h2&gt;参考实现&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;async-deep-agents&lt;/strong&gt; 仓库：https://github.com/langchain-ai/async-deep-agents
Python 和 TypeScript 两种语言的&lt;strong&gt;可运行示例&lt;/strong&gt;，都能部署到 LangSmith Deployments；里面演示了一个主管带 researcher 和 coder 两个子智能体、以后台任务方式并跑。想抄作业就从这里开始，它把&quot;主管 + 两个后台子智能体&quot;的最小闭环跑通了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;异步子智能体 = 后台作业 + 主管不阻塞&lt;/strong&gt;。启动立刻拿 task ID，用户该聊天聊天，随时能查进度、改需求、取消——这正是 &lt;a href=&quot;/posts/multiagent-04-subagents/#%E5%86%B3%E7%AD%96%E4%B8%80%E5%90%8C%E6%AD%A5%E8%BF%98%E6%98%AF%E5%BC%82%E6%AD%A5&quot;&gt;04 篇&lt;/a&gt; 讲的&quot;异步&quot;概念在 Deep Agents 里的落地 API。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置只差一个 &lt;code&gt;url&lt;/code&gt;&lt;/strong&gt;：&lt;code&gt;AsyncSubAgent&lt;/code&gt; 必填 &lt;code&gt;name&lt;/code&gt; / &lt;code&gt;description&lt;/code&gt; / &lt;code&gt;graph_id&lt;/code&gt;；不写 &lt;code&gt;url&lt;/code&gt; 走 ASGI 传输（同部署、推荐默认），写了走 HTTP 传输（远端、要认证）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主管多 5 个工具&lt;/strong&gt;：&lt;code&gt;start_async_task&lt;/code&gt; / &lt;code&gt;check_async_task&lt;/code&gt; / &lt;code&gt;update_async_task&lt;/code&gt; / &lt;code&gt;cancel_async_task&lt;/code&gt; / &lt;code&gt;list_async_tasks&lt;/code&gt;。update 是&quot;打断重启、task ID 不变&quot;，cancel 走服务器的 &lt;code&gt;runs.cancel()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态存在主管 graph 的 &lt;code&gt;async_tasks&lt;/code&gt; 通道里，刻意和消息历史分开&lt;/strong&gt;——因为消息会被压缩，task ID 一旦进了被摘要掉的工具消息就找不回来了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;部署拓扑从单部署起步&lt;/strong&gt;：不够用了再拆分或混合，别一上来就上多套部署 + 认证 + 网络延迟。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本机能导入、能构造，但要有 server 才跑得通&lt;/strong&gt;：本机 &lt;code&gt;deepagents 0.7.5&lt;/code&gt; 已导出 &lt;code&gt;AsyncSubAgent&lt;/code&gt; 与 &lt;code&gt;AsyncSubAgentMiddleware&lt;/code&gt;；端到端跑通还需要能跑的 agent server / 部署环境。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;用户不该干等、活儿又长又可能要改方向 → 用异步子智能体（先单部署 + ASGI 起步）；主管必须拿到结果才能往下走 → 老老实实用 &lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 篇&lt;/a&gt; / &lt;a href=&quot;/posts/deepagents-02-dynamic/&quot;&gt;02 篇&lt;/a&gt; 的同步子智能体，别为了&quot;显得高级&quot;把对话冻住。&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>02 Dynamic subagents：用代码编排子智能体（解释器运行时）</title><link>https://aicooleo.cn/blog/posts/deepagents-02-dynamic/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/deepagents-02-dynamic/</guid><description>静态子智能体像给主管一本通讯录，让他自己一个一个打电话；动态子智能体像主管直接写了一段脚本——打给谁、打几个、谁先谁后、结果怎么比，全由脚本说了算，主管只负责最后拍板。</description><pubDate>Tue, 01 Sep 2026 11:08:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：静态子智能体像给主管一本通讯录，让他自己一个一个打电话；动态子智能体像主管直接写了一段脚本——打给谁、打几个、谁先谁后、结果怎么比，全由脚本说了算，主管只负责最后拍板。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;先分清：静态子智能体 vs 动态子智能体&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 篇&lt;/a&gt; 讲的是&lt;strong&gt;静态子智能体的配置面&lt;/strong&gt;：&lt;code&gt;subagents=[...]&lt;/code&gt; 怎么写、&lt;code&gt;name&lt;/code&gt;/&lt;code&gt;description&lt;/code&gt;/&lt;code&gt;system_prompt&lt;/code&gt; 各管什么、&lt;code&gt;mode&lt;/code&gt; 怎么选。那一篇里，&lt;strong&gt;派活这个动作是模型做的&lt;/strong&gt;——模型看到一个 &lt;code&gt;task&lt;/code&gt; 工具，自己决定调谁、调几次、什么时候停。&lt;/p&gt;
&lt;p&gt;本篇完全换了一个位置：&lt;strong&gt;派活这个动作交给代码做&lt;/strong&gt;。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;静态（&lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 篇&lt;/a&gt;）&lt;/th&gt;
&lt;th&gt;动态（本篇）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;谁决定派谁&lt;/td&gt;
&lt;td&gt;模型：每次一次 &lt;code&gt;task&lt;/code&gt; 工具调用&lt;/td&gt;
&lt;td&gt;代码：&lt;code&gt;task()&lt;/code&gt; 在一段 JS 循环 / 并发里被调用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;派几次&lt;/td&gt;
&lt;td&gt;模型边看结果边决定&lt;/td&gt;
&lt;td&gt;代码写死，比如 &lt;code&gt;files.length&lt;/code&gt; 次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;并发&lt;/td&gt;
&lt;td&gt;串行，一次一个&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Promise.all&lt;/code&gt; 并发一批&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;结果怎么处理&lt;/td&gt;
&lt;td&gt;文本回灌对话，模型自己记&lt;/td&gt;
&lt;td&gt;JS 值，可以 &lt;code&gt;filter&lt;/code&gt; / &lt;code&gt;sort&lt;/code&gt; / 投票&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适合&lt;/td&gt;
&lt;td&gt;单次委派、职责隔离&lt;/td&gt;
&lt;td&gt;批量、多视角、递归、需要明确收敛&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一句话：&lt;strong&gt;静态 = 模型决定派谁；动态 = 代码决定什么时候派、派几个、怎么收敛。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;底层机制（&lt;code&gt;subagents=&lt;/code&gt; 本质上就是一个 &lt;code&gt;task&lt;/code&gt; 工具）见 &lt;a href=&quot;/posts/multiagent-14-appendix-tool-wrap/&quot;&gt;14 篇&lt;/a&gt; 和模式原理 &lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;04 篇&lt;/a&gt;；异步版见 &lt;a href=&quot;/posts/deepagents-03-async/&quot;&gt;03 篇&lt;/a&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;它解决什么问题&lt;/h2&gt;
&lt;p&gt;静态派活有三个绕不过去的局限：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;① 一次只能派一个。&lt;/strong&gt; 模型调用 &lt;code&gt;task&lt;/code&gt; 工具，等结果回来，再决定下一次调用。审查 20 个文件就是 20 个串行的来回，墙钟时间线性叠加。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 派几次、按什么顺序，由模型决定。&lt;/strong&gt; &quot;把这批工单都处理一遍&quot;这种需求，模型可能处理 8 个就&quot;觉得差不多了&quot;，也可能重复处理同一个。这是概率行为，不是确定性行为——你没法保证它一定遍历完 &lt;code&gt;src/routes/&lt;/code&gt; 下的每个文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 子智能体之间不能互相比较、筛选。&lt;/strong&gt; 模型拿到的是一堆文本消息。让它&quot;从 5 个方案里按这个标准挑最好的&quot;，它只能靠&quot;读一遍再拍脑袋&quot;。它没法真的 &lt;code&gt;sort&lt;/code&gt;，也没法真的做多数投票。&lt;/p&gt;
&lt;p&gt;换成代码编排，换来的是三件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;确定性&lt;/strong&gt;：循环次数、并发度、停止条件都写在代码里，可复现、可单测。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;并发&lt;/strong&gt;：&lt;code&gt;Promise.all&lt;/code&gt; 一次把 N 个 &lt;code&gt;task()&lt;/code&gt; 同时发出去，墙钟时间从 N× 变成约 1×。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可计算的结果处理&lt;/strong&gt;：结构化输出直接是 JS 对象，能在代码里排序、去重、投票、打分——真正意义上的&quot;结果收敛（reduce）&quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代价也很直白：&lt;strong&gt;编排逻辑从提示词搬到了代码里，出问题时要看代码而不是看提示词&lt;/strong&gt;；而且并发和循环都是 token 放大器，失控的 &lt;code&gt;while&lt;/code&gt; 和无限扇出能把账单打穿。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;整体机制&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;graph TB
    U[&quot;用户任务&amp;lt;br/&amp;gt;User Task&quot;] --&amp;gt; S[&quot;主管智能体&amp;lt;br/&amp;gt;Supervisor&quot;]
    S --&amp;gt;|&quot;写一段 JS 交出去&quot;| I[&quot;解释器 Interpreter&amp;lt;br/&amp;gt;QuickJS 运行时&quot;]
    I --&amp;gt; C{&quot;代码编排&amp;lt;br/&amp;gt;Code Orchestration&amp;lt;br/&amp;gt;循环 / 分支 / Promise.all&quot;}
    C --&amp;gt;|&quot;task()&quot;| A1[&quot;子智能体 A&amp;lt;br/&amp;gt;Subagent A&quot;]
    C --&amp;gt;|&quot;task()&quot;| A2[&quot;子智能体 B&amp;lt;br/&amp;gt;Subagent B&quot;]
    C --&amp;gt;|&quot;task()&quot;| A3[&quot;子智能体 C&amp;lt;br/&amp;gt;Subagent C&quot;]
    A1 --&amp;gt; R[&quot;结果收敛&amp;lt;br/&amp;gt;Reduce / Synthesize&amp;lt;br/&amp;gt;filter / sort / 投票&quot;]
    A2 --&amp;gt; R
    A3 --&amp;gt; R
    R --&amp;gt; F[&quot;最终答案&amp;lt;br/&amp;gt;Final Answer&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;① &lt;strong&gt;主管不再直接调子智能体&lt;/strong&gt;，而是写一段 JavaScript 丢给解释器（Interpreter）——也就是把&quot;怎么编排&quot;这件事从模型脑子里搬进代码里。
② &lt;strong&gt;解释器里的代码负责编排（Code Orchestration）&lt;/strong&gt;：循环、分支、&lt;code&gt;Promise.all&lt;/code&gt; 并发，通过内置的 &lt;code&gt;task()&lt;/code&gt; 把活派给子智能体（Subagent）。派几个、什么顺序、要不要重试，全由这段代码说了算。
③ &lt;strong&gt;子智能体返回的是 JS 值，不是对话文本&lt;/strong&gt;，所以代码能做真正的收敛（Reduce）——过滤、排序、投票、打分——最后只把收敛后的结论交回主管。&lt;/p&gt;
&lt;p&gt;换成 ASCII 看形状：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        用户任务：&quot;审查 src/routes/ 下每个文件，汇总 top 风险&quot;
                            │
                            ▼
              ┌────────────────────────────┐
              │  主管 Supervisor（LLM）      │ 产出：一段 JS 代码
              └─────────────┬──────────────┘
                            │ eval
                            ▼
┌──────────────── 解释器 Interpreter（QuickJS）────────────────┐
│  files = (await tools.glob({pattern:&quot;src/routes/**/*.ts&quot;}))  │
│            .split(&quot;\n&quot;).filter(Boolean);     ← PTC 打开才有   │
│                                                              │
│  reviews = await Promise.all(   ← 代码决定并发几个            │
│    files.map(f =&amp;gt; task({ description: `Review ${f}`,         │
│                          subagentType: &quot;reviewer&quot; })));      │
│  issues = reviews.flatMap(r =&amp;gt; r.issues).sort(bySeverity);   │
│                                              ← 结果收敛 Reduce│
└──────────────────────────────┬───────────────────────────────┘
       ┌──────────┬────────────┴────────────┬──────────┐
       ▼          ▼                         ▼          ▼
   reviewer   reviewer      ......      reviewer   reviewer
    file 1     file 2                   file N-1    file N
       └──────────┴────────────┬────────────┴──────────┘
                  issues（已排序、已去重）→ 最终答案 → 主管 → 用户
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;版本要求&lt;/strong&gt;：解释器（interpreter）运行时目前是 &lt;strong&gt;beta&lt;/strong&gt;，官方明确说 API 和生命周期行为可能随版本变化。它依赖 &lt;code&gt;langchain-quickjs&amp;gt;=0.2.0&lt;/code&gt; 和 &lt;strong&gt;Python 3.11+&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;本机实测（2026-09-01）&lt;/strong&gt;：本机 &lt;code&gt;deepagents&lt;/code&gt; 是 &lt;strong&gt;0.7.5&lt;/strong&gt;，包内&lt;strong&gt;没有解释器运行时&lt;/strong&gt;——&lt;code&gt;deepagents&lt;/code&gt; 包下不存在 &lt;code&gt;interpreters&lt;/code&gt; 相关模块（&lt;code&gt;deepagents.interpreters&lt;/code&gt; 的模块 spec 为 &lt;code&gt;None&lt;/code&gt;），顶层导出里也没有任何解释器 / &lt;code&gt;CodeInterpreter&lt;/code&gt; 相关符号；&lt;code&gt;langchain_quickjs&lt;/code&gt; 也&lt;strong&gt;未安装&lt;/strong&gt;。所以&lt;strong&gt;动态子智能体在本机不可用&lt;/strong&gt;，下面的 Quickstart 和模式代码都跑不起来，需要 &lt;code&gt;pip install langchain-quickjs&lt;/code&gt; 并升级到包含解释器运行时的深版本（beta）后再试。&lt;/p&gt;
&lt;p&gt;本篇的价值不在&quot;照抄能跑&quot;，而在于&lt;strong&gt;讲清编排思路与六种模式&lt;/strong&gt;。这些代码骨架可以直接迁移到任何&quot;代码编排 + 调用 Agent&quot;的实现上——用 LangGraph 自己搭图，或者就用普通 Python 写循环和线程池，思路完全一样。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;快速上手（Quickstart）&lt;/h2&gt;
&lt;p&gt;动态子智能体不是一套新 API，而是给已有的 agent &lt;strong&gt;多挂一个中间件&lt;/strong&gt;。三步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;装解释器运行时：&lt;code&gt;pip install &quot;langchain-quickjs&amp;gt;=0.2.0&quot;&lt;/code&gt;（Python 3.11+）。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;create_deep_agent(...)&lt;/code&gt; 的 &lt;code&gt;middleware=[...]&lt;/code&gt; 里挂上 &lt;code&gt;CodeInterpreterMiddleware()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;subagents=[...]&lt;/code&gt; 里配好子智能体——&lt;strong&gt;它们的 &lt;code&gt;name&lt;/code&gt; 就是代码里 &lt;code&gt;subagentType&lt;/code&gt; 能填的取值&lt;/strong&gt;，&lt;code&gt;description&lt;/code&gt; 是模型判断&quot;该找谁&quot;的依据。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;原文档给了 7 个模型厂商的版本（Google / OpenAI / Anthropic / OpenRouter / Fireworks / Baseten / Ollama），差别只在 &lt;code&gt;model=&lt;/code&gt; 那一行；这里只保留 OpenAI 版：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from deepagents import create_deep_agent
from langchain_quickjs import CodeInterpreterMiddleware

agent = create_deep_agent(
    model=&quot;openai:gpt-5.5&quot;,
    subagents=[{
        &quot;name&quot;: &quot;reviewer&quot;,
        &quot;description&quot;: &quot;Reviews code for security issues, citing lines and severity&quot;,
        &quot;system_prompt&quot;: &quot;You are a security-focused code reviewer. Report issues with line numbers and severity.&quot;,
    }],
    middleware=[CodeInterpreterMiddleware()],  # 关键：挂上解释器，task() 才会出现
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明几点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只要 &lt;code&gt;subagents&lt;/code&gt; 非空、又挂了 &lt;code&gt;CodeInterpreterMiddleware()&lt;/code&gt;，解释器里就会自动多出一个全局函数 &lt;code&gt;task()&lt;/code&gt;——不需要你额外注册什么。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不需要额外配置就能做基础扇出&lt;/strong&gt;：Deep Agents 内置的 &lt;code&gt;general-purpose&lt;/code&gt; 子智能体（默认子智能体）可以直接被 &lt;code&gt;task()&lt;/code&gt; 调用。&lt;/li&gt;
&lt;li&gt;想要专业分工，才需要自己配子智能体，把名字、描述、系统提示词写清楚。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;再用一句&quot;workflow&quot;触发它：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result = agent.invoke({
    &quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;Run a workflow that reviews every file in src/routes/ and summarizes the top risks.&quot;}]
})
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：&quot;workflow&quot; 这个词是一个&lt;strong&gt;有用的触发器&lt;/strong&gt;。解释器的系统提示词把 &quot;workflow&quot; 当成信号——看到它就倾向于&quot;通过解释器组织工作&quot;，用代码里的 &lt;code&gt;task()&lt;/code&gt; 派子智能体，而不是一个一个地做模型选的工具调用。把需求描述成一个 &quot;workflow&quot;，就是你主动打开动态编排的拉杆。反过来，&lt;strong&gt;如果只是一次直接的委派，就平铺直叙地说&lt;/strong&gt;，别用这个词，免得白白绕进解释器。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;注意&lt;/strong&gt;：&lt;code&gt;dcode&lt;/code&gt;（LangChain 的终端编码 agent）&lt;strong&gt;自带了代码解释器&lt;/strong&gt;，所以在 &lt;code&gt;dcode&lt;/code&gt; 里动态子智能体开箱即用，不用自己挂中间件。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;它是怎么工作的&lt;/h2&gt;
&lt;p&gt;这一段是全篇重点，把机制拆开讲。&lt;/p&gt;
&lt;h3&gt;三个零件&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;① 解释器运行时（interpreter runtime）&lt;/strong&gt;：一个跑在 agent 旁边的 JavaScript（QuickJS）沙箱。模型不是&quot;调用解释器工具&quot;，而是&lt;strong&gt;写一段代码&lt;/strong&gt;交给它执行（底层是一个 &lt;code&gt;eval&lt;/code&gt; 工具）。它平时就很有用——算数、处理大文本、保存中间变量；挂了子智能体之后，它多了一个能力：&lt;strong&gt;在代码里派活&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② &lt;code&gt;task()&lt;/code&gt; 全局函数&lt;/strong&gt;：解释器里的内置入口，等价于&quot;从代码里调用子智能体&quot;。它接收三个入参：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;入参&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;交给子智能体的提示词&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subagentType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用哪个已配置的子智能体（填 &lt;code&gt;subagents=[...]&lt;/code&gt; 里的 &lt;code&gt;name&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;responseSchema&lt;/code&gt;（可选）&lt;/td&gt;
&lt;td&gt;结构化输出的 JSON Schema&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一次 &lt;code&gt;task()&lt;/code&gt; 会&lt;strong&gt;跑完一整轮完整的 agent loop&lt;/strong&gt;（子智能体自己思考、调工具、再思考），然后 resolve 出这个子智能体的最终结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 子智能体（subagent）&lt;/strong&gt;：还是 &lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 篇&lt;/a&gt; 里那套配置——只不过在静态模式里它是模型手上的一个 &lt;code&gt;task&lt;/code&gt; 工具，在动态模式里它是代码里的一个函数调用。&lt;/p&gt;
&lt;h3&gt;它和 &lt;code&gt;task&lt;/code&gt; 工具到底差在哪&lt;/h3&gt;
&lt;p&gt;这是最容易被含糊过去的地方。两者最终跑的是同一批子智能体，但&lt;strong&gt;发起方和结果形态完全不同&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;&lt;code&gt;task&lt;/code&gt; 工具（静态）&lt;/th&gt;
&lt;th&gt;&lt;code&gt;task()&lt;/code&gt; 函数（动态）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;谁发起&lt;/td&gt;
&lt;td&gt;模型，每轮一次工具调用&lt;/td&gt;
&lt;td&gt;解释器里的 JS 代码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;一次能派几个&lt;/td&gt;
&lt;td&gt;一个&lt;/td&gt;
&lt;td&gt;想几个就几个（&lt;code&gt;Promise.all&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;次数谁定&lt;/td&gt;
&lt;td&gt;模型&lt;/td&gt;
&lt;td&gt;代码（&lt;code&gt;files.length&lt;/code&gt;、&lt;code&gt;while&lt;/code&gt; 条件）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;并发的实现&lt;/td&gt;
&lt;td&gt;靠框架 / 无&lt;/td&gt;
&lt;td&gt;JS 原生并发原语&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;返回什么&lt;/td&gt;
&lt;td&gt;文本，回灌进对话上下文&lt;/td&gt;
&lt;td&gt;JS 值，可以 &lt;code&gt;filter&lt;/code&gt; / &lt;code&gt;sort&lt;/code&gt; / 投票&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;结构化输出&lt;/td&gt;
&lt;td&gt;需要额外解析&lt;/td&gt;
&lt;td&gt;传 &lt;code&gt;responseSchema&lt;/code&gt; 即可&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;审批链路&lt;/td&gt;
&lt;td&gt;&lt;code&gt;interrupt_on&lt;/code&gt; 逐个生效&lt;/td&gt;
&lt;td&gt;&lt;code&gt;task()&lt;/code&gt; 在 &lt;code&gt;eval&lt;/code&gt; 里派发，&lt;strong&gt;不逐次经过审批&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;看一段完整形态的 &lt;code&gt;task()&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const review = await task({
  description: &quot;Review src/auth/login.ts for auth issues. Cite line numbers.&quot;,
  subagentType: &quot;reviewer&quot;,
  responseSchema: {
    type: &quot;object&quot;,
    properties: {
      issues: { type: &quot;array&quot;, items: { type: &quot;object&quot;, properties: {
        file: { type: &quot;string&quot; }, line: { type: &quot;number&quot; },
        severity: { type: &quot;string&quot; }, description: { type: &quot;string&quot; },
      }}},
    },
  },
});

// 带 responseSchema 时，拿到的已经是类型化对象，不需要 JSON.parse
const critical = review.issues.filter((issue) =&amp;gt; issue.severity === &quot;high&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键细节：&lt;strong&gt;只有子智能体故意返回了一个 JSON 字符串时，你才需要 &lt;code&gt;JSON.parse&lt;/code&gt;&lt;/strong&gt;；只要传了 &lt;code&gt;responseSchema&lt;/code&gt;，resolved 出来的就是现成的类型化对象。这是&quot;结果能在代码里算&quot;的前提。&lt;/p&gt;
&lt;h3&gt;为什么&quot;确定性&quot;是重点&lt;/h3&gt;
&lt;p&gt;原文的原话是：一个跨很多独立单元的任务（审查目录下的每个文件、批量分诊工单），变成&lt;strong&gt;一个把工作扇出去的循环&lt;/strong&gt;，于是它&lt;strong&gt;确定性地运行&lt;/strong&gt;，而不是&quot;一次一个由模型挑选的工具调用&quot;。&lt;/p&gt;
&lt;p&gt;这不只是性能问题。串行 20 次工具调用，中间任何一次模型判断失误都可能提前收工；而 &lt;code&gt;files.map(...)&lt;/code&gt; 的循环次数是 &lt;code&gt;files.length&lt;/code&gt;，不依赖模型心情。&lt;/p&gt;
&lt;h3&gt;两个配套能力&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;递归语言模型（RLM, Recursive Language Models）&lt;/strong&gt;：出自论文 &lt;a href=&quot;https://arxiv.org/abs/2512.24601&quot;&gt;Recursive Language Models&lt;/a&gt;。做法是——&lt;strong&gt;把工作集放在解释器变量里&lt;/strong&gt;，按需切出片段，用 &lt;code&gt;task()&lt;/code&gt; 派子智能体处理，再把结果汇总。本文这六种模式，本质上都是这个思路的不同切法。它的价值在于：你不用把 200 个文件塞进上下文，只把&quot;当前这一片&quot;喂给子智能体。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;程序化工具调用（PTC, programmatic tool calling）&lt;/strong&gt;：很多编排流程要先&quot;发现输入&quot;（列出文件、筛出待处理项），这就需要从解释器代码里调普通工具，也就是 &lt;code&gt;tools.*&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const files = (await tools.glob({ pattern: &quot;src/routes/**/*.ts&quot; }))
  .split(&quot;\n&quot;)
  .filter(Boolean);          // 在代码里发现 / 过滤输入
const reviews = await Promise.all(files.map((f) =&amp;gt; task({  // 再用 task() 派活
  description: `Review ${f}`, subagentType: &quot;reviewer&quot;,
})));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;PTC 默认是关的&lt;/strong&gt;，必须在解释器中间件上给显式白名单才开——比如 &lt;code&gt;CodeInterpreterMiddleware(ptc=[&quot;glob&quot;])&lt;/code&gt;。不开白名单，解释器里的代码就看不见文件系统。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;task()&lt;/code&gt; 和 PTC 是&lt;strong&gt;一对对称的能力桥&lt;/strong&gt;：&lt;code&gt;tools.*&lt;/code&gt; 是&quot;代码调到工具&quot;，&lt;code&gt;task()&lt;/code&gt; 是&quot;代码调子智能体&quot;。安全默认值、审批边界、中间件选项见官方 Interpreter 页的 Security / Configuration 小节。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;注意&lt;/strong&gt;：用 &lt;code&gt;mode=&quot;thread&quot;&lt;/code&gt;（默认值）时，&lt;strong&gt;解释器变量可以跨多轮对话持久化&lt;/strong&gt;。也就是说上一轮存在解释器里的 &lt;code&gt;seen&lt;/code&gt;、&lt;code&gt;found&lt;/code&gt; 这些变量，下一轮还在——多轮编排能接着上一轮的进度干。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;笔记补充的示意伪代码（非原文）&lt;/strong&gt;：本机跑不了动态子智能体，但下面这套&quot;代码编排 + 调用 Agent&quot;的骨架是通用的——换成 LangGraph 或普通 Python 一样成立：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;# 纯文本伪代码：同一套编排思路，与语言和框架无关
files   = discover_inputs(&quot;src/routes/**/*.ts&quot;)     # 发现输入（对应 tools.glob）
reviews = parallel_map(files, limit = 4,            # 并发要有上限（对应 Promise.all）
                       fn = call_subagent(&quot;reviewer&quot;, 结构化输出))
issues  = flatten(r.issues for r in reviews)        # 显式收敛（对应 flatMap）
issues  = dedupe(sort_by_severity(issues))          # 排序 + 去重
return  issues                                      # 收敛后的结论才是答案
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;警告&lt;/strong&gt;：&lt;code&gt;task()&lt;/code&gt; 是在一个&lt;strong&gt;已经在运行的 &lt;code&gt;eval&lt;/code&gt; 调用内部&lt;/strong&gt;派发的。它&lt;strong&gt;不走常规的工具调用路径&lt;/strong&gt;，所以父 agent 上配的 &lt;code&gt;interrupt_on&lt;/code&gt; 审批流程&lt;strong&gt;不会对每一次派发生效&lt;/strong&gt;。如果你需要在子智能体编排开始前有人点&quot;同意&quot;，正确做法是&lt;strong&gt;拦截 &lt;code&gt;eval&lt;/code&gt; 工具本身&lt;/strong&gt;（gate the &lt;code&gt;eval&lt;/code&gt; tool），而不是去拦 &lt;code&gt;task&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;六种编排模式&lt;/h2&gt;
&lt;p&gt;原文的 Patterns 一节说得很清楚：&lt;strong&gt;这些模式不是配置出来的&lt;/strong&gt;，是模型根据任务形状自己写的解释器代码&quot;长&quot;出来的；你能控制的只有&quot;放哪些子智能体进去&quot;。但六种模式共享同一套编排姿势：&lt;strong&gt;把工作握在 JS 变量里 → 用 &lt;code&gt;task()&lt;/code&gt; 派子智能体 → 在代码里合并结果&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;下面逐个讲，每种给：适用场景 / 代码骨架 / 为什么这么写 / 代价。&lt;/p&gt;
&lt;h3&gt;分类后分发（Classify and act）&lt;/h3&gt;
&lt;p&gt;先说结论：&lt;strong&gt;先给每个 item 打标签，再按标签路由给对应的专家。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：客服工单分诊、错误日志归类、用户反馈分流——凡是&quot;一锅里混了好几种类型&quot;的批量输入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;流程&lt;/strong&gt;：&lt;code&gt;items → 分类器 → bug 交给 bug-fixer / feature 交给 feature-analyst / question 交给 support-agent → 汇总成分诊报告&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需要的子智能体&lt;/strong&gt;：&lt;code&gt;bug-fixer&lt;/code&gt;（调查 bug 报告并给出复现步骤）、&lt;code&gt;feature-analyst&lt;/code&gt;（评估需求可行性与工作量）、&lt;code&gt;support-agent&lt;/code&gt;（基于文档回答用户问题）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 前提：tickets 已经带上 category 字段（分类可以在同一段代码里先派一个分类子智能体做）
const SPECIALIST = { bug: &quot;bug-fixer&quot;, feature: &quot;feature-analyst&quot;, question: &quot;support-agent&quot; };

const handled = await Promise.all(
  tickets.map((ticket) =&amp;gt;
    task({
      description: `Handle this ${ticket.category}:\n${ticket.text}`,
      subagentType: SPECIALIST[ticket.category],
    }),
  ),
);
// ... 把 handled 按 category 分组，拼成一份分诊报告
handled;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;为什么这么写&lt;/strong&gt;：路由表 &lt;code&gt;SPECIALIST&lt;/code&gt; 就是一张普通的 JS 对象——&lt;strong&gt;改路由不用改模型提示词&lt;/strong&gt;，加一个类型加一行就行。&lt;code&gt;Promise.all&lt;/code&gt; 让不同专家并行开工，而且每个 item 只被处理一次。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代价&lt;/strong&gt;：分类错了整条就错了，而且没人复核。新类型（比如 &lt;code&gt;refund&lt;/code&gt;）进来时路由表里没有键，&lt;code&gt;subagentType&lt;/code&gt; 会是 &lt;code&gt;undefined&lt;/code&gt;——要么补兜底键，要么加一道人工抽检。&lt;/p&gt;
&lt;h3&gt;扇出后汇总（Fan-out and synthesize）&lt;/h3&gt;
&lt;p&gt;先说结论：&lt;strong&gt;同一件活复制 N 份并行干，干完在代码里合并。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：整个目录的代码审查、批量文档分析、日志文件处理、同一项检查跑遍多个服务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;流程&lt;/strong&gt;：&lt;code&gt;items → worker ×N（并发）→ collect → synthesize → 一份合并结论&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需要的子智能体&lt;/strong&gt;：&lt;code&gt;reviewer&lt;/code&gt;（安全审查，标明行号与严重级别）；中间件要开 PTC：&lt;code&gt;CodeInterpreterMiddleware(ptc=[&quot;glob&quot;])&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 每个文件派一个 reviewer 并行审，最后把发现合并
const files = (await tools.glob({ pattern: &quot;src/routes/**/*.ts&quot; }))
  .split(&quot;\n&quot;)
  .filter(Boolean);

const reviews = await Promise.all(
  files.map((file) =&amp;gt;
    task({
      description: `Review ${file} for authentication issues. Cite line numbers.`,
      subagentType: &quot;reviewer&quot;,
      responseSchema: issuesSchema, // -&amp;gt; { issues: [{ file, line, severity }] }
    }),
  ),
);

const issues = reviews.flatMap((r) =&amp;gt; r.issues);
// ... 按严重级别排序、去重，总结 top 风险
issues;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;为什么这么写&lt;/strong&gt;：两点。一是 &lt;code&gt;tools.glob&lt;/code&gt; —— &lt;strong&gt;从解释器代码里发现文件必须开 PTC&lt;/strong&gt;，并且把 &lt;code&gt;glob&lt;/code&gt; 加进 &lt;code&gt;ptc=[...]&lt;/code&gt; 白名单，否则代码看不见文件系统，只能靠模型把文件列表贴进来（那就退化了）。二是 &lt;code&gt;flatMap&lt;/code&gt; —— 每个子智能体返回的是 &lt;code&gt;{ issues: [...] }&lt;/code&gt; 这种结构，得先摊平才能统一排序去重。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代价&lt;/strong&gt;：N 个文件 = N 个并发 agent loop，token 和速率限制都是线性涨的。&lt;strong&gt;绝大多数&quot;扇出事故&quot;都出在这里没分批&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;对抗式验证（Adversarial verification）&lt;/h3&gt;
&lt;p&gt;先说结论：&lt;strong&gt;两趟。第一趟出结论，第二趟让独立的验证者逐条挑刺，只有活下来的才留。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：误报代价很高的安全审计、合规检查，以及任何&quot;宁缺毋滥、置信度比速度重要&quot;的复核。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;流程&lt;/strong&gt;：&lt;code&gt;items → workers → findings → verifier ×N → 多数投票 → confirmed&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需要的子智能体&lt;/strong&gt;：&lt;code&gt;reviewer&lt;/code&gt;（找潜在漏洞，报文件/行号/描述）+ &lt;code&gt;verifier&lt;/code&gt;（独立验证某个漏洞是否真的可利用，提示词要求 &quot;Be skeptical&quot;，&lt;strong&gt;只确认真实问题&lt;/strong&gt;）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 第一趟：审计；第二趟：逐条独立验证，只保留被确认的
const { findings } = await task({
  description: &quot;Audit the payments module for vulnerabilities.&quot;,
  subagentType: &quot;reviewer&quot;,
  responseSchema: findingsSchema, // -&amp;gt; { findings: [{ id, file, line, description }] }
});

const verdicts = await Promise.all(
  findings.map((f) =&amp;gt;
    task({
      description: `Verify ${f.file}:${f.line} (${f.description}). Confirm or refute.`,
      subagentType: &quot;verifier&quot;,
      responseSchema: verdictSchema, // -&amp;gt; { confirmed: boolean }
    }),
  ),
);

const confirmed = findings.filter((_, i) =&amp;gt; verdicts[i]?.confirmed);
// ... 只报告被确认的漏洞
confirmed;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;为什么这么写&lt;/strong&gt;：验证者用的是&lt;strong&gt;另一个子智能体&lt;/strong&gt;（&lt;code&gt;verifier&lt;/code&gt; 而不是 &lt;code&gt;reviewer&lt;/code&gt;），提示词也明确要求怀疑态度——&lt;strong&gt;独立性才有意义&lt;/strong&gt;，同一个人复核自己会一路点头。原文的图是三个 verifier 加多数投票；代码骨架为了短只放了一个 verifier per finding，要投票就在 &lt;code&gt;Promise.all&lt;/code&gt; 里再套一层，按 &lt;code&gt;confirmed&lt;/code&gt; 的比例判。注意 &lt;code&gt;findings.filter((_, i) =&amp;gt; verdicts[i]?.confirmed)&lt;/code&gt; 里用了 &lt;code&gt;?.&lt;/code&gt;——验证失败留下 &lt;code&gt;undefined&lt;/code&gt; 时不会被误判为确认。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代价&lt;/strong&gt;：成本至少翻倍——&lt;code&gt;findings 数量 × 验证者数量&lt;/code&gt; 次 &lt;code&gt;task()&lt;/code&gt;。而且验证者本身也会犯错，它只是把&quot;单点判断&quot;换成&quot;多数判断&quot;，不是把错误变成零。&lt;/p&gt;
&lt;h3&gt;生成后筛选（Generate and filter）&lt;/h3&gt;
&lt;p&gt;先说结论：&lt;strong&gt;让多个子智能体各自独立出方案，再在代码里打分、排序、留最好的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：架构方案、重构策略、内容变体——凡是&quot;先多探索再定稿&quot;收益明显的任务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;流程&lt;/strong&gt;：&lt;code&gt;prompt → generator ×3 → filter + rank → best result&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需要的子智能体&lt;/strong&gt;：&lt;code&gt;architect&lt;/code&gt;（给出 schema 设计，附权衡分析、迁移考虑和理由）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 并发生成多个独立方案，再按需求打分留最优
const proposals = await Promise.all(
  [1, 2, 3].map((n) =&amp;gt;
    task({
      description: `Approach ${n}: redesign the orders schema, with tradeoffs.`,
      subagentType: &quot;architect&quot;,
      responseSchema: designSchema, // -&amp;gt; { design, tradeoffs }
    }),
  ),
);

// ... 按需求给每个方案打分
const best = proposals.sort((a, b) =&amp;gt; score(b) - score(a))[0];
best;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;为什么这么写&lt;/strong&gt;：生成阶段&lt;strong&gt;故意不让生成者看到彼此的方案&lt;/strong&gt;——独立性带来多样性，互相抄就会收敛到同一个平庸答案。筛选阶段放在代码里，因为规则要可见可测。&lt;code&gt;responseSchema&lt;/code&gt; 是这一模式的命门：没有结构化输出，你拿到的是三段散文，&lt;strong&gt;根本没法 &lt;code&gt;sort&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代价&lt;/strong&gt;：N 倍生成成本加一次筛选成本。另外 &lt;code&gt;score()&lt;/code&gt; 在原文里是省略号——打分可以再派一个子智能体，也可以是代码里的确定性规则；&lt;strong&gt;如果打分也交给模型，那这个模式就变成了下面的锦标赛&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;锦标赛（Tournament）&lt;/h3&gt;
&lt;p&gt;先说结论：&lt;strong&gt;变体两两 PK，赢的进下一轮，直到只剩一个冠军。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：主观标准下的择优——代码风格选择、文案变体、在两个能跑的竞争实现里挑一个。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;流程&lt;/strong&gt;：&lt;code&gt;attempt ×5 → judge（两两对战）→ ... → winner&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需要的子智能体&lt;/strong&gt;：&lt;code&gt;writer&lt;/code&gt;（重写函数，以可读性为目标，并解释选择）+ &lt;code&gt;judge&lt;/code&gt;（比较两份实现，选更可读的那个，并要求给出具体评判依据）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 先生成变体，再两两判胜负，直到决出唯一冠军
let bracket = await Promise.all(
  [1, 2, 3, 4, 5].map((n) =&amp;gt;
    task({ description: `Rewrite processOrder for readability (variant ${n}).`, subagentType: &quot;writer&quot; }),
  ),
);

while (bracket.length &amp;gt; 1) {
  const winners = [];
  for (let i = 0; i &amp;lt; bracket.length; i += 2) {
    if (bracket[i + 1] === undefined) { winners.push(bracket[i]); break; } // 轮空直接晋级
    const { winner } = await task({
      description: `Pick the more readable:\n\nA:\n${bracket[i]}\n\nB:\n${bracket[i + 1]}`,
      subagentType: &quot;judge&quot;,
      responseSchema: pickSchema, // -&amp;gt; { winner: &quot;A&quot; | &quot;B&quot; }
    });
    winners.push(winner === &quot;A&quot; ? bracket[i] : bracket[i + 1]);
  }
  bracket = winners;
}
bracket[0]; // 冠军
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;为什么这么写&lt;/strong&gt;：模型的&lt;strong&gt;绝对打分很不稳，相对判断稳得多&lt;/strong&gt;——&quot;给这三份方案各打 1-10 分&quot;远不如&quot;这两份哪个更好读&quot;。淘汰赛把问题从&quot;N 份一起排序&quot;换成 N-1 场两两比较，轮数是对数级。代码里显式处理了奇数长度时的轮空（&lt;code&gt;bracket[i + 1] === undefined&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代价&lt;/strong&gt;：场次 = N-1，每场都是一轮完整 agent loop（&lt;code&gt;await&lt;/code&gt; 在 &lt;code&gt;for&lt;/code&gt; 里，是串行的）。冠军的稳定性完全取决于 judge 提示词的质量；判官最好和选手是&lt;strong&gt;不同的子智能体&lt;/strong&gt;，否则容易自我偏袒。&lt;/p&gt;
&lt;h3&gt;循环直到达标（Loop until done）&lt;/h3&gt;
&lt;p&gt;先说结论：&lt;strong&gt;一轮轮派活，每轮把新发现并进集合，某一轮没有新东西了就停。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：穷尽式搜索、死代码检测、依赖审计——凡是&quot;要的是完整，不是固定条数&quot;的清扫任务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;流程&lt;/strong&gt;：&lt;code&gt;agent → 有新发现？→ 是：再来一轮 / 否：done&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 反复派活，每轮和&quot;已找到的&quot;去重，直到某一轮没有新东西
const seen = new Set();
const found = [];

while (true) {
  const { items } = await task({
    description: `Find dead code. Already found: ${[...seen].join(&quot;, &quot;) || &quot;(none)&quot;}.`,
    subagentType: &quot;analyzer&quot;,
    responseSchema: itemsSchema, // -&amp;gt; { items: [{ id, file }] }
  });
  const fresh = items.filter((i) =&amp;gt; !seen.has(i.id));
  if (fresh.length === 0) break; // 收敛：没有新东西了
  for (const i of fresh) { seen.add(i.id); found.push(i); }
}
found;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;为什么这么写&lt;/strong&gt;：把&quot;&lt;strong&gt;已经找到的东西&lt;/strong&gt;&quot;回灌进 &lt;code&gt;description&lt;/code&gt;，子智能体才知道该往哪儿找新的，否则它每轮都会重复报同一批。&lt;code&gt;seen&lt;/code&gt; 就是一个普通的 &lt;code&gt;Set&lt;/code&gt;，去重逻辑在代码里——可解释、可调试、可单测，这是静态模式给不了的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代价&lt;/strong&gt;：这是六种里&lt;strong&gt;唯一一个轮数不确定&lt;/strong&gt;的模式，token 消耗事先估不出来。而且&lt;strong&gt;去重键 &lt;code&gt;id&lt;/code&gt; 必须稳定&lt;/strong&gt;——如果子智能体每轮给出的 &lt;code&gt;id&lt;/code&gt; 都不太一样，这个循环永远不会收敛。&lt;code&gt;while (true)&lt;/code&gt; 上生产前一定要加最大轮数上限。&lt;/p&gt;
&lt;h3&gt;六种模式对照表&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;一句话&lt;/th&gt;
&lt;th&gt;什么时候用&lt;/th&gt;
&lt;th&gt;主要成本&lt;/th&gt;
&lt;th&gt;用 token 换质量？&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;分类后分发&lt;/td&gt;
&lt;td&gt;先打标，再按标路由&lt;/td&gt;
&lt;td&gt;混合类型的批量输入&lt;/td&gt;
&lt;td&gt;一次分类 + N 次处理&lt;/td&gt;
&lt;td&gt;否（反而省）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;扇出后汇总&lt;/td&gt;
&lt;td&gt;同一件活并发干 N 份再合并&lt;/td&gt;
&lt;td&gt;目录级审查、批量文档、日志&lt;/td&gt;
&lt;td&gt;N 个并发 agent loop&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;对抗式验证&lt;/td&gt;
&lt;td&gt;二次独立复核，活下来才算&lt;/td&gt;
&lt;td&gt;误报代价高的安全 / 合规审计&lt;/td&gt;
&lt;td&gt;findings × 验证者数量&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;是&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;生成后筛选&lt;/td&gt;
&lt;td&gt;多方案并发生成，代码里打分&lt;/td&gt;
&lt;td&gt;架构选型、方案探索&lt;/td&gt;
&lt;td&gt;N 倍生成 + 打分&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;是&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;锦标赛&lt;/td&gt;
&lt;td&gt;两两 PK，淘汰晋级&lt;/td&gt;
&lt;td&gt;主观标准下选最优&lt;/td&gt;
&lt;td&gt;N-1 场 judge（串行）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;是&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;循环直到达标&lt;/td&gt;
&lt;td&gt;一轮轮找，没新的就停&lt;/td&gt;
&lt;td&gt;穷尽搜索、死代码检测&lt;/td&gt;
&lt;td&gt;轮数不可预知&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;是&lt;/strong&gt;（换的是完整性）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;选型口径：&lt;strong&gt;前两种是&quot;把活干完&quot;，后四种是&quot;把活干好&quot;&lt;/strong&gt;。前两种该用就用，收益是确定性的；后四种要先问一句&quot;这个场景的误报/平庸方案的代价，值不值这几倍 token&quot;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;关掉动态子智能体&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;只要有子智能体，动态派发就是默认开的。&lt;/strong&gt; 如果你想保留子智能体、但只允许它们从常规的 &lt;code&gt;task&lt;/code&gt; 工具路径被调用（模型一个个派，不接受代码编排），就在中间件上关掉：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from deepagents import create_deep_agent
from langchain_quickjs import CodeInterpreterMiddleware

agent = create_deep_agent(
    model=&quot;openai:gpt-5.5&quot;,
    subagents=[{&quot;name&quot;: &quot;reviewer&quot;, &quot;description&quot;: &quot;Reviews code&quot;, &quot;system_prompt&quot;: &quot;Review code.&quot;}],
    middleware=[CodeInterpreterMiddleware(subagents=False)],  # 关掉代码里的 task()
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原文档同样给了 7 个厂商的版本，差别只在 &lt;code&gt;model=&lt;/code&gt; 一行，这里保留一版。&lt;/p&gt;
&lt;p&gt;这个开关适合的场景：你已经有一套稳定的静态委派提示词和工作流，不想让模型某天突然&quot;自由发挥&quot;写一段解释器代码；或者你需要在每次派发前做审批——因为 &lt;code&gt;task()&lt;/code&gt; 不逐次经过 &lt;code&gt;interrupt_on&lt;/code&gt;，关掉它是拿到逐次审批最省事的办法。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;常见坑&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;① 子智能体依然是无状态的。&lt;/strong&gt; 每次 &lt;code&gt;task()&lt;/code&gt; 都是全新的上下文，上一次的结论不会自动带过去。想让第二轮知道第一轮发现了什么，必须&lt;strong&gt;显式写进 &lt;code&gt;description&lt;/code&gt;&lt;/strong&gt;（就像&quot;循环直到达标&quot;里回灌 &lt;code&gt;seen&lt;/code&gt; 那样），或者塞进 &lt;code&gt;responseSchema&lt;/code&gt; 要求它自己带上。这点和 &lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 篇&lt;/a&gt; 的静态子智能体完全一致。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 并发一定要有上限。&lt;/strong&gt; &lt;code&gt;Promise.all(files.map(...))&lt;/code&gt; 看着优雅，但 200 个文件就是 200 个同时跑的 agent loop。上线前按 &lt;code&gt;chunk&lt;/code&gt; 分批、按 &lt;code&gt;limit&lt;/code&gt; 限流、或者只对&lt;strong&gt;筛选后的输入&lt;/strong&gt;扇出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 结果必须显式收敛。&lt;/strong&gt; 代码块最后要有一个&quot;值&quot;被交回去（原文每个例子的末尾都孤零零站着一个 &lt;code&gt;handled;&lt;/code&gt; / &lt;code&gt;issues;&lt;/code&gt; / &lt;code&gt;confirmed;&lt;/code&gt; / &lt;code&gt;best;&lt;/code&gt; / &lt;code&gt;found;&lt;/code&gt;——那&lt;strong&gt;不是笔误&lt;/strong&gt;，那是把结果送回模型的方式）。只循环不收敛，模型拿不到任何东西。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;④ 不能把不收敛的循环留在生产。&lt;/strong&gt; &lt;code&gt;while (true)&lt;/code&gt; 只适合&quot;每轮严格去重、必然收敛&quot;的场景。去重键不稳定、子智能体不听话、或者任务本身是开放的，都会变成烧钱黑洞。&lt;strong&gt;加最大轮数兜底。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑤ &lt;code&gt;task()&lt;/code&gt; 绕过了逐次审批。&lt;/strong&gt; 它从 &lt;code&gt;eval&lt;/code&gt; 内部派发，&lt;code&gt;interrupt_on&lt;/code&gt; 不生效。要审批就拦 &lt;code&gt;eval&lt;/code&gt; 工具本身，或者直接 &lt;code&gt;subagents=False&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑥ 没有 &lt;code&gt;responseSchema&lt;/code&gt; 就没有&quot;可编程的结果&quot;。&lt;/strong&gt; 拿到一段散文，&lt;code&gt;filter&lt;/code&gt; / &lt;code&gt;sort&lt;/code&gt; / 投票全都无从谈起，动态编排就退化成了&quot;并发地要几段文本&quot;。&lt;strong&gt;能用 schema 就用 schema。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑦ PTC 默认关闭。&lt;/strong&gt; 代码里想用 &lt;code&gt;tools.*&lt;/code&gt; 发现输入，必须在中间件上给白名单（&lt;code&gt;ptc=[&quot;glob&quot;]&lt;/code&gt;），否则解释器看不见文件系统。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑧ 这是 beta，本机也跑不起来。&lt;/strong&gt; 本机 &lt;code&gt;deepagents 0.7.5&lt;/code&gt; 没有解释器运行时、&lt;code&gt;langchain_quickjs&lt;/code&gt; 未安装，所以上面的代码骨架&lt;strong&gt;在本机只能读不能跑&lt;/strong&gt;。API 和生命周期行为在正式版前都可能变——迁移时记得锁版本。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;动态子智能体 = 把编排逻辑从提示词搬进代码。&lt;/strong&gt; 模型不再&quot;一次一个&quot;地调 &lt;code&gt;task&lt;/code&gt; 工具，而是在解释器里写 JS：循环、分支、&lt;code&gt;Promise.all&lt;/code&gt;、投票、排序。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;机制上只多了一个零件&lt;/strong&gt;：&lt;code&gt;CodeInterpreterMiddleware()&lt;/code&gt; 挂上、&lt;code&gt;subagents&lt;/code&gt; 非空，解释器里就出现内置的 &lt;code&gt;task()&lt;/code&gt; 全局函数，入参 &lt;code&gt;description&lt;/code&gt; / &lt;code&gt;subagentType&lt;/code&gt; / &lt;code&gt;responseSchema&lt;/code&gt;，一次调用跑完一整轮子智能体的 agent loop。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;它和 &lt;code&gt;task&lt;/code&gt; 工具是同一个能力的两种用法&lt;/strong&gt;：同一批子智能体、同一套配置，区别只在&lt;strong&gt;谁发起、并发几个、结果以什么形态回来&lt;/strong&gt;。但 &lt;code&gt;task()&lt;/code&gt; 不逐次走审批路径。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;六种模式共享一套姿势&lt;/strong&gt;：把工作握在变量里 → 用 &lt;code&gt;task()&lt;/code&gt; 派活 → 在代码里 &lt;code&gt;filter&lt;/code&gt;/&lt;code&gt;sort&lt;/code&gt;/投票收敛。分类、扇出是&quot;把活干完&quot;；对抗验证、生成筛选、锦标赛、循环到达标是&quot;用 token 换质量&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两个配套能力&lt;/strong&gt;：&lt;code&gt;tools.*&lt;/code&gt;（PTC，默认关，需白名单）负责&quot;发现输入&quot;，&lt;code&gt;mode=&quot;thread&quot;&lt;/code&gt; 负责&quot;跨轮持久化解释器变量&quot;；底层思路来自递归语言模型（RLM）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本机不可用&lt;/strong&gt;：&lt;code&gt;deepagents 0.7.5&lt;/code&gt; 包内无解释器运行时、&lt;code&gt;langchain_quickjs&lt;/code&gt; 未安装，官方标注 beta。本篇价值在于编排思路，代码骨架可平移到 LangGraph 或普通 Python。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;一次委派、职责隔离 → 用 &lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 篇&lt;/a&gt; 的静态子智能体；一批活要遍历完、要多视角互验、要按结果排序筛选 → 用代码编排。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;判断标准很简单：&lt;strong&gt;如果&quot;派几次、派给谁&quot;这件事你希望它是确定的，就把它写进代码。&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;延伸阅读&lt;/strong&gt;：&lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 静态子智能体配置面&lt;/a&gt; ｜ &lt;a href=&quot;/posts/deepagents-03-async/&quot;&gt;03 异步子智能体&lt;/a&gt; ｜ &lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;04 Subagents 模式原理&lt;/a&gt; ｜ &lt;a href=&quot;/posts/multiagent-14-appendix-tool-wrap/&quot;&gt;14 底层机制与抓包&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>01 Subagents：Deep Agents 的子智能体（内置 task 工具 + 一整套配置面）</title><link>https://aicooleo.cn/blog/posts/deepagents-01-subagents/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/deepagents-01-subagents/</guid><description>主管像项目经理，子智能体像外包团队——你把一整包脏活丢出去，只收回一页结论文档；中间那几十次搜索、读文件、试错重来，全留在外包团队的办公室里，不进你的会议纪要。</description><pubDate>Mon, 31 Aug 2026 22:34:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：主管像项目经理，子智能体像外包团队——你把一整包脏活丢出去，只收回一页结论文档；中间那几十次搜索、读文件、试错重来，全留在外包团队的办公室里，不进你的会议纪要。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;它解决什么问题&lt;/h2&gt;
&lt;p&gt;主管 Agent 的上下文窗口是&lt;strong&gt;最贵的资源&lt;/strong&gt;。一次 web 搜索回来 3000 token，读一个文件 8000 token，跑十次窗口就满了——而这些中间结果 99% 是一次性的，主管真正需要的只是最后那句结论。&lt;/p&gt;
&lt;p&gt;子智能体干的就是&lt;strong&gt;上下文隔离（context quarantine）&lt;/strong&gt;：脏活在一个独立上下文窗口里干完，只把最后一条 &lt;code&gt;AIMessage&lt;/code&gt; 交回主管。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;该用（✅）&lt;/strong&gt;：多步骤任务，中间产物会把主管上下文撑爆；需要专门指令或专门工具的领域（合同审查、SQL 查询、代码审查）；不同任务需要不同模型能力（长文档用大窗口模型，数值分析换另一个）；你希望主管只当&quot;协调者&quot;，专注高层调度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不该用（❌）&lt;/strong&gt;：简单的一步任务——派活本身就是一次额外的 LLM 调用；你需要保留中间上下文（子智能体不回传中间过程）；收益盖不过开销，调用次数、延迟、排错复杂度都会涨。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;注意&lt;/strong&gt;：本篇只讲&lt;strong&gt;同步&lt;/strong&gt;子智能体（主管阻塞等它跑完）。长任务、并行工作流、需要中途干预/取消的场景看 &lt;a href=&quot;/posts/deepagents-03-async/&quot;&gt;03 异步子智能体&lt;/a&gt;。调用次数的账见 &lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03 性能对比&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;整体机制&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;graph TB
    Main[&quot;主管 Supervisor&amp;lt;br/&amp;gt;（主 Agent）&quot;] --&amp;gt;|&quot;task 工具&amp;lt;br/&amp;gt;subagent_type=xxx&quot;| Sub[&quot;子智能体 Subagent&amp;lt;br/&amp;gt;独立上下文窗口&quot;]
    Sub --&amp;gt; R[&quot;研究 Research&quot;]
    Sub --&amp;gt; C[&quot;编码 Code&quot;]
    Sub --&amp;gt; G[&quot;通用 General-purpose&quot;]
    R --&amp;gt; Res[&quot;最终结果 Final Result&amp;lt;br/&amp;gt;只回一条摘要&quot;]
    C --&amp;gt; Res
    G --&amp;gt; Res
    Res --&amp;gt;|&quot;作为 ToolMessage 回传&quot;| Main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;① &lt;strong&gt;只有一个入口&lt;/strong&gt;：主管永远通过内置的 &lt;code&gt;task(description, subagent_type)&lt;/code&gt; 工具派活。&lt;code&gt;subagent_type&lt;/code&gt; 填的就是子智能体的 &lt;code&gt;name&lt;/code&gt;。&lt;br /&gt;
② &lt;strong&gt;上下文在两个箭头处断开&lt;/strong&gt;：向下的箭头只携带那段 &lt;code&gt;description&lt;/code&gt;，向上的箭头只携带一条最终消息。子智能体中间调了什么工具、失败了几次，主管一概看不见。&lt;br /&gt;
③ &lt;strong&gt;子智能体之间互不通信&lt;/strong&gt;：三个平级的隔离房间，唯一的共享总线是主管；要串联就得分两轮派活。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌────────────────────────────────────────────────┐
│ 主管 Supervisor（主 Agent）                      │
│ context = [用户问题] [task 调用] [子智能体摘要]   │
└───────────────────┬────────────────────────────┘
                    │ task(description, subagent_type)
                    ▼
   ┌────────────────────────────────────────┐
   │ 子智能体 Subagent（隔离的上下文窗口）    │
   │ search()    → 12 条结果    ┐            │
   │ read_file() → 3 个大文件   ├─ 全是脏活   │
   │ 重试 8 轮    → 全是噪音     ┘            │
   └──────────────────┬─────────────────────┘
                      │ 只回最后一条 AIMessage
                      ▼  主管 context 只多了这一小段摘要
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;和上层 04 / 14 篇什么关系&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;04 Subagents 子智能体&lt;/a&gt; 讲&lt;strong&gt;模式原理&lt;/strong&gt;：为什么是&quot;主管 + 子智能体即工具&quot;，上下文隔离为什么成立。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/multiagent-14-appendix-tool-wrap/&quot;&gt;14 手写 Tool 包装 vs Deep Agents 内置 subagents&lt;/a&gt; 用源码和抓包证明 &lt;code&gt;subagents=&lt;/code&gt; 展开后就是一个 &lt;code&gt;task&lt;/code&gt; 工具加一张注册表。&lt;/li&gt;
&lt;li&gt;本篇不重复以上原理，只回答&lt;strong&gt;工程问题&lt;/strong&gt;：每个字段怎么填、默认值是什么、什么时候继承什么时候覆盖、fork 怎么配、怎么流式、怎么排错。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;配置面（SubAgent 字段全表）&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;subagents=&lt;/code&gt; 收一个列表，元素可以是 &lt;code&gt;SubAgent&lt;/code&gt; 字典，也可以是 &lt;code&gt;CompiledSubAgent&lt;/code&gt;。字典式 &lt;code&gt;SubAgent&lt;/code&gt; 的全部字段如下。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;必填性&lt;/th&gt;
&lt;th&gt;默认行为&lt;/th&gt;
&lt;th&gt;说人话&lt;/th&gt;
&lt;th&gt;坑&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;必填&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;子智能体的唯一 ID，主管调 &lt;code&gt;task(subagent_type=...)&lt;/code&gt; 用的就是它&lt;/td&gt;
&lt;td&gt;会写进 &lt;code&gt;AIMessage&lt;/code&gt; 元数据和 LangSmith 的 &lt;code&gt;lc_agent_name&lt;/code&gt;，改名等于改过滤条件；叫 &lt;code&gt;general-purpose&lt;/code&gt; 时是&quot;替换默认那个&quot;而非新增&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;必填&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;写给&lt;strong&gt;主管&lt;/strong&gt;看的&quot;什么时候该派我&quot;说明书&lt;/td&gt;
&lt;td&gt;主管只靠它做路由，写成 &lt;code&gt;&quot;helps with stuff&quot;&lt;/code&gt; = 永远不被调用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;system_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mode=&quot;isolated&quot;&lt;/code&gt; 时必填&lt;/td&gt;
&lt;td&gt;不继承主 Agent&lt;/td&gt;
&lt;td&gt;写给&lt;strong&gt;子智能体自己&lt;/strong&gt;看的&quot;怎么干活、输出什么格式&quot;&lt;/td&gt;
&lt;td&gt;不填就是空提示词，输出格式全靠运气；&lt;code&gt;mode=&quot;fork&quot;&lt;/code&gt; 时退化成可选追加段&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;isolated&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;fork&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;isolated&quot;&lt;/code&gt;（只看到派活描述）&lt;/td&gt;
&lt;td&gt;决定子智能体开局看到什么：一段任务描述，还是父级整段对话&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;继承主 Agent 的全部工具&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;子智能体的工具白名单&lt;/td&gt;
&lt;td&gt;一旦写了就&lt;strong&gt;整体覆盖&lt;/strong&gt;而非追加；想保留文件工具得自己塞 &lt;code&gt;FilesystemMiddleware&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BaseChatModel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;继承主 Agent 的模型&lt;/td&gt;
&lt;td&gt;给某个子智能体单独换便宜模型或长窗口模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;middleware&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;不继承&lt;/strong&gt;，只叠加自己的&lt;/td&gt;
&lt;td&gt;给子智能体加日志、限流、文件工具白名单&lt;/td&gt;
&lt;td&gt;按 &lt;code&gt;.name&lt;/code&gt; 合并进默认栈：同名替换默认项，异名插在核心中间件之后；名字写错会静默失效&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interrupt_on&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;继承主 Agent&lt;/strong&gt;，子智能体值可覆盖&lt;/td&gt;
&lt;td&gt;子智能体的工具级人工审批&lt;/td&gt;
&lt;td&gt;没有 checkpointer 时形同虚设；值可以是 &lt;code&gt;True&lt;/code&gt; / &lt;code&gt;False&lt;/code&gt; / &lt;code&gt;InterruptOnConfig&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list[str]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;不继承&lt;/strong&gt;（唯一例外是 GP 子智能体）&lt;/td&gt;
&lt;td&gt;给这个子智能体单独挂技能目录&lt;/td&gt;
&lt;td&gt;双向完全隔离：父看不到子的技能，子也看不到父的；只有传了才创建 &lt;code&gt;SkillsMiddleware&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;response_format&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pydantic / Strategy / dict&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;无（父级收到最后一条文本）&lt;/td&gt;
&lt;td&gt;让子智能体回结构化 JSON&lt;/td&gt;
&lt;td&gt;需 &lt;code&gt;&amp;gt;=0.5.3&lt;/code&gt;；父级拿到的是&lt;strong&gt;字符串化 JSON&lt;/strong&gt;，还得自己 &lt;code&gt;json.loads&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;permissions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list[FilesystemPermission]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;继承父级&lt;/td&gt;
&lt;td&gt;限制子智能体只能读写哪些路径&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;设置就整体替换&lt;/strong&gt;父级规则；按声明顺序匹配，第一条命中即生效&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;继承 vs 覆盖：最容易踩的四个字段&lt;/h3&gt;
&lt;p&gt;全篇最值钱的就是这张表——&quot;我以为它叠加，其实它替换&quot;。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;不写时&lt;/th&gt;
&lt;th&gt;写了时&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;继承主 Agent 全部工具&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;整体覆盖&lt;/strong&gt;，不是追加&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interrupt_on&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;继承主 Agent&lt;/td&gt;
&lt;td&gt;子智能体值覆盖默认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;不继承&lt;/strong&gt;（只有 GP 子智能体继承）&lt;/td&gt;
&lt;td&gt;用子智能体自己的技能目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;permissions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;继承父级&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;整体替换&lt;/strong&gt;父级规则，不是合并&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;middleware&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不继承&lt;/td&gt;
&lt;td&gt;按 &lt;code&gt;.name&lt;/code&gt; 合并进默认栈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt; / &lt;code&gt;response_format&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;分别继承模型 / 无&lt;/td&gt;
&lt;td&gt;覆盖&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;# 主 Agent 的工具是 [search, read_file, write_file]
# 下面这个子智能体只会拿到 [search]，文件工具全没了
searcher = {
    &quot;name&quot;: &quot;searcher&quot;,
    &quot;description&quot;: &quot;只做网络检索并返回摘要&quot;,
    &quot;system_prompt&quot;: &quot;用 search 检索，返回不超过 200 字的摘要。&quot;,
    &quot;tools&quot;: [search],  # 整体覆盖，不是&quot;在继承基础上再加一个 search&quot;
}

# 想保留文件工具，就得自己把 FilesystemMiddleware 带进来
from deepagents import FilesystemMiddleware

searcher_with_fs = {
    **searcher,
    &quot;middleware&quot;: [FilesystemMiddleware(tools=[&quot;read_file&quot;, &quot;write_file&quot;])],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;两种定义方式&lt;/h2&gt;
&lt;h3&gt;字典式 &lt;code&gt;SubAgent&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;就是上面那张表的字段全集，90% 的场景用它就够了。声明式、可序列化，字段语义由 Deep Agents 负责翻译成中间件栈。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;CompiledSubAgent&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;要更复杂的编排（自己的图、自己的循环、非 &lt;code&gt;create_agent&lt;/code&gt; 的结构）时，把编译好的 LangGraph 图塞进去：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from deepagents import CompiledSubAgent, create_deep_agent
from langchain.agents import create_agent

# ① 先用 create_agent 或 LangGraph 图 API 编译一个 agent
custom_graph = create_agent(
    model=&quot;openai:gpt-5.5&quot;, tools=[analyze_contract], system_prompt=&quot;你是数据分析专家……&quot;
)

# ② 再包成 CompiledSubAgent；runnable 必须已经 .compile()
custom_subagent = CompiledSubAgent(
    name=&quot;data-analyzer&quot;,
    description=&quot;分析结构化数据并给出结论&quot;,
    runnable=custom_graph,
)

agent = create_deep_agent(model=&quot;openai:gpt-5.5&quot;, subagents=[custom_subagent])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;字段只有四个：&lt;code&gt;name&lt;/code&gt;、&lt;code&gt;description&lt;/code&gt;、&lt;code&gt;runnable&lt;/code&gt;，以及 fork 模式下的 &lt;code&gt;mode&lt;/code&gt;。三条硬性要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;runnable&lt;/code&gt; 必须是&lt;strong&gt;已经 &lt;code&gt;.compile()&lt;/code&gt; 过&lt;/strong&gt;的图，不能是未编译的 builder。&lt;/li&gt;
&lt;li&gt;图的 state schema &lt;strong&gt;必须含 &lt;code&gt;messages&lt;/code&gt; 键&lt;/strong&gt;——子智能体就是靠它把结果传回主管的，缺了这条回传链路直接断。&lt;/li&gt;
&lt;li&gt;它是&lt;strong&gt;原样使用&lt;/strong&gt;的：不继承 &lt;code&gt;create_deep_agent(state_schema=...)&lt;/code&gt;，要自定义 state 字段得自己用兼容 schema 编译。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结果回传规则和字典式一致：state 里 &lt;code&gt;structured_response&lt;/code&gt; 非 &lt;code&gt;None&lt;/code&gt; 就序列化成 JSON 当 &lt;code&gt;ToolMessage&lt;/code&gt; 内容；否则取最后一条非空 &lt;code&gt;AIMessage&lt;/code&gt; 的文本。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;怎么选&lt;/strong&gt;：能用字典就用字典——字段有默认值、自动接中间件栈、后续升级不容易被内部实现变更打到。只有字典表达不了的图结构才上 &lt;code&gt;CompiledSubAgent&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;默认的 general-purpose 子智能体&lt;/h2&gt;
&lt;p&gt;只要你没有提供一个叫 &lt;code&gt;general-purpose&lt;/code&gt; 的同步子智能体，Deep Agents 就&lt;strong&gt;自动注入&lt;/strong&gt;一个。它天生具备：自己的默认 system prompt（叠加 profile 覆盖）、和主 Agent &lt;strong&gt;一样的全部工具&lt;/strong&gt;、&lt;strong&gt;一样的模型&lt;/strong&gt;（除非你覆盖），以及配置了 skills 时&lt;strong&gt;继承主 Agent 的 skills&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;替换、改名、关掉&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;替换&lt;/strong&gt;——在 &lt;code&gt;subagents&lt;/code&gt; 里放一个 &lt;code&gt;name=&quot;general-purpose&quot;&lt;/code&gt; 的项，默认那个就不注入了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;agent = create_deep_agent(
    model=&quot;openai:gpt-5.5&quot;,
    tools=[internet_search],
    subagents=[
        {
            &quot;name&quot;: &quot;general-purpose&quot;,  # 同名 = 替换默认项
            &quot;description&quot;: &quot;通用研究员，负责多步检索与综合&quot;,
            &quot;system_prompt&quot;: &quot;你是通用助手，检索后只回结论。&quot;,
            &quot;tools&quot;: [internet_search],
            &quot;model&quot;: &quot;openai:gpt-5.5&quot;,  # 给委派任务单独指定模型
        },
    ],
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;改名 / 改提示词&lt;/strong&gt;——不要动 &lt;code&gt;subagents&lt;/code&gt;，改&lt;strong&gt;当前生效的 harness profile&lt;/strong&gt;。0.7.5 里 profile 用 &lt;code&gt;register_harness_profile&lt;/code&gt; 按 &lt;code&gt;provider:model&lt;/code&gt; 或 provider 注册：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from deepagents import (
    GeneralPurposeSubagentProfile,
    HarnessProfile,
    register_harness_profile,
)

register_harness_profile(
    &quot;openai:gpt-5.5&quot;,
    HarnessProfile(
        general_purpose_subagent=GeneralPurposeSubagentProfile(
            description=&quot;通用研究助手，适合需要多轮检索的复杂问题&quot;,
        )
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关掉&lt;/strong&gt;——两件事一起做，缺一不可：① 在生效的 harness profile 上设 &lt;code&gt;GeneralPurposeSubagentProfile(enabled=False)&lt;/code&gt;；② 调 &lt;code&gt;create_deep_agent&lt;/code&gt; 时 &lt;code&gt;subagents=&lt;/code&gt; 不传任何同步子智能体。因为 Deep Agents &lt;strong&gt;只有在至少存在一个同步子智能体时&lt;/strong&gt;才挂 &lt;a href=&quot;https://reference.langchain.com/python/deepagents/middleware/subagents/SubAgentMiddleware&quot;&gt;&lt;code&gt;SubAgentMiddleware&lt;/code&gt;&lt;/a&gt; 和 &lt;code&gt;task&lt;/code&gt; 工具。两个条件都满足，Agent 才真正没有任何委派能力。（异步子智能体不受影响，它走自己的中间件和工具。）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：别想着用 &lt;code&gt;excluded_middleware&lt;/code&gt; 绕。&lt;code&gt;SubAgentMiddleware&lt;/code&gt; 是必需脚手架，把它列进去会直接抛 &lt;code&gt;ValueError&lt;/code&gt;。&lt;code&gt;general_purpose_subagent.enabled = False&lt;/code&gt; 才是官方路径。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;什么时候该用它&lt;/h3&gt;
&lt;p&gt;纯粹为了隔离上下文、不需要专门行为时，它就是最优解。主管不用自己做 10 次 web 搜索，一句 &lt;code&gt;task(subagent_type=&quot;general-purpose&quot;, description=&quot;调研量子计算趋势&quot;)&lt;/code&gt; 就够了：子智能体在内部做完所有检索，只回一段摘要。&lt;/p&gt;
&lt;h3&gt;skills 继承规则&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;general-purpose 子智能体&lt;/strong&gt;：自动继承主 Agent 的 skills&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自定义子智能体&lt;/strong&gt;：默认&lt;strong&gt;不继承&lt;/strong&gt;，要用 &lt;code&gt;skills=&lt;/code&gt; 显式给&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只有配了 &lt;code&gt;skills&lt;/code&gt; 的子智能体才会拿到 &lt;code&gt;SkillsMiddleware&lt;/code&gt; 实例，技能状态双向完全隔离。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;research_subagent = {
    &quot;name&quot;: &quot;researcher&quot;,
    &quot;description&quot;: &quot;带专门技能的研究助手&quot;,
    &quot;system_prompt&quot;: &quot;你是研究员。&quot;,
    &quot;skills&quot;: [&quot;/skills/researcher/&quot;],  # 只吃自己的技能目录
}

agent = create_deep_agent(
    model=&quot;openai:gpt-5.5&quot;,
    skills=[&quot;/skills/main/&quot;],  # 主 Agent 和 GP 子智能体吃这份
    subagents=[research_subagent],  # researcher 只吃自己那份
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;分叉子智能体（&lt;code&gt;mode: &quot;fork&quot;&lt;/code&gt;）&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;版本要求&lt;/strong&gt;：fork 需要 &lt;code&gt;deepagents&amp;gt;=0.7.13&lt;/code&gt;，且官方标注为 &lt;strong&gt;beta&lt;/strong&gt;，API 和行为可能随版本变化。&lt;strong&gt;本机装的是 &lt;code&gt;deepagents 0.7.5&lt;/code&gt;，实测 &lt;code&gt;SubAgent&lt;/code&gt; TypedDict 里根本没有 &lt;code&gt;mode&lt;/code&gt; 字段，包内也没有任何 &lt;code&gt;fork&lt;/code&gt; 实现——fork 在本机不可用。&lt;/strong&gt; 下面内容来自官方文档，落地前必须先升级版本。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;默认 &lt;code&gt;mode: &quot;isolated&quot;&lt;/code&gt;：子智能体只看到你给的那段任务描述，对&quot;事情是怎么走到这一步的&quot;一无所知。fork 反过来，它继承父级的&lt;strong&gt;完整对话历史&lt;/strong&gt;和&lt;strong&gt;一模一样的 system prompt&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;什么时候需要 fork？当子智能体的任务是&lt;strong&gt;接着父级已经开的头往下做&lt;/strong&gt;——父级已经诊断出问题，让子智能体去写修复；父级查完事故，让子智能体去起草复盘报告。fork 是定义子智能体时就定下来的模式，不是调用时临时选的。&lt;/p&gt;
&lt;h3&gt;配置&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;SubAgent&lt;/code&gt; 上设 &lt;code&gt;mode: &quot;fork&quot;&lt;/code&gt;。除了 &lt;code&gt;skills&lt;/code&gt;，其余字段都可用（&lt;code&gt;name&lt;/code&gt;、&lt;code&gt;description&lt;/code&gt;、&lt;code&gt;tools&lt;/code&gt;、&lt;code&gt;model&lt;/code&gt;、&lt;code&gt;middleware&lt;/code&gt;、&lt;code&gt;interrupt_on&lt;/code&gt;、&lt;code&gt;permissions&lt;/code&gt;、&lt;code&gt;response_format&lt;/code&gt;）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from deepagents import create_deep_agent

def read_diff(path: str) -&amp;gt; str:
    &quot;&quot;&quot;读取某个文件的 diff。&quot;&quot;&quot;
    return f&quot;diff for {path}&quot;

comment_writer = {
    &quot;name&quot;: &quot;comment-writer&quot;,
    &quot;description&quot;: &quot;接着正在进行的 PR 评审，起草评审意见&quot;,
    &quot;mode&quot;: &quot;fork&quot;,
    &quot;tools&quot;: [read_diff],
    # 不写 system_prompt：fork 自带父级的，追加会破坏提示词缓存
}

agent = create_deep_agent(&quot;openai:gpt-5.5&quot;, tools=[read_diff], subagents=[comment_writer])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个额外规则：&lt;strong&gt;&lt;code&gt;skills&lt;/code&gt; 会被拒绝&lt;/strong&gt;（fork 继承的是父级的对话，不是父级之外的技能）；&lt;strong&gt;&lt;code&gt;system_prompt&lt;/code&gt; 允许但不建议&lt;/strong&gt;，它会被追加到父级继承来的 prompt 后面作为补充段，而这通常会打断提示词缓存（prompt cache）——除非确实有 fork 专属指令，否则留空。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CompiledSubAgent&lt;/code&gt; 也支持 &lt;code&gt;mode: &quot;fork&quot;&lt;/code&gt;，但它保持自己那份 system prompt，因为图已经编译好了。&lt;/p&gt;
&lt;h3&gt;它到底看到什么&lt;/h3&gt;
&lt;p&gt;fork 拿到的&lt;strong&gt;不是&lt;/strong&gt;一份新任务描述，而是父级自己的对话，只改一处：&lt;strong&gt;丢掉最后那条触发派活的 AI 消息，换成一段短前言&lt;/strong&gt;，声明上面的消息是&quot;接续&quot;而不是新请求。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 父级在派活前一刻拥有的消息列表
[
    HumanMessage(&quot;看看 PR #482 的改动&quot;),
    AIMessage(&quot;&quot;, tool_calls=[{&quot;name&quot;: &quot;read_diff&quot;, &quot;args&quot;: {&quot;path&quot;: &quot;src/auth/session.py&quot;}}]),
    ToolMessage(&quot;- session token 明文打日志\n- refresh 时没校验过期&quot;, tool_call_id=&quot;1&quot;),
    AIMessage(&quot;发现两个问题：token 明文打日志、refresh 没校验过期。&quot;),
    HumanMessage(&quot;找得好。给这两个问题起草评审意见。&quot;),
    AIMessage(&quot;&quot;, tool_calls=[{&quot;name&quot;: &quot;task&quot;, &quot;args&quot;: {&quot;subagent_type&quot;: &quot;comment-writer&quot;, &quot;description&quot;: &quot;给上面发现的两个问题起草评审意见。&quot;}}]),
]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# fork 实际看到的消息列表
[
    HumanMessage(&quot;看看 PR #482 的改动&quot;),
    AIMessage(&quot;&quot;, tool_calls=[{&quot;name&quot;: &quot;read_diff&quot;, &quot;args&quot;: {&quot;path&quot;: &quot;src/auth/session.py&quot;}}]),
    ToolMessage(&quot;- session token 明文打日志\n- refresh 时没校验过期&quot;, tool_call_id=&quot;1&quot;),
    AIMessage(&quot;发现两个问题：token 明文打日志、refresh 没校验过期。&quot;),
    HumanMessage(&quot;找得好。给这两个问题起草评审意见。&quot;),
    HumanMessage(&quot;你现在作为刚被调用的子智能体继续。给上面发现的两个问题起草评审意见。&quot;),
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;差异只有两条，但很关键：&lt;/p&gt;
&lt;p&gt;① &lt;strong&gt;最后那条 &lt;code&gt;AIMessage&lt;/code&gt; 被删掉了&lt;/strong&gt;。那条消息本身就是&quot;调用 &lt;code&gt;task&lt;/code&gt;&quot;这个动作，留着会让 fork 看到&quot;我要派活给自己&quot;这种自指结构。删掉之后，对话历史上最后一个发言者变成 &lt;code&gt;HumanMessage&lt;/code&gt;，fork 自然就会接着往下答。
② &lt;strong&gt;换成一段 continuation 前言&lt;/strong&gt;。原文里那段派活的 &lt;code&gt;description&lt;/code&gt; 被改写成 &lt;code&gt;HumanMessage&lt;/code&gt;，措辞是&quot;你现在作为刚被调用的子智能体继续&quot;——把语义从&quot;这是一个新请求&quot;提升成&quot;这是刚才那件事的延续&quot;。&lt;/p&gt;
&lt;p&gt;fork 跑完后，它的回答作为一条普通工具结果回到父级，父级从原来断掉的地方无缝接上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;顺带的收益：提示词缓存&lt;/strong&gt;。前缀和父级完全一致，fork 可以直接复用父级已经建立的缓存，不用冷启动。但如果 fork 用了一堆父级没用过的工具，导致工具定义段不同，缓存还是会 miss。&lt;/p&gt;
&lt;h3&gt;fork 还是 isolated&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;isolated（默认）&lt;/th&gt;
&lt;th&gt;fork&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;上下文&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;只有你传进去的任务描述&lt;/td&gt;
&lt;td&gt;父级完整对话历史 + 同款 system prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;system prompt / skills&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;都在子智能体上自己设&lt;/td&gt;
&lt;td&gt;skills 不可设；system prompt 只会追加到父级的（破坏缓存，通常留空）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;能不能再派活&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;能用 &lt;code&gt;task&lt;/code&gt; 工具&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;不能用 &lt;code&gt;task&lt;/code&gt;&lt;/strong&gt;，必须自己把活干完&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;最适合&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;不太依赖前情提要的独立工作&lt;/td&gt;
&lt;td&gt;接续父级已经开始的调查&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;动态子智能体（只指路）&lt;/h2&gt;
&lt;p&gt;原文还讲了一种走&lt;strong&gt;解释器运行时&lt;/strong&gt;的玩法：挂上解释器中间件后，Agent 可以从&lt;strong&gt;代码里&lt;/strong&gt;派子智能体——循环、分支、批量并行，程序化地汇总结果，这叫动态子智能体（dynamic subagents）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;注意&lt;/strong&gt;：这条路依赖解释器运行时（interpreter runtime），官方标注 &lt;strong&gt;beta&lt;/strong&gt;，且&lt;strong&gt;本机 0.7.5 包内没有解释器运行时&lt;/strong&gt;，本篇不展开。细节见 &lt;a href=&quot;/posts/deepagents-02-dynamic/&quot;&gt;02 动态子智能体&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;结构化输出&lt;/h2&gt;
&lt;p&gt;给子智能体设 &lt;code&gt;response_format&lt;/code&gt;，父级收到的就不再是自由文本，而是可解析的 JSON。子智能体跑完时，它的结构化响应会被 JSON 序列化，&lt;strong&gt;作为 &lt;code&gt;ToolMessage&lt;/code&gt; 的内容&lt;/strong&gt;交给父级。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;注意&lt;/strong&gt;：子智能体的结构化输出需要 &lt;code&gt;deepagents&amp;gt;=0.5.3&lt;/code&gt;（本机 0.7.5 满足）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;schema 接受 &lt;code&gt;create_agent&lt;/code&gt; 支持的一切：Pydantic 模型、&lt;code&gt;ToolStrategy(...)&lt;/code&gt;、&lt;code&gt;ProviderStrategy(...)&lt;/code&gt;，或者裸 schema 类型。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pydantic import BaseModel, Field
from deepagents import create_deep_agent

class ResearchFindings(BaseModel):
    &quot;&quot;&quot;研究任务的结构化结论。&quot;&quot;&quot;
    summary: str = Field(description=&quot;结论摘要&quot;)
    confidence: float = Field(description=&quot;置信度，0 到 1&quot;)
    sources: list[str] = Field(description=&quot;来源 URL 列表&quot;)

research_subagent = {
    &quot;name&quot;: &quot;researcher&quot;,
    &quot;description&quot;: &quot;研究话题并返回结构化结论&quot;,
    &quot;system_prompt&quot;: &quot;彻底研究给定话题，返回你的结论。&quot;,
    &quot;tools&quot;: [web_search],  # 你自己的检索工具
    &quot;response_format&quot;: ResearchFindings,  # 父级拿到的是 JSON 字符串
}

agent = create_deep_agent(model=&quot;openai:gpt-5.5&quot;, subagents=[research_subagent])
# 父级 ToolMessage 内容是序列化 JSON：
# &apos;{&quot;summary&quot;: &quot;...&quot;, &quot;confidence&quot;: 0.87, &quot;sources&quot;: [&quot;https://...&quot;]}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个实操要点：父级拿到的是&lt;strong&gt;字符串&lt;/strong&gt;不是 dict，用它之前记得 &lt;code&gt;json.loads&lt;/code&gt;；结构化输出会顶掉默认的&quot;取最后一条消息文本&quot;逻辑，两者不叠加。&lt;/p&gt;
&lt;h2&gt;流式与可观测性&lt;/h2&gt;
&lt;h3&gt;子智能体进度流&lt;/h3&gt;
&lt;p&gt;用 &lt;code&gt;stream_events&lt;/code&gt; 能拿到分类型的投影——&lt;code&gt;messages&lt;/code&gt;、&lt;code&gt;subagents&lt;/code&gt;、&lt;code&gt;tool_calls&lt;/code&gt; 各自独立迭代。最简单的用法是迭代 &lt;code&gt;stream.subagents&lt;/code&gt;，跟踪每个委派任务从启动、运行到完成的全程：每个 handle 上有 &lt;code&gt;.name&lt;/code&gt;、&lt;code&gt;.messages&lt;/code&gt;、&lt;code&gt;.tool_calls&lt;/code&gt;、&lt;code&gt;.output&lt;/code&gt;、&lt;code&gt;.status&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;stream = agent.stream_events(
    {&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;调研一个量子计算的最新进展。&quot;}]},
    version=&quot;v3&quot;,
)

coordinator_messages: list[str] = []
for name, item in stream.interleave(&quot;messages&quot;, &quot;subagents&quot;):
    if name == &quot;messages&quot;:
        print(&quot;[coordinator]&quot;, item.text)  # 主管自己的输出
        coordinator_messages.append(item.text)
    else:
        print(f&quot;[{item.name}] started&quot;)  # 子智能体开始干活
        for message in item.messages:
            print(f&quot;[{item.name}]&quot;, message.text)
        print(f&quot;[{item.name}] status: {item.status}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要看见子智能体真的被调用，通常还得在主管的 system prompt 里下命令，比如&quot;你是项目协调者，自己不掌握研究知识；每个用户请求都必须调用 &lt;code&gt;task()&lt;/code&gt; 并把 &lt;code&gt;subagent_type&lt;/code&gt; 设为 &lt;code&gt;research-agent&lt;/code&gt;，绝不自己回答研究问题&quot;。&lt;/p&gt;
&lt;p&gt;原文这里给每个 provider 各写了一遍完整示例，逻辑完全相同，&lt;strong&gt;换 provider 只改 &lt;code&gt;model&lt;/code&gt; 这一行&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;&lt;code&gt;model=&lt;/code&gt; 写法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;&lt;code&gt;google_genai:gemini-3.6-flash&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;openai:gpt-5.5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;anthropic:claude-sonnet-5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenRouter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;openrouter:z-ai/glm-5.2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fireworks&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fireworks:accounts/fireworks/models/glm-5p2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Baseten&lt;/td&gt;
&lt;td&gt;&lt;code&gt;baseten:zai-org/GLM-5.2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ollama&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ollama:north-mini-code-1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;LangSmith 里的子智能体标记&lt;/h3&gt;
&lt;p&gt;子智能体跑出来的每一个 run，都会在自己的 metadata 里带上 &lt;code&gt;lc_agent_name&lt;/code&gt;，值就是子智能体的 &lt;code&gt;name&lt;/code&gt;（例如 &lt;code&gt;{&apos;lc_agent_name&apos;: &apos;research-agent&apos;}&lt;/code&gt;）。主管的 run 也带这个名字。这就是&quot;按子智能体过滤&quot;的全部原理。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 原文附了一个公开的 LangSmith trace 示例，可以对照看主管 trace 和子智能体 run 的差异：&lt;a href=&quot;https://smith.langchain.com/public/8690f61a-2e91-442f-b772-589ad1e807c7/r&quot;&gt;https://smith.langchain.com/public/8690f61a-2e91-442f-b772-589ad1e807c7/r&lt;/a&gt;（fork 示例）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;UI 里过滤&lt;/strong&gt;：打开 tracing 项目 → 切到 &lt;strong&gt;Runs&lt;/strong&gt; 视图 → &lt;strong&gt;Add filter&lt;/strong&gt; 选 &lt;strong&gt;Metadata&lt;/strong&gt; → Key 填 &lt;code&gt;lc_agent_name&lt;/code&gt;，Value 填子智能体名（如 &lt;code&gt;coordinator&lt;/code&gt;）。筛出来的就是这个子智能体产出的全部 run，可以存成命名视图复用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SDK 里过滤&lt;/strong&gt;：用 &lt;code&gt;has&lt;/code&gt; 比较器匹配 metadata 键值对。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langsmith import Client

client = Client()

# 只捞某个子智能体的 run
runs = client.list_runs(
    project_name=&quot;&amp;lt;your-project&amp;gt;&quot;,
    filter=&apos;has(metadata, \&apos;{&quot;lc_agent_name&quot;: &quot;research-agent&quot;}\&apos;)&apos;,
)

# 捞&quot;任意命名子智能体&quot;的 run（只要带 lc_agent_name 键），以此排除主 Agent
subagent_runs = client.list_runs(
    project_name=&quot;&amp;lt;your-project&amp;gt;&quot;,
    filter=&quot;has(metadata, &apos;lc_agent_name&apos;)&quot;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;同一个工具在多个智能体间共用&lt;/h3&gt;
&lt;p&gt;工具里能读到&quot;我是谁&quot;，靠的就是同一个 &lt;code&gt;lc_agent_name&lt;/code&gt;，只是这次从 &lt;code&gt;runtime.config&lt;/code&gt; 的 metadata 里取：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import ToolRuntime, tool

@tool
def shared_lookup(query: str, runtime: ToolRuntime) -&amp;gt; str:
    &quot;&quot;&quot;查询信息。&quot;&quot;&quot;
    agent_name = runtime.config.get(&quot;metadata&quot;, {}).get(&quot;lc_agent_name&quot;)
    if agent_name == &quot;fact-checker&quot;:
        return strict_lookup(query)
    return general_lookup(query)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外两条运行期事实：&lt;strong&gt;runtime context 会自动往下透传&lt;/strong&gt;——父级 &lt;code&gt;invoke(context=...)&lt;/code&gt; 传的那份值，每个子智能体 run 和它内部的工具都能通过 &lt;code&gt;runtime.context&lt;/code&gt; 读到（要按子智能体区分，就在 context 类型上开独立字段，或用 &lt;code&gt;researcher:max_depth&lt;/code&gt; 这种命名空间 key）。所以 &lt;code&gt;runtime.context&lt;/code&gt; 管&quot;每个智能体不同的设置&quot;，&lt;code&gt;runtime.config&lt;/code&gt; 的 metadata 管&quot;我是谁&quot;，两者可以组合使用。&lt;/p&gt;
&lt;h2&gt;最佳实践&lt;/h2&gt;
&lt;h3&gt;description 要写清楚&lt;/h3&gt;
&lt;p&gt;主管就是靠它做路由决策的。✅ 好：&lt;code&gt;&quot;分析财务数据并生成带置信度的投资洞察&quot;&lt;/code&gt;；❌ 差：&lt;code&gt;&quot;搞金融的&quot;&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;system prompt 要详细&lt;/h3&gt;
&lt;p&gt;把工具用法、步骤和输出格式都写进去：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;research_subagent = {
    &quot;name&quot;: &quot;research-agent&quot;,
    &quot;description&quot;: &quot;用 web 搜索做深度研究并综合结论&quot;,
    &quot;system_prompt&quot;: &quot;&quot;&quot;你是严谨的研究员。你的任务是：
1. 把研究问题拆成可搜索的查询
2. 用 internet_search 找相关信息
3. 综合成全面但精简的摘要
4. 下结论时标注来源

输出格式：摘要（2-3 段）+ 关键发现（要点）+ 来源（带 URL）
回答控制在 500 字以内，保持上下文干净。&quot;&quot;&quot;,
    &quot;tools&quot;: [internet_search],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;工具集要最小&lt;/h3&gt;
&lt;p&gt;只给真正需要的工具，既提升专注度也改善安全性：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ✅ 好：聚焦的工具集
email_agent = {&quot;name&quot;: &quot;email-sender&quot;, &quot;tools&quot;: [send_email, validate_email]}

# ❌ 差：工具太多太杂
email_agent_bad = {
    &quot;name&quot;: &quot;email-sender&quot;,
    &quot;tools&quot;: [send_email, web_search, database_query, format_document],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;按任务选模型&lt;/h3&gt;
&lt;p&gt;不同模型擅长不同的事，一个 Agent 里可以混着用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;subagents = [
    {
        &quot;name&quot;: &quot;contract-reviewer&quot;,
        &quot;description&quot;: &quot;审阅法律文档与合同&quot;,
        &quot;system_prompt&quot;: &quot;你是资深法律审阅者……&quot;,
        &quot;tools&quot;: [read_document, analyze_contract],
        &quot;model&quot;: &quot;google_genai:gemini-3.6-flash&quot;,  # 大上下文，吃长文档
    },
    {
        &quot;name&quot;: &quot;financial-analyst&quot;,
        &quot;description&quot;: &quot;分析财务数据与市场趋势&quot;,
        &quot;system_prompt&quot;: &quot;你是资深财务分析师……&quot;,
        &quot;tools&quot;: [get_stock_price, analyze_fundamentals],
        &quot;model&quot;: &quot;openai:gpt-5.5&quot;,  # 数值分析更强
    },
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;让子智能体只回精简结果&lt;/h3&gt;
&lt;p&gt;明确要求&quot;给摘要，不给原始数据&quot;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data_analyst = {
    &quot;system_prompt&quot;: &quot;&quot;&quot;分析数据并返回：
1. 关键洞察（3-5 条）
2. 总体置信度
3. 建议的下一步

不要包含：原始数据、中间计算过程、详细的工具输出。
回答控制在 300 字以内。&quot;&quot;&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常见坑&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;主管不派活，什么都自己干。&lt;/strong&gt; 三个解法依次试：① 把 &lt;code&gt;description&lt;/code&gt; 写具体（&lt;code&gt;&quot;用 web 搜索对特定话题做深度研究，需要多次搜索时使用&quot;&lt;/code&gt; 好过 &lt;code&gt;&quot;helps with stuff&quot;&lt;/code&gt;）；② 在主管的 system prompt 里明确下令——&lt;code&gt;&quot;重要：复杂任务请用 task() 工具委派给你的子智能体，这能保持你的上下文干净并提升效果&quot;&lt;/code&gt;；③ 确认子智能体确实注入了（见下一条）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;明明配了子智能体，&lt;code&gt;task&lt;/code&gt; 工具却不存在。&lt;/strong&gt; 检查是不是把 &lt;code&gt;general_purpose_subagent&lt;/code&gt; 关掉了却没传任何同步子智能体——只有至少一个同步子智能体存在时 &lt;code&gt;SubAgentMiddleware&lt;/code&gt; 才挂上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;以为 &lt;code&gt;tools&lt;/code&gt; 是追加。&lt;/strong&gt; 它是整体覆盖。写 &lt;code&gt;&quot;tools&quot;: [search]&lt;/code&gt; 之后文件工具全没了。同理 &lt;code&gt;permissions&lt;/code&gt; 是整体替换父级规则。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;上下文还是爆了。&lt;/strong&gt; 子智能体回了一条又长又啰嗦的结论，等于没隔离。两个办法：① 硬性限制——&lt;code&gt;&quot;重要：只返回必要的摘要，不要包含原始数据、中间搜索结果或详细工具输出，控制在 500 字以内&quot;&lt;/code&gt;；② 让它把大数据落到文件系统——&lt;code&gt;&quot;数据量大时：先把原始数据存到 /data/raw_results.txt，再分析，最后只返回分析摘要&quot;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主管选错子智能体。&lt;/strong&gt; 描述之间要有区分度，别造两个语义重叠的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;subagents = [
    {
        &quot;name&quot;: &quot;quick-researcher&quot;,
        &quot;description&quot;: &quot;用于简单快速的研究问题，1-2 次搜索即可。需要基础事实或定义时使用。&quot;,
        &quot;system_prompt&quot;: &quot;你是 quick-researcher 子智能体。&quot;,
    },
    {
        &quot;name&quot;: &quot;deep-researcher&quot;,
        &quot;description&quot;: &quot;用于复杂深度研究，需要多次搜索、综合与分析。写综合报告时使用。&quot;,
        &quot;system_prompt&quot;: &quot;你是 deep-researcher 子智能体。&quot;,
    },
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;fork 相关&lt;/strong&gt;：&lt;code&gt;skills&lt;/code&gt; 传了会被拒；&lt;code&gt;system_prompt&lt;/code&gt; 传了会追加到父级 prompt 后面并破坏提示词缓存；fork 里&lt;strong&gt;不能用 &lt;code&gt;task&lt;/code&gt;&lt;/strong&gt;，它不能再往下派活。外加版本门槛 &lt;code&gt;&amp;gt;=0.7.13&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结构化输出&lt;/strong&gt;：父级 &lt;code&gt;ToolMessage&lt;/code&gt; 里是 JSON &lt;strong&gt;字符串&lt;/strong&gt;，别直接当 dict 用；另外它顶掉默认的&quot;取最后一条文本&quot;逻辑。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一个工具、一张注册表&lt;/strong&gt;：主管只通过 &lt;code&gt;task(description, subagent_type)&lt;/code&gt; 派活，&lt;code&gt;subagent_type&lt;/code&gt; 就是子智能体的 &lt;code&gt;name&lt;/code&gt;。原理细节见 &lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;04&lt;/a&gt; 和 &lt;a href=&quot;/posts/multiagent-14-appendix-tool-wrap/&quot;&gt;14&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;必填的只有 &lt;code&gt;name&lt;/code&gt; / &lt;code&gt;description&lt;/code&gt; / &lt;code&gt;system_prompt&lt;/code&gt;&lt;/strong&gt;（最后一个仅 isolated 模式需要），其余字段都有明确默认值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;继承与覆盖是四组反直觉规则&lt;/strong&gt;：&lt;code&gt;tools&lt;/code&gt; 整体覆盖、&lt;code&gt;interrupt_on&lt;/code&gt; 覆盖默认、&lt;code&gt;skills&lt;/code&gt; 不继承、&lt;code&gt;permissions&lt;/code&gt; 整体替换。这是全篇最值钱的一张表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;general-purpose&lt;/code&gt; 是自动注入的&lt;/strong&gt;，同名即替换，改装走 harness profile；关掉要 profile 与 &lt;code&gt;subagents=&lt;/code&gt; 双管齐下，且&lt;strong&gt;不能&lt;/strong&gt;用 &lt;code&gt;excluded_middleware&lt;/code&gt;（会抛 &lt;code&gt;ValueError&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fork 是&quot;接着干&quot;而不是&quot;重新干&quot;&lt;/strong&gt;：丢掉触发派活的那条 AI 消息、换成 continuation 前言，换来父级完整历史和提示词缓存复用——但需要 &lt;code&gt;&amp;gt;=0.7.13&lt;/code&gt;，本机 0.7.5 用不了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每个子智能体的名字都会写进 &lt;code&gt;lc_agent_name&lt;/code&gt;&lt;/strong&gt;，这是流式分组、LangSmith 过滤、工具内按智能体分支行为的统一抓手。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;任务能拆、中间过程脏、结果要一句话——就用 &lt;code&gt;isolated&lt;/code&gt; 子智能体；子智能体的活是&quot;接着父级刚开的头往下写&quot;——才值得上 &lt;code&gt;fork&lt;/code&gt;；两者都够不上，别派活，自己干。&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>Deep Agents 多 Agent 文档中文精讲</title><link>https://aicooleo.cn/blog/posts/deepagents-overview/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/deepagents-overview/</guid><description>上层的 多agent/ 讲的是 LangChain 官方 Multi-agent 板块——模式与选型（五种模式、什么场景用哪个、调用与 token 代价）；这个子板块讲的是 Deep Agents 的多 Agent 能力——同一套模式在 …</description><pubDate>Mon, 31 Aug 2026 20:55:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：上层的 &lt;a href=&quot;/posts/multiagent-overview/&quot;&gt;&lt;code&gt;多agent/&lt;/code&gt;&lt;/a&gt; 讲的是 LangChain 官方 &lt;strong&gt;Multi-agent 板块&lt;/strong&gt;——&lt;strong&gt;模式与选型&lt;/strong&gt;（五种模式、什么场景用哪个、调用与 token 代价）；这个子板块讲的是 &lt;strong&gt;Deep Agents 的多 Agent 能力&lt;/strong&gt;——&lt;strong&gt;同一套模式在 Deep Agents 里长什么 API、默认值是什么、坑在哪&lt;/strong&gt;。一句话分工：&lt;strong&gt;上层教你怎么选，这里教你怎么用。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;和上层笔记的关系（先看这个，别重复读）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;你已经读过的&lt;/th&gt;
&lt;th&gt;它讲什么&lt;/th&gt;
&lt;th&gt;本子板块对应补充&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;04 Subagents 子智能体&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;模式原理&lt;/strong&gt;：主管 + 子智能体即工具、上下文隔离、同步/异步概念、三处上下文阀门&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 Subagents&lt;/a&gt; 把它落成 Deep Agents 的&lt;strong&gt;字段与默认值&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-14-appendix-tool-wrap/&quot;&gt;14 手写 Tool 包装 vs Deep Agents 内置 subagents&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;底层机制&lt;/strong&gt;：&lt;code&gt;subagents=&lt;/code&gt; 最终就是一个 &lt;code&gt;task(description, subagent_type)&lt;/code&gt; 工具 + 注册表（源码 + 抓包）&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 Subagents&lt;/a&gt; 讲这套机制上的完整配置面&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03 性能对比&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Subagents 一次请求 ≈ &lt;strong&gt;4 次模型调用&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/deepagents-03-async/&quot;&gt;03 异步子智能体&lt;/a&gt; 讲怎么让这些调用&lt;strong&gt;不阻塞用户&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;07 Router 路由&lt;/a&gt;、&lt;a href=&quot;/posts/multiagent-08-custom-workflow/&quot;&gt;08 Custom workflow&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;先分诊再并行 / 自己画图&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/posts/deepagents-02-dynamic/&quot;&gt;02 动态子智能体&lt;/a&gt; 是&quot;用&lt;strong&gt;代码&lt;/strong&gt;编排子智能体&quot;的官方答案（扇出、筛选、锦标赛、循环）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;文件索引&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;一句话&lt;/th&gt;
&lt;th&gt;对应原文&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;01 Subagents：内置 task 工具 + 一整套配置面&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;静态子智能体：&lt;code&gt;SubAgent&lt;/code&gt; 每个字段（含 &lt;code&gt;tools&lt;/code&gt;/&lt;code&gt;skills&lt;/code&gt;/&lt;code&gt;permissions&lt;/code&gt; 的&lt;strong&gt;继承 vs 覆盖&lt;/strong&gt;规则）、&lt;code&gt;CompiledSubAgent&lt;/code&gt;、默认 &lt;code&gt;general-purpose&lt;/code&gt;、fork 模式、流式与追踪、结构化输出、最佳实践&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/subagents&quot;&gt;deepagents/subagents&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/deepagents-02-dynamic/&quot;&gt;02 Dynamic subagents：用代码编排子智能体&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;动态编排：把&quot;派谁、派几个、怎么收敛&quot;从模型手里拿回代码——分类分发、扇出汇总、对抗验证、生成筛选、锦标赛、循环到达标 6 种模式&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/dynamic-subagents&quot;&gt;deepagents/dynamic-subagents&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/deepagents-03-async/&quot;&gt;03 Async subagents：后台跑的子智能体&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;异步子智能体：启动 / 轮询 / 中途干预 / 取消，状态机、传输方式（ASGI vs HTTP）、部署拓扑、排障清单&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/async-subagents&quot;&gt;deepagents/async-subagents&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;建议阅读顺序：&lt;strong&gt;01 → 03 → 02&lt;/strong&gt;。01 是使用频率最高的（大多数系统只需要静态子智能体 + 异步化）；03 解决&quot;不要让用户干等&quot;；02 最重，等你确实需要&quot;代码控制编排&quot;时再上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;三者怎么选（一页速查）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;你的需求&lt;/th&gt;
&lt;th&gt;用哪种&lt;/th&gt;
&lt;th&gt;代价 / 前提&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;几个固定领域的专家、由模型决定派谁&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;静态子智能体&lt;/strong&gt;（&lt;code&gt;subagents=&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;每次派活多一次模型调用（见 &lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03 篇&lt;/a&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;想让子智能体&quot;接着父级已开工的活干&quot;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;fork 模式&lt;/strong&gt;（&lt;code&gt;mode:&quot;fork&quot;&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;需 &lt;code&gt;deepagents&amp;gt;=0.7.13&lt;/code&gt;&lt;/strong&gt;，且会削弱上下文隔离、可能破坏提示词缓存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;任务长（几十秒~几分钟），用户不该干等；要能中途干预/取消&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;异步子智能体&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;需要能跑的 agent server（同进程 ASGI 或远端 HTTP）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;要&lt;strong&gt;严格控制&lt;/strong&gt;派几个、并行度、失败重试、结果筛选与收敛&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;动态子智能体&lt;/strong&gt;（解释器运行时）&lt;/td&gt;
&lt;td&gt;官方标注 &lt;strong&gt;beta&lt;/strong&gt;；本机 0.7.5 不含该运行时&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;既要灵活又不想上解释器&lt;/td&gt;
&lt;td&gt;用 LangGraph / 普通 Python 自己编排，把 Agent 当节点（见 &lt;a href=&quot;/posts/multiagent-08-custom-workflow/&quot;&gt;08 篇&lt;/a&gt;）&lt;/td&gt;
&lt;td&gt;编排逻辑、并发上限、收敛条件都要自己写&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;本机可用性矩阵（&lt;code&gt;deepagents 0.7.5&lt;/code&gt; 实测）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;能力&lt;/th&gt;
&lt;th&gt;本机 0.7.5&lt;/th&gt;
&lt;th&gt;依据&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;静态子智能体（&lt;code&gt;subagents=&lt;/code&gt; / &lt;code&gt;SubAgent&lt;/code&gt; / &lt;code&gt;CompiledSubAgent&lt;/code&gt; / &lt;code&gt;SubAgentMiddleware&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;包内已导出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;默认 &lt;code&gt;general-purpose&lt;/code&gt; 子智能体（&lt;code&gt;GeneralPurposeSubagentProfile&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;包内已导出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;子智能体结构化输出（&lt;code&gt;response_format&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;需 &lt;code&gt;deepagents&amp;gt;=0.5.3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;异步子智能体（&lt;code&gt;AsyncSubAgent&lt;/code&gt; / &lt;code&gt;AsyncSubAgentMiddleware&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;包内已导出；但需要 agent server 环境才能真正跑起来&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fork 分叉子智能体（&lt;code&gt;mode:&quot;fork&quot;&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;需 &lt;code&gt;deepagents&amp;gt;=0.7.13&lt;/code&gt;；0.7.5 的 &lt;code&gt;SubAgent&lt;/code&gt; 里没有 &lt;code&gt;mode&lt;/code&gt; 字段&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;动态子智能体（解释器运行时）&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;需要 &lt;code&gt;langchain-quickjs&lt;/code&gt;（PyPI 已发布）并在 agent 上挂 &lt;code&gt;CodeInterpreterMiddleware&lt;/code&gt;；0.7.5 包内没有解释器运行时模块，本机也未安装该包（官方标注 beta）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;结论：&lt;strong&gt;本机现在就能用的是「静态子智能体 + 异步子智能体 + 结构化输出」&lt;/strong&gt;；fork 与动态编排需要先升级 &lt;code&gt;deepagents&lt;/code&gt;。三篇笔记里凡是涉及版本门控的地方都标了 &lt;code&gt;&amp;gt; ⚠️ **版本要求**&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;另外提醒一个容易踩的认知偏差：&lt;strong&gt;动态子智能体的编排代码写在解释器里（TypeScript），不是 Python&lt;/strong&gt;——Python 只负责&quot;把解释器中间件挂上去&quot;，&lt;code&gt;task()&lt;/code&gt; 这些编排调用发生在解释器内部。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;术语中英对照（与上层笔记保持一致）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;英文&lt;/th&gt;
&lt;th&gt;译法&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SubAgent / CompiledSubAgent&lt;/td&gt;
&lt;td&gt;子智能体（字典式 / 编译式）&lt;/td&gt;
&lt;td&gt;字符串配置 vs 现成的 LangGraph 图&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task tool&lt;/td&gt;
&lt;td&gt;task 工具&lt;/td&gt;
&lt;td&gt;Deep Agents 里唯一的派活入口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;isolated / fork&lt;/td&gt;
&lt;td&gt;隔离 / 分叉&lt;/td&gt;
&lt;td&gt;只看到任务描述 vs 继承父级对话与系统提示词&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;context quarantine&lt;/td&gt;
&lt;td&gt;上下文隔离&lt;/td&gt;
&lt;td&gt;子智能体的核心价值：把噪音关在外面&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;interpreter runtime&lt;/td&gt;
&lt;td&gt;解释器运行时&lt;/td&gt;
&lt;td&gt;动态子智能体执行代码的地方（beta）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fan-out / synthesize&lt;/td&gt;
&lt;td&gt;扇出 / 汇总&lt;/td&gt;
&lt;td&gt;动态编排的两种基本动作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tournament / adversarial verification&lt;/td&gt;
&lt;td&gt;锦标赛 / 对抗式验证&lt;/td&gt;
&lt;td&gt;用多次生成 + 比较来换质量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;async subagent / job / task id&lt;/td&gt;
&lt;td&gt;异步子智能体 / 作业 / 任务 ID&lt;/td&gt;
&lt;td&gt;后台执行与轮询&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;transport (ASGI / HTTP)&lt;/td&gt;
&lt;td&gt;传输方式（同进程 / 远端）&lt;/td&gt;
&lt;td&gt;决定子智能体跑在哪&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;steering&lt;/td&gt;
&lt;td&gt;中途干预&lt;/td&gt;
&lt;td&gt;跑着的时候改方向&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;structured output&lt;/td&gt;
&lt;td&gt;结构化输出&lt;/td&gt;
&lt;td&gt;父级收到 JSON 而不是自由文本&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;来源清单（3 篇原文）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;原文标题&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Subagents（Deep Agents）&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/deepagents/subagents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Dynamic subagents&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/deepagents/dynamic-subagents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Async subagents&lt;/td&gt;
&lt;td&gt;https://docs.langchain.com/oss/python/deepagents/async-subagents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;延伸阅读&lt;/strong&gt;：Deep Agents 总览（规划 / 虚拟文件系统 / 上下文管理 / 技能）— &lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/overview&quot;&gt;overview&lt;/a&gt; ｜ 解释器（动态子智能体的底座）— &lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/interpreters&quot;&gt;interpreters&lt;/a&gt; ｜ 子智能体前端流式展示 — &lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/frontend/subagent-streaming&quot;&gt;frontend/subagent-streaming&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;加工说明（哪些是原文、哪些是笔记加的）&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;定位&lt;/strong&gt;：这三篇官方文档本身就是&lt;strong&gt;API/工程实现层&lt;/strong&gt;的说明（配置字段、默认值、传输与部署、排障），与上层 LangChain Multi-agent 板块的&lt;strong&gt;模式/选型层&lt;/strong&gt;互补。笔记保留了它们&quot;手册&quot;的性质——&lt;strong&gt;字段表、默认值、版本门控、坑&lt;/strong&gt;尽量一条不漏。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;压缩&lt;/strong&gt;：原文里成排的 provider 代码 Tab（Google / OpenAI / Anthropic / OpenRouter / Fireworks / Baseten / Ollama…）在笔记里压成一张表或一行&quot;换 provider 只改 model 这一行&quot;；同一段逻辑不重复贴 7 遍。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码&lt;/strong&gt;：只保留骨架（单块 ≤30 行、注释中文化、删除文档站渲染标记），完整示例指向原文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;补充解读&lt;/strong&gt;：凡标注 &lt;strong&gt;🧭 补充解读（非原文）&lt;/strong&gt; 的段落都是笔记加的（类比、取舍建议、与上层笔记的呼应），&lt;strong&gt;不是官方说法&lt;/strong&gt;；原文没写清楚的地方会明确标&quot;原文未说明&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;版本与实测&lt;/strong&gt;：可用性矩阵、版本门控均为&lt;strong&gt;本机实测核对&lt;/strong&gt;（&lt;code&gt;deepagents 0.7.5&lt;/code&gt;，见上表），核对对象是 &lt;code&gt;site-packages/deepagents/&lt;/code&gt; 的实际导出与字段。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;未验证的部分&lt;/strong&gt;：除可用性矩阵外，笔记中的示例代码&lt;strong&gt;未在本机实际运行&lt;/strong&gt;，不承诺可直接跑通（异步与动态子智能体还需要 agent server / 解释器运行时）。&lt;strong&gt;以官方原文为最终依据。&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>16 主 Agent 的任务拆分与串并行决策</title><link>https://aicooleo.cn/blog/posts/multiagent-16-task-split/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-16-task-split/</guid><description>一句话：主 Agent 决定&quot;谁做什么、什么顺序&quot;；领域 Agent 决定&quot;这一步具体怎么干&quot;。</description><pubDate>Mon, 31 Aug 2026 10:41:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话回答&lt;/strong&gt;：拆分分&lt;strong&gt;两级&lt;/strong&gt;——主 Agent 把用户请求拆成&lt;strong&gt;子任务清单&lt;/strong&gt;（谁做、做什么、依赖谁），子任务内部的工具编排交给下游 Agent；&lt;strong&gt;串行还是并行只看子任务之间的数据依赖&lt;/strong&gt;，而依赖是由&lt;strong&gt;动作契约算出来的&lt;/strong&gt;，不是让模型临场决定。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;一、任务拆分是怎么做的&lt;/h2&gt;
&lt;h3&gt;1）拆两级，主 Agent 只做上面那一级&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层级&lt;/th&gt;
&lt;th&gt;谁拆&lt;/th&gt;
&lt;th&gt;拆成什么&lt;/th&gt;
&lt;th&gt;拆完就不管什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;宏观（跨 Agent）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;主 Agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;请求 → &lt;strong&gt;子任务清单&lt;/strong&gt;（每个子任务指定执行方 + 依赖关系）&lt;/td&gt;
&lt;td&gt;不管工具怎么调、参数怎么拼、失败怎么重试&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;微观（Agent 内）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;领域 Agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;子任务 → 工具调用序列（Function Calling 自己决定）&lt;/td&gt;
&lt;td&gt;不管还有没有别的子任务，也不管全局顺序&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;编排&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;编排层&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;不拆&lt;/strong&gt;，只按清单调度&lt;/td&gt;
&lt;td&gt;它不是决策者，是&lt;strong&gt;执行状态的持有者&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一句话：&lt;strong&gt;主 Agent 决定&quot;谁做什么、什么顺序&quot;；领域 Agent 决定&quot;这一步具体怎么干&quot;。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;2）拆出来的东西是一份契约，不是一段自然语言计划&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SubTask(
    id,                # 唯一标识：依赖引用、断点续跑、日志回溯都用它
    agent,             # 谁执行（哪个领域 Agent）
    action,            # 做什么（必须来自动作注册表，不能自由发挥）
    inputs,            # 输入切片：引用 state 字段或上游产物，不塞整包上下文
    depends_on,        # 依赖哪些子任务  ← 串并行的唯一依据
    timeout,           # 超时
    retry,             # 重试策略（次数 / 退避 / 幂等键）
    success_criteria,  # 成功判据：没有它就无法自动重试，也判断不了该降级还是转人工
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;为什么必须落成结构化契约&lt;/strong&gt;：后面的依赖计算、波次编排、失败降级、并行合并，全都依赖这些字段。如果拆出来是&quot;先查规则再看看召回然后写文案&quot;这种自然语言，代码无法校验、无法重试、也无法统计。&lt;/p&gt;
&lt;h3&gt;3）拆的依据：按&quot;业务动作&quot;切，不按&quot;工具调用&quot;切&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一个子任务 = 一个&lt;strong&gt;能独立验收的业务动作&lt;/strong&gt;，例如：查活动规则 / 召回商品 / 生成话术 / 合规审核。&lt;/li&gt;
&lt;li&gt;不能按工具切：工具是执行细节，一个业务动作在下游可能对应好几次工具调用；按工具切会让子任务数量爆炸，&lt;strong&gt;调度开销吃掉并行收益&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;粒度经验值：&lt;strong&gt;平均子任务数 &amp;lt; 1.2 → 拆得太细&lt;/strong&gt;（开销大于收益）；&lt;strong&gt;永远只有 1 个 → 拆得太粗&lt;/strong&gt;（拿不到并行和独立重试的好处）。&lt;/li&gt;
&lt;li&gt;分配领域 Agent 时，&lt;strong&gt;权限是硬门槛&lt;/strong&gt;（先按业务线过滤，再排序），其次才是能力匹配、健康负载、成本与历史效果。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4）模型负责什么、代码负责什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;环节&lt;/th&gt;
&lt;th&gt;谁做&lt;/th&gt;
&lt;th&gt;具体做法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;识别&quot;要做哪几个动作&quot;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;模型&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;结构化输出（JSON Schema / Pydantic）+ &lt;strong&gt;枚举约束&lt;/strong&gt;：只能从动作注册表里选&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;定动作之间的先后（偏序）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;代码&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;用动作契约的 &lt;code&gt;requires / produces&lt;/code&gt; 求交集，做拓扑排序&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;校验与兜底&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;代码&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;模型多给的依赖只当&quot;提示&quot;；&lt;strong&gt;缺依赖 → 保守串行&lt;/strong&gt;；动作不在注册表 → 拒绝并降级&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;选领域 Agent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;主 Agent（规则 + 模型）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;意图标签匹配能力描述；权限先过滤&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;为什么不让模型直接决定顺序&lt;/strong&gt;（这是这件事的核心判断）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不稳定&lt;/strong&gt;：同样输入两次调用，模型可能给出不同的依赖顺序 → 同一个请求两次行为不一致，没法回归测试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不可回滚&lt;/strong&gt;：改一句提示词，整条流程的偏序可能全变，出了事故定位不到是哪一步的锅。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;依赖是业务事实，不是模型知识&lt;/strong&gt;：哪个动作需要哪些输入，是数据字典/接口契约里定义好的，不该让模型&quot;猜&quot;。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;二、串行还是并行怎么决定&lt;/h2&gt;
&lt;h3&gt;1）主判据：数据依赖（producer → consumer）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;A 的产出是 B 的输入     →  B 依赖 A   →  必须串行（A → B）
A、B 互不需要对方的产出  →  无依赖     →  可以并行
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以&quot;推荐一批活动商品 + 文案&quot;为例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    A[&quot;① 查活动规则&quot;] --&amp;gt; D[&quot;④ 合规审核&quot;]
    B[&quot;② 召回商品&quot;] --&amp;gt; C[&quot;③ 生成话术&quot;] --&amp;gt; D
    A -. &quot;无依赖，可并行&quot; .- B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① 与 ② 互不依赖 → 同一波次并行；③ 需要 ② 的召回结果 → 排在 ② 之后；④ 是硬顺序（审核必须最后）。最终得到三个波次：&lt;code&gt;wave1=[①,②]&lt;/code&gt;、&lt;code&gt;wave2=[③]&lt;/code&gt;、&lt;code&gt;wave3=[④]&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;2）完整判定流程（代码里就是这五步，按顺序套）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;步&lt;/th&gt;
&lt;th&gt;判什么&lt;/th&gt;
&lt;th&gt;怎么判&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;数据依赖&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;B.requires ∩ A.produces ≠ ∅&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;B 排到 A 之后&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;硬顺序&lt;/strong&gt;（与数据无关也必须先后）&lt;/td&gt;
&lt;td&gt;契约里的顺序规则：审核通过才允许发布、鉴权通过才允许调下游、扣款成功才允许发货&lt;/td&gt;
&lt;td&gt;强制串行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;副作用与幂等&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;side_effect = true&lt;/code&gt; 的动作不并行；同一资源的写操作遵&lt;strong&gt;单写者&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;写操作串行，读操作可并行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;合成波次&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;把&quot;依赖已全部满足&quot;的节点合成一波，受&lt;strong&gt;并发上限&lt;/strong&gt;约束&lt;/td&gt;
&lt;td&gt;&lt;code&gt;waves = [[①,②],[③],[④]]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;人工节点单独成波&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;需要用户确认 / 审批的节点&lt;/td&gt;
&lt;td&gt;单独一波，等人，不占并发额度&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3）六条判据汇总（每条都要能说出&quot;违反会怎样&quot;）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;判据&lt;/th&gt;
&lt;th&gt;典型场景&lt;/th&gt;
&lt;th&gt;违反的后果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;数据依赖&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;召回 → 生成话术&lt;/td&gt;
&lt;td&gt;下游拿不到输入，只能编或报错&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;硬顺序（合规 / 资金）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;先审核后发布、先扣款后发货&lt;/td&gt;
&lt;td&gt;合规事故、不可逆的资金错误&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;副作用与幂等&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;改同一张订单 / 同一个购物车&lt;/td&gt;
&lt;td&gt;竞态、重复下单、数据错乱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;用户可感知的顺序&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;客服：先登记保修 → 再分类 → 再给方案&lt;/td&gt;
&lt;td&gt;用户被并行结果淹没，对话不连贯&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;资源与成本&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;十路并行&lt;/td&gt;
&lt;td&gt;触发下游限流、P99 抖动、token 放大&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;可恢复性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;长批处理任务&lt;/td&gt;
&lt;td&gt;一个分支挂掉，整批结果交付不了&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;4）并行的收益是延迟，不是成本&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;并行把 P95 压下来，但&lt;strong&gt;通常让 token 更贵&lt;/strong&gt;——每个并行分支都要带自己的上下文。官方数据里多领域场景，Skills 只调 3 次却最贵（约 15K token），Handoffs 串行 7+ 次约 14K+。&lt;/li&gt;
&lt;li&gt;所以策略是：&lt;strong&gt;先按依赖排出偏序，再把&quot;无依赖 + 只读 + 可幂等&quot;的节点合成一波并行&lt;/strong&gt;；写操作、合规节点、需要用户确认的节点一律单独成波。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;三、走一遍完整例子&lt;/h2&gt;
&lt;p&gt;请求：&lt;em&gt;&quot;给这个用户推一批活动商品，文案要合规。&quot;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;主 Agent 产出的子任务清单：
  ① query_campaign_rules  → 规则 Agent   depends_on=[]       只读
  ② recall_products       → 推荐 Agent   depends_on=[]       只读
  ③ generate_copy         → 推荐 Agent   depends_on=[②]      只读（输入 = 召回结果）
  ④ compliance_review     → 审核 Agent   depends_on=[③]      只读 + 硬顺序（审核后才能发）

代码算出的波次：
  wave1 = [①, ②]     无依赖 → 并行
  wave2 = [③]        等 ② 的召回结果
  wave3 = [④]        硬顺序：审核必须最后

执行与合并：
  波内并行（图扇出 / asyncio.gather），结果按 reducer 归并；
  ④ 判定&quot;不合规&quot; → 一票否决，回到 ③ 重写（最多 N 轮）；仍不过 → 转人工，带上下文工单。

失败处理（按代价从低到高）：
  波内某个子任务失败 → 重试（幂等键，重试前先查状态而不是重放动作）
                     → 换实例
                     → 缩小能力集降级
                     → 跳过并标注
                     → 整体转人工
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;四、工程落地要点&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;关注点&lt;/th&gt;
&lt;th&gt;做法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;单路串行&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Command(goto=...)&lt;/code&gt; 路由到单个节点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;并行扇出&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Send(...)&lt;/code&gt; 列表——分支数量运行时才确定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;并行结果合并&lt;/td&gt;
&lt;td&gt;reducer（&lt;code&gt;Annotated[list, operator.add]&lt;/code&gt;）自动归并 + 一个汇总节点；冲突按优先级仲裁（合规一票否决）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;顺序与恢复&lt;/td&gt;
&lt;td&gt;checkpointer + 同一 &lt;code&gt;thread_id&lt;/code&gt;；状态外置，实例重启可断点续跑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;依赖数据怎么传&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;结构化字段走 state&lt;/strong&gt;，不把上游结论用自然语言塞进 prompt（避免转述失真与 token 浪费）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;防失控&lt;/td&gt;
&lt;td&gt;并发上限 + 每层超时 + 总超时 + 最大轮次（防止 Agent 之间互相踢皮球）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可观测&lt;/td&gt;
&lt;td&gt;每个子任务一个 span（&lt;code&gt;request_id&lt;/code&gt; / &lt;code&gt;thread_id&lt;/code&gt; / &lt;code&gt;run_id&lt;/code&gt;），能回答&quot;这次为什么是串行&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;该盯的四个数&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;异常信号&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;并行度分布&lt;/td&gt;
&lt;td&gt;每次请求实际并行执行的波次宽度&lt;/td&gt;
&lt;td&gt;长期恒为 1 → 规则太保守，白吃串行的延迟&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;依赖违例率&lt;/td&gt;
&lt;td&gt;下游先于上游执行 / 拿不到输入的比例&lt;/td&gt;
&lt;td&gt;目标为 0；&amp;gt;0 说明契约与模型输出没对齐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;子任务数分布&lt;/td&gt;
&lt;td&gt;每个请求拆出几个子任务&lt;/td&gt;
&lt;td&gt;均值 &amp;lt; 1.2 → 拆得太细，开销大于收益&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;失败归因分布&lt;/td&gt;
&lt;td&gt;失败集中在哪个环节&lt;/td&gt;
&lt;td&gt;集中在&quot;路由后的第一个子任务&quot; → 分错 Agent 了&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;五、小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;拆分两级&lt;/strong&gt;：主 Agent 拆&lt;strong&gt;子任务清单&lt;/strong&gt;（谁做、依赖谁），领域 Agent 拆&lt;strong&gt;工具调用&lt;/strong&gt;；主 Agent 拆完就不管执行细节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子任务必须是契约&lt;/strong&gt;：含 &lt;code&gt;depends_on&lt;/code&gt;、&lt;code&gt;timeout&lt;/code&gt;、&lt;code&gt;retry&lt;/code&gt;、&lt;code&gt;success_criteria&lt;/code&gt;——否则没法自动重试，也判断不了该降级还是转人工。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;串并行看数据依赖&lt;/strong&gt;，再叠加硬顺序（合规 / 资金）、副作用（写操作单写者）、并发成本、人工节点这四类约束，最终得到一组&lt;strong&gt;执行波次&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型给意图，代码定 DAG&lt;/strong&gt;：模型只能从动作注册表里选动作，代码按 &lt;code&gt;requires / produces&lt;/code&gt; 算偏序；模型多说的依赖只当提示，缺依赖就保守串行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;并行的收益是延迟不是成本&lt;/strong&gt;：写操作与合规节点永远单独成波。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;口径说明：两级分配、&quot;看子任务之间有无数据依赖&quot;、编排层只托管不分配，来自项目资料；L1/L2/L3 三层入口与部分字段是按职责推导的分层设计，对外讲时说&quot;我们的分层设计&quot;即可。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>15 附录：重复请求占比怎么统计（一次调用耗时、平均 token 好量，这个得先定义）</title><link>https://aicooleo.cn/blog/posts/multiagent-15-appendix-dup-rate/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-15-appendix-dup-rate/</guid><description>它不是一个能从数据库里直接取出来的字段，而是一个&quot;要先定义清楚&quot;的派生指标。它唯一要服务的决策是——有状态模式（Handoffs / Skills）到底能替你省掉多少次&quot;决策调用&quot;。所以定义必须对着这个目的来：别拿&quot;用户消息文本一样&quot;当判…</description><pubDate>Sun, 30 Aug 2026 23:27:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：&lt;strong&gt;它不是一个能从数据库里直接取出来的字段&lt;/strong&gt;，而是一个&quot;要先定义清楚&quot;的派生指标。它唯一要服务的决策是——&lt;strong&gt;有状态模式（Handoffs / Skills）到底能替你省掉多少次&quot;决策调用&quot;&lt;/strong&gt;。所以定义必须对着这个目的来：&lt;strong&gt;别拿&quot;用户消息文本一样&quot;当判据&lt;/strong&gt;（追问、换说法、补充信息全都会漏判）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;关联：&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;&lt;code&gt;03&lt;/code&gt; 场景二与结尾的&quot;量三件事&quot;&lt;/a&gt; ｜ &lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;&lt;code&gt;05&lt;/code&gt; Handoffs&lt;/a&gt; ｜ &lt;a href=&quot;/posts/multiagent-06-skills/&quot;&gt;&lt;code&gt;06&lt;/code&gt; Skills&lt;/a&gt; ｜ 抓包方法见 &lt;a href=&quot;/posts/multiagent-13-appendix-4-calls/&quot;&gt;&lt;code&gt;13&lt;/code&gt; 附录&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;一、先定义：什么叫&quot;一次重复请求&quot;&lt;/h2&gt;
&lt;p&gt;对着决策来定义：&lt;strong&gt;同一会话里，这一轮能不能沿用上一轮已经&quot;进入&quot;的那个域/步骤&lt;/strong&gt;——能，就是一次重复。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;thread A:   轮1 [咖啡域]    轮2 [咖啡域]    轮3 [咖啡域]    轮4 [机票域]
                    └─ 粘住 ─┘    └─ 粘住 ─┘    └─ 切换 ─┘
相邻轮次对 3 个，其中粘住 2 个 → sticky_rate = 2 / 3 ≈ 66.7%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;公式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sticky_rate = Σ(i≥2) [ d(i) == d(i-1) 且 gap(i) ≤ 空闲阈值 ]  /  Σ(i≥2) 1

  d(i)   = 第 i 轮**真正被使用**的域/步骤标签（落到了哪个子智能体 / 技能 / 路由分支）
  gap(i) = 与同会话上一轮的时间间隔（超过阈值就算&quot;重新开始&quot;，不算粘住）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;为什么是这个量&lt;/strong&gt;：有状态模式省掉的那一次调用，正是&quot;重新进入这个域&quot;的开销——Handoffs 省的是&lt;strong&gt;交接&lt;/strong&gt;，Skills 省的是&lt;strong&gt;加载技能&lt;/strong&gt;，Router 若做成有状态省的是&lt;strong&gt;分诊&lt;/strong&gt;。所以&quot;能沿用上一轮&quot;的比例，就是它们省钱比例的&lt;strong&gt;上界&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二、口径先选一个（三种 d(i) 取法，别混着比）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;口径&lt;/th&gt;
&lt;th&gt;&lt;code&gt;d(i)&lt;/code&gt; 取什么&lt;/th&gt;
&lt;th&gt;优点&lt;/th&gt;
&lt;th&gt;代价&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;业务主键&lt;/strong&gt;（推荐优先）&lt;/td&gt;
&lt;td&gt;这一轮操作的业务对象：订单号 / 工单号 / 设备号 / SKU&lt;/td&gt;
&lt;td&gt;最客观、SQL 一句 join 就能算、不用模型&lt;/td&gt;
&lt;td&gt;通用闲聊类请求没有主键，算不出来&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;域 / 专家标签&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;本轮落到哪个子智能体、哪个技能、哪条路由分支&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;直接对应省下来的那次调用&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;需要标签（要从 trace/checkpoint 里取或补埋点）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;意图分类&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;离线用分类提示词打出的意图&lt;/td&gt;
&lt;td&gt;通用、无业务假设&lt;/td&gt;
&lt;td&gt;要花一次离线模型调用；分类口径本身会漂&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;建议&lt;/strong&gt;：&lt;strong&gt;先试业务主键&lt;/strong&gt;——多数 ToB 场景（客服工单、订单、设备）天然有主键，&quot;同一工单连续追问&quot;就是最典型的重复请求。没有主键的通用助手，再退到域标签或离线分类。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;三、三条取数路线&lt;/h2&gt;
&lt;h3&gt;路线 A：从 trace / checkpoint / 日志派生（推荐，半天能出第一个数）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;每轮只需要四个字段&lt;/strong&gt;：&lt;code&gt;thread_id&lt;/code&gt;、&lt;code&gt;turn_no&lt;/code&gt;、&lt;code&gt;ts&lt;/code&gt;、&lt;code&gt;domain_label&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;标签从哪来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LangGraph checkpoint&lt;/strong&gt;：状态里就有历史 messages。每轮的 &lt;code&gt;AIMessage.tool_calls&lt;/code&gt; 告诉你调了哪个工具/子智能体；按 tool → 域 的映射表就能得到 &lt;code&gt;domain_label&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deep Agents&lt;/strong&gt;：更省事——它会给子智能体运行打上 &lt;code&gt;lc_agent_name&lt;/code&gt; 元数据（见 &lt;a href=&quot;/posts/deepagents-01-subagents/&quot;&gt;&lt;code&gt;deepagents/01&lt;/code&gt;&lt;/a&gt; 的&quot;同一个工具在多个智能体间共用&quot;），在工具里 &lt;code&gt;runtime.config[&quot;metadata&quot;][&quot;lc_agent_name&quot;]&lt;/code&gt; 就能读到&quot;这轮是谁在干活&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;纯日志&lt;/strong&gt;：按 &lt;code&gt;request_id&lt;/code&gt; / &lt;code&gt;thread_id&lt;/code&gt; 聚合，取日志里&quot;本轮调用了哪个工具/子智能体&quot;的字段。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;如果现在什么都没有&lt;/strong&gt;：加一行埋点，就四个字段，成本远低于事后靠猜。&lt;/p&gt;
&lt;h3&gt;路线 B：业务主键 join（SQL 模板）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 模板：表名/列名换成你自己的；核心是 LAG 取上一轮，再比主键与时间间隔
WITH t AS (
  SELECT thread_id,
         turn_no,
         biz_key,                                                  -- 订单号/工单号/设备号…
         created_at,
         LAG(biz_key)    OVER (PARTITION BY thread_id ORDER BY turn_no) AS prev_key,
         LAG(created_at) OVER (PARTITION BY thread_id ORDER BY turn_no) AS prev_ts
  FROM agent_turn_log
  WHERE created_at &amp;gt;= NOW() - INTERVAL 14 DAY
    AND thread_id IS NOT NULL
)
SELECT
  COUNT(*)                                                                   AS pairs,          -- 分母：相邻轮次对
  SUM(prev_key IS NOT NULL AND biz_key = prev_key
      AND TIMESTAMPDIFF(MINUTE, prev_ts, created_at) &amp;lt;= 30)                   AS sticky_pairs,   -- 分子
  ROUND(100 * SUM(prev_key IS NOT NULL AND biz_key = prev_key
      AND TIMESTAMPDIFF(MINUTE, prev_ts, created_at) &amp;lt;= 30) / COUNT(*), 1)    AS sticky_rate_pct
FROM t
WHERE prev_key IS NOT NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;PostgreSQL：把 &lt;code&gt;TIMESTAMPDIFF(MINUTE, prev_ts, created_at)&lt;/code&gt; 换成 &lt;code&gt;EXTRACT(EPOCH FROM (created_at - prev_ts)) / 60&lt;/code&gt;。
顺手再加一列 &lt;code&gt;GROUP BY&lt;/code&gt; 维度（业务线 / 域 / 用户类型），&lt;strong&gt;分域看&lt;/strong&gt;——重复率在域之间的差异通常比整体均值有用得多。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;路线 C：离线分类（兜底）&lt;/h3&gt;
&lt;p&gt;没有主键、也没有埋点标签时：用 &lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;&lt;code&gt;07&lt;/code&gt; Router&lt;/a&gt; 的分类提示词，&lt;strong&gt;抽样&lt;/strong&gt; 1–2 周会话，给每轮打一个域标签，再按第一节的公式算。
&lt;strong&gt;务必只抽样&lt;/strong&gt;——为了统计而全量跑一遍模型，本身就违反这次优化的初衷。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;四、该报告的五个数（一张 measurement card）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;怎么算&lt;/th&gt;
&lt;th&gt;拿来回答什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;会话轮数分布&lt;/td&gt;
&lt;td&gt;单轮会话占比、轮数 P50/P90&lt;/td&gt;
&lt;td&gt;单轮会话对&quot;重复&quot;没贡献；占比高说明这个指标影响面小&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;sticky_rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;第一节公式&lt;/td&gt;
&lt;td&gt;主指标：有状态模式能省的比例上界&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;平均连续同域长度&lt;/strong&gt;（run length）&lt;/td&gt;
&lt;td&gt;按 thread 切段，统计同域连续段长度均值&lt;/td&gt;
&lt;td&gt;直接换算&quot;每个会话能省几次调用&quot;≈ 粘性轮数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;域切换频率&lt;/td&gt;
&lt;td&gt;相邻轮次 &lt;code&gt;d(i) != d(i-1)&lt;/code&gt; 的比例&lt;/td&gt;
&lt;td&gt;切换太频繁 → Handoffs 的交接开销会把省下的吃回去&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;空闲阈值敏感性&lt;/td&gt;
&lt;td&gt;阈值取 5 / 30 / 120 分钟各算一次&lt;/td&gt;
&lt;td&gt;检验结论稳不稳；差异大说明&quot;会话边界&quot;定得不合理&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;五、把这个比例换成钱和延迟&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;可省调用数/天   ≈ 日轮次 × sticky_rate
省下的 token/天 ≈ 可省调用数/天 × 单次调用平均 token
省下的钱/天     ≈ 省下的 token/天 × 单价
省下的延迟      ≈ 可省调用数/天 × 单次调用耗时        （串行时直接叠加）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;反向判据&lt;/strong&gt;：如果 &lt;code&gt;sticky_rate&lt;/code&gt; 只有 10%–15%，有状态模式带来的那点节省，通常撑不起它引入的&quot;粘性复杂度&quot;（要管状态、要兜底切回入口、要解释&quot;为什么这个会话里一直是同一个专家&quot;）——这时候 Subagents 的&quot;恒定成本 + 强隔离&quot;更划算。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;六、六个坑（踩了会让数字虚高或虚低）&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;分母口径&lt;/strong&gt;：按&lt;strong&gt;相邻轮次对&lt;/strong&gt;，不是按会话数——你要省的是调用，不是会话。&lt;/li&gt;
&lt;li&gt;**别用&quot;用户消息文本相同&quot;**判重：&lt;code&gt;&quot;再买一杯&quot;&lt;/code&gt; 和 &lt;code&gt;&quot;帮我买杯咖啡&quot;&lt;/code&gt; 是同一件事；而连着两次 &lt;code&gt;&quot;帮我查下订单&quot;&lt;/code&gt; 问的是两个订单，反而是两件事。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;必须设空闲阈值&lt;/strong&gt;：隔天再来的一句&quot;再买一杯&quot;，状态语义上已经不算&quot;粘住&quot;了；不设阈值，sticky_rate 会明显虚高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;新 thread 不算粘&lt;/strong&gt;：状态不跨会话保留（除非你另做长期记忆），跨会话的&quot;同样问题&quot;是&lt;strong&gt;新请求&lt;/strong&gt;，不是重复请求。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;这是上界，不是承诺&lt;/strong&gt;：上下文压缩可能把粘性打断——比如 Skills 的手册被摘要/裁掉后，下一轮其实得重新加载。真实节省要再打一个折（可用 A/B 或影子流量验证）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别跨用户拼会话&lt;/strong&gt;：一定按 &lt;code&gt;thread_id&lt;/code&gt; 分组后再取 &lt;code&gt;LAG&lt;/code&gt;，否则会把两个人的对话接成一条&quot;连续追问&quot;。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;七、一页速览&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;定义&lt;/strong&gt;：同一会话里&quot;这一轮能沿用上一轮的域/步骤且间隔不超阈值&quot; = 一次重复。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;公式&lt;/strong&gt;：&lt;code&gt;sticky_rate = 粘住的相邻轮次对 / 全部相邻轮次对&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;取数&lt;/strong&gt;：业务主键（首选）→ 域标签（trace/checkpoint/埋点）→ 离线分类（抽样兜底）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;报告&lt;/strong&gt;：sticky_rate + 平均连续同域长度 + 域切换率 + 会话轮数分布 + 阈值敏感性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结论用法&lt;/strong&gt;：sticky_rate 高 → 值得上有状态模式（Handoffs / Skills）；很低 → 老实用 Subagents，把精力放在上下文工程上。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>14 附录：手写 `@tool` 包装子智能体 vs Deep Agents 的 `subagents=`——同一个原理，两种实现</title><link>https://aicooleo.cn/blog/posts/multiagent-14-appendix-tool-wrap/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-14-appendix-tool-wrap/</guid><description>class TaskToolSchema(BaseModel):</description><pubDate>Sun, 30 Aug 2026 21:12:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话结论&lt;/strong&gt;：&lt;strong&gt;底层原理是同一个&lt;/strong&gt;——都是 &lt;code&gt;04&lt;/code&gt; 篇里的 &lt;strong&gt;Subagents 模式（主管 + 子智能体即工具 + 上下文隔离）&lt;/strong&gt;。区别只在&quot;包装&quot;这件事谁来做：手写版你自己写 &lt;code&gt;create_agent&lt;/code&gt; + &lt;code&gt;@tool&lt;/code&gt;（&lt;code&gt;04&lt;/code&gt; 篇的 &lt;strong&gt;tool per agent&lt;/strong&gt;），Deep Agents 内置的是一套 &lt;strong&gt;单一派发工具（single dispatch tool）&lt;/strong&gt;+ Agent 注册表 + 中间件栈——也就是 &lt;code&gt;04&lt;/code&gt; 篇「决策二：工具模式」里那条&lt;strong&gt;约定优于配置&lt;/strong&gt;的路。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;实测日期：2026-08-30 ｜ 环境：&lt;code&gt;deepagents&lt;/code&gt; 0.7.5 + &lt;code&gt;langchain&lt;/code&gt; 1.3.15 ｜ 模型：DeepSeek（&lt;code&gt;deepseek-v4-flash-vision-exp&lt;/code&gt; → 实际 &lt;code&gt;deepseek-flash&lt;/code&gt;）｜ 协议：Responses API&lt;/p&gt;
&lt;p&gt;实验脚本与抓包文件：&lt;code&gt;demo/deepagents_demo.py&lt;/code&gt;、&lt;code&gt;demo/captured_deepagents.jsonl&lt;/code&gt;；运行方式见&lt;a href=&quot;/posts/multiagent-demo/&quot;&gt;复现实验说明&lt;/a&gt;；手写版对照见 &lt;a href=&quot;/posts/multiagent-13-appendix-4-calls/&quot;&gt;&lt;code&gt;13 附录&lt;/code&gt;&lt;/a&gt;（&lt;code&gt;demo/demo.py&lt;/code&gt;）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;一、先看结论表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;手写版（&lt;code&gt;create_agent&lt;/code&gt; + &lt;code&gt;@tool&lt;/code&gt;，&lt;code&gt;04&lt;/code&gt; 篇 tool per agent）&lt;/th&gt;
&lt;th&gt;Deep Agents（&lt;code&gt;create_deep_agent(subagents=[...])&lt;/code&gt;）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;暴露给模型的工具&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;每个子智能体一个工具&lt;/strong&gt;（本例 &lt;code&gt;schedule_event&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;只有一个 &lt;code&gt;task&lt;/code&gt; 工具&lt;/strong&gt;，参数 &lt;code&gt;description&lt;/code&gt; + &lt;code&gt;subagent_type&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;模型怎么知道有哪些子智能体&lt;/td&gt;
&lt;td&gt;工具名 + 工具描述（tool description）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;task&lt;/code&gt; 的工具描述里自动拼上一份 &lt;strong&gt;Agent 目录&lt;/strong&gt;（&lt;code&gt;- name: description&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;子智能体怎么定义&lt;/td&gt;
&lt;td&gt;自己 &lt;code&gt;create_agent(model, tools, system_prompt)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;传 dict（&lt;code&gt;name&lt;/code&gt;/&lt;code&gt;description&lt;/code&gt;/&lt;code&gt;system_prompt&lt;/code&gt;/&lt;code&gt;tools&lt;/code&gt;/&lt;code&gt;model&lt;/code&gt;/…）或 &lt;code&gt;CompiledSubAgent&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;子智能体的工具&lt;/td&gt;
&lt;td&gt;你显式指定&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;默认继承主 Agent 的全部工具&lt;/strong&gt;（除非显式给 &lt;code&gt;tools&lt;/code&gt; 覆盖）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;上下文隔离&lt;/td&gt;
&lt;td&gt;你自己决定传什么（我们传了 &lt;code&gt;request&lt;/code&gt; 一句话）&lt;/td&gt;
&lt;td&gt;内置 &lt;code&gt;mode:&quot;isolated&quot;&lt;/code&gt;（只传 &lt;code&gt;description&lt;/code&gt;），可切 &lt;code&gt;mode:&quot;fork&quot;&lt;/code&gt;（带父对话历史 + 复用 prompt cache）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;结果怎么回到主管&lt;/td&gt;
&lt;td&gt;&lt;code&gt;return result[&quot;messages&quot;][-1].content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;内置：反向找最后一条&lt;strong&gt;非空 AIMessage&lt;/strong&gt; → 包成 &lt;code&gt;ToolMessage&lt;/code&gt;，并合并 state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;结构化输出&lt;/td&gt;
&lt;td&gt;自己解析&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response_format=&lt;/code&gt; → 父级直接收到 JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;人工介入 / 权限&lt;/td&gt;
&lt;td&gt;自己接 &lt;code&gt;interrupt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每子智能体可配 &lt;code&gt;interrupt_on&lt;/code&gt;、&lt;code&gt;permissions&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可观测性&lt;/td&gt;
&lt;td&gt;自己打点&lt;/td&gt;
&lt;td&gt;自动打 &lt;code&gt;ls_agent_type=&quot;subagent&quot;&lt;/code&gt;；工具里能读 &lt;code&gt;lc_agent_name&lt;/code&gt; 判断是谁在调&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;白送的东西&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;默认注入 &lt;code&gt;general-purpose&lt;/code&gt; 子智能体 + 文件系统工具（ls/read/write/edit/delete/glob/grep）+ todos/规划 + 摘要压缩 + skills + memory + 子智能体工具隔离&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;首轮上下文成本&lt;/td&gt;
&lt;td&gt;小（本例 in=347）&lt;/td&gt;
&lt;td&gt;大（本例 in=2622，工具 schema + 系统提示词都更重）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适合&lt;/td&gt;
&lt;td&gt;想精细控制、轻量、只用一种模式&lt;/td&gt;
&lt;td&gt;想要一整套现成的 agent harness（规划/文件/压缩/技能/子智能体）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;二、原理相同的证据：源码里就是一个&quot;单一派发工具&quot;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;deepagents/middleware/subagents.py&lt;/code&gt;（0.7.5）把 &lt;code&gt;subagents=&lt;/code&gt; 变成了这些东西：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;① 工具 schema：就是 &lt;code&gt;task(description, subagent_type)&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class TaskToolSchema(BaseModel):
    &quot;&quot;&quot;Input schema for the `task` tool.&quot;&quot;&quot;

    description: str = Field(description=(
        &quot;A detailed description of the task for the subagent to perform autonomously. &quot;
        &quot;Include all necessary context and specify the expected output format.&quot;))
    subagent_type: str = Field(description=(
        &quot;The type of subagent to use. Must be one of the available agent types listed in the tool description.&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;② Agent 目录直接拼进工具描述&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;subagent_description_str = &quot;\n&quot;.join(f&quot;- {s[&apos;name&apos;]}: {s[&apos;description&apos;]}&quot; for s in compiled_subagents)
description = TASK_TOOL_DESCRIPTION.format(available_agents=subagent_description_str)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;③ 隔离模式：只把任务描述塞进子智能体&lt;/strong&gt;（和我们手写版传 &lt;code&gt;request&lt;/code&gt; 一句话是同一个动作）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;subagent_state[&quot;messages&quot;] = [HumanMessage(content=description)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;④ 结果回传：反向找最后一条非空 AIMessage，包成 ToolMessage&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for msg in reversed(result[&quot;messages&quot;]):
    if isinstance(msg, AIMessage):
        text = msg.text.rstrip() if msg.text else &quot;&quot;
        if text:
            content = text
            break
return Command(update={**state_update, &quot;messages&quot;: [ToolMessage(content, tool_call_id=tool_call_id)]})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;⑤ 附带的可观测性&lt;/strong&gt;：&lt;code&gt;_subagent_tracing_context()&lt;/code&gt; 会给子智能体运行打上 &lt;code&gt;ls_agent_type=&quot;subagent&quot;&lt;/code&gt; 元数据。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;换句话说：&lt;strong&gt;Deep Agents 并没有发明新机制，它是把 &lt;code&gt;04&lt;/code&gt; 篇「单一派发工具 + 注册表」那一节产品化了&lt;/strong&gt;，再套上 Deep Agents 自己的中间件栈。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;三、抓包实证：线报文里长什么样&lt;/h2&gt;
&lt;p&gt;同一次运行，暴露给模型的工具清单（Responses API 的 &lt;code&gt;tools&lt;/code&gt; 数组，节选）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;• ls            params=[&apos;path&apos;]
• read_file     params=[&apos;file_path&apos;,&apos;offset&apos;,&apos;limit&apos;]
• write_file    params=[&apos;file_path&apos;,&apos;content&apos;]
• edit_file     params=[&apos;file_path&apos;,&apos;old_string&apos;,&apos;new_string&apos;,&apos;replace_all&apos;]
• delete        params=[&apos;file_path&apos;]
• glob          params=[&apos;pattern&apos;,&apos;path&apos;]
• grep          params=[&apos;pattern&apos;,&apos;path&apos;,&apos;glob&apos;,&apos;output_mode&apos;,&apos;max_count&apos;]
• task          params=[&apos;description&apos;,&apos;subagent_type&apos;]      ← 唯一的派发入口
• save_note     params=[&apos;title&apos;,&apos;body&apos;]                     ← 我们自己的业务工具
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;task&lt;/code&gt; 工具的描述（真实报文，节选）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Launch an ephemeral subagent to handle a complex, multi-step task in an isolated context window.

Available agent types and the tools they have access to:
- general-purpose: General-purpose agent for researching complex questions, ...
- research-agent: 用于需要多步调研、比较、查证的问题

Specify subagent_type to select the agent. Usage notes:
- Launch multiple agents concurrently when their tasks are independent, using a single message with multiple tool calls.
- Each invocation is stateless: the agent sees only the prompt you give it and returns a single final report. ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;三个关键信息&lt;/strong&gt;：① &lt;strong&gt;一个工具管所有子智能体&lt;/strong&gt;（靠 &lt;code&gt;subagent_type&lt;/code&gt; 分发）；② &lt;code&gt;general-purpose&lt;/code&gt; 是&lt;strong&gt;自动注入&lt;/strong&gt;的（我们只传了 &lt;code&gt;research-agent&lt;/code&gt;）；③ 描述里直接告诉模型&quot;可以一次消息里并发派多个&quot;、&quot;每次调用是无状态的、只看到你给的那段 prompt&quot;——这跟 &lt;code&gt;04&lt;/code&gt; 篇讲的 specs / inputs 两个阀门一一对应。&lt;/p&gt;
&lt;h3&gt;这次运行的 4 次模型调用（形状和手写版完全一致）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;谁在调模型&lt;/th&gt;
&lt;th&gt;上下文&lt;/th&gt;
&lt;th&gt;产出&lt;/th&gt;
&lt;th&gt;in / out&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;主管&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;system(技术选型助手) + 用户原话（+ 8 个工具的 schema）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;task(description=…, subagent_type=&quot;research-agent&quot;)&lt;/code&gt; + 一句&quot;我来交给子代理&quot;&lt;/td&gt;
&lt;td&gt;2622 / 466&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;子智能体&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;只有它自己的 system + 主管给的任务描述（&lt;strong&gt;输入项数 = 2&lt;/strong&gt;）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ls(path=&quot;/&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2536 / 841&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;子智能体&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;上者 + &lt;code&gt;ls&lt;/code&gt; 结果&lt;/td&gt;
&lt;td&gt;结构化调研结论（回给主管）&lt;/td&gt;
&lt;td&gt;3392 / 1473&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;主管&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;system + 用户原话 + &lt;code&gt;task&lt;/code&gt; 调用 + 工具结果&lt;/td&gt;
&lt;td&gt;面向用户的最终回答&lt;/td&gt;
&lt;td&gt;4561 / 933&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;对照 &lt;code&gt;13&lt;/code&gt; 篇手写版的 &lt;code&gt;①主管派活 → ②子智能体调工具 → ③子智能体作答 → ④主管收尾&lt;/code&gt;——&lt;strong&gt;结构完全同构&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;两个额外观察：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;子智能体默认继承主 Agent 的工具&lt;/strong&gt;：&lt;code&gt;research-agent&lt;/code&gt; 没写 &lt;code&gt;tools&lt;/code&gt;，于是它手里直接有 &lt;code&gt;ls&lt;/code&gt;（文件系统工具），第 2 次调用就去 &lt;code&gt;ls(&quot;/&quot;)&lt;/code&gt; 找资料了。想限制它，就得显式传 &lt;code&gt;tools=&lt;/code&gt;（文档：显式指定会&lt;strong&gt;整体覆盖&lt;/strong&gt;继承来的工具集）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;派活次数由模型决定&lt;/strong&gt;：同一份代码的前一次运行里，主管连续调了 &lt;strong&gt;5 次&lt;/strong&gt; &lt;code&gt;task&lt;/code&gt;（12 条消息，边调研边补细节）；抓包这次只调了 1 次。所以&quot;Deep Agents 一次请求 = 几次模型调用&quot;没有固定答案——&lt;strong&gt;上限取决于模型何时认为够了&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;四、什么时候用哪个&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;你的情况&lt;/th&gt;
&lt;th&gt;建议&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;只需要&quot;一个主管 + 几个专家&quot;、想完全掌控提示词与工具边界、不想引入额外中间件&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;手写 &lt;code&gt;@tool&lt;/code&gt; 包装&lt;/strong&gt;（&lt;code&gt;04&lt;/code&gt; 篇 tool per agent），代码量也就十几行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;子智能体数量多、团队各自维护、希望加一个新专家&quot;不改协调者代码&quot;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;task&lt;/code&gt; 单一派发工具&lt;/strong&gt;（手写或者直接用 Deep Agents）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;需要规划（todos）、虚拟文件系统、超长上下文压缩、技能按需加载、权限与人工介入&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Deep Agents&lt;/strong&gt;（这些是白送的；手写得自己拼）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;想让子智能体&quot;接着父级已开工的活干&quot;&lt;/td&gt;
&lt;td&gt;Deep Agents &lt;code&gt;mode:&quot;fork&quot;&lt;/code&gt;（手写版要自己把父级 messages 传进去）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;只是想让子智能体独立跑一段、要结构化结果&lt;/td&gt;
&lt;td&gt;两者都行；Deep Agents 用 &lt;code&gt;response_format=&lt;/code&gt; 更省事&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;代价提醒&lt;/strong&gt;：Deep Agents 的首轮上下文明显更贵（本例 in=2622 vs 手写版 in=347，因为多了 7 个文件系统工具的 schema 与更重的系统提示词），而且它默认注入 &lt;code&gt;general-purpose&lt;/code&gt;、默认让子智能体继承全部工具——&lt;strong&gt;这两条默认值在&quot;工具很危险&quot;的场景里要主动改掉&lt;/strong&gt;（&lt;code&gt;general_purpose_subagent=GeneralPurposeSubagentProfile(enabled=False)&lt;/code&gt; 关掉默认子智能体；给子智能体显式 &lt;code&gt;tools=&lt;/code&gt; 收窄权限）。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;五、自己复现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 抓包跑 Deep Agents 版（脚本见 demo/deepagents_demo.py）
CAPTURE_OUT=$PWD/captured_deepagents.jsonl \
  /tmp/mitmenv/bin/mitmdump -q -s capture.py -p 8888 --set confdir=/tmp/subagents-demo/.mitmproxy

export CLOUD_MODEL_BASE_URL=https://api.deepseek.com
export CLOUD_MODEL_API_KEY=&amp;lt;你的 key&amp;gt;
export CLOUD_MODEL_NAME=deepseek-v4-flash-vision-exp
export CLOUD_MODEL_PROTOCOL=RESPONSES
HTTPS_PROXY=http://127.0.0.1:8888 SSL_CERT_FILE=/tmp/subagents-demo/.mitmproxy/mitmproxy-ca-cert.pem \
  python deepagents_demo.py

python summarize.py captured_deepagents.jsonl
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;说明：两次抓包（&lt;code&gt;13&lt;/code&gt; 篇的手写版、本篇的 Deep Agents 版）用的是同一个 mitmdump addon，抓包文件只存请求/响应&lt;strong&gt;正文&lt;/strong&gt;，不含请求头与密钥。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>附录：Subagents 为什么是 4 次模型调用——抓包实录</title><link>https://aicooleo.cn/blog/posts/multiagent-13-appendix-4-calls/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-13-appendix-4-calls/</guid><description>一个最小的 Subagents 系统（源码见 demo/demo.py，只有 3 个部件）：</description><pubDate>Sun, 30 Aug 2026 14:38:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话结论&lt;/strong&gt;：Subagents 的 4 次 = &lt;strong&gt;主管派活（1）+ 子智能体干活（它自己也要&quot;决定调工具→拿结果再答&quot;，2 次）+ 主管把结果说成人话（1）&lt;/strong&gt;。第 4 次不是多余的，它是&quot;&lt;strong&gt;工具结果必须由主管变成用户可读的回答&lt;/strong&gt;&quot;这一步——也就是官方文档说的&quot;集中管控的过路费&quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;本篇是 &lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;&lt;code&gt;03 性能对比&lt;/code&gt;&lt;/a&gt; 里「Subagents：4 次调用」的实证附录 ｜ 实测日期：2026-08-30 ｜ 模型：DeepSeek（&lt;code&gt;deepseek-v4-flash-vision-exp&lt;/code&gt;，接口回报 &lt;code&gt;deepseek-flash&lt;/code&gt;）｜ 协议：OpenAI &lt;strong&gt;Responses API&lt;/strong&gt;（&lt;code&gt;/v1/responses&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/posts/multiagent-demo/&quot;&gt;复现实验说明&lt;/a&gt;：&lt;code&gt;demo.py&lt;/code&gt; / &lt;code&gt;capture.py&lt;/code&gt; / &lt;code&gt;summarize.py&lt;/code&gt; / &lt;code&gt;captured.jsonl&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;一、这次抓包到底跑了什么&lt;/h2&gt;
&lt;p&gt;一个最小的 Subagents 系统（实验脚本为 &lt;code&gt;demo/demo.py&lt;/code&gt;，运行方式见&lt;a href=&quot;/posts/multiagent-demo/&quot;&gt;复现实验说明&lt;/a&gt;，只有 3 个部件）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;部件&lt;/th&gt;
&lt;th&gt;代码&lt;/th&gt;
&lt;th&gt;手里有什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;主管 Supervisor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;create_agent(model, tools=[call_calendar_agent], system_prompt=&quot;你是个人助理主管…&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;只有 &lt;strong&gt;1 个工具&lt;/strong&gt; &lt;code&gt;schedule_event&lt;/code&gt;（它就是子智能体的包装壳）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;子智能体 Calendar Agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;create_agent(model, tools=[create_calendar_event], system_prompt=&quot;你是日历助手…&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;只有 &lt;strong&gt;1 个工具&lt;/strong&gt; &lt;code&gt;create_calendar_event&lt;/code&gt;（桩函数，代替真实日历 API）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;包装工具&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@tool(&quot;schedule_event&quot;)&lt;/code&gt; → &lt;code&gt;calendar_agent.invoke(...)&lt;/code&gt;，只返回 &lt;code&gt;messages[-1]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;——&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;用户输入一句话：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;帮我约明天下午 3 点和张三开产品评审会，1 小时
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、4 次调用的时序（每条都标注了抓包序号与耗时）&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;用户请求
   │
   │  ① 11:35:03  主管第一次调模型（in=347 tokens）
   │     上下文 = [主管的 system prompt, 用户原话]
   │     产出 → function_call: schedule_event({&quot;request&quot;:&quot;…时长 1 小时&quot;})
   ▼
┌─ 工具 schedule_event 被调用（进程内，不是 HTTP）：主管把活转给日历子智能体
│     │
│     │  ② 11:35:06  日历子智能体调模型（in=398 tokens）
│     │     上下文 = [日历助手的 system prompt, 主管转交的那句话]   ← 注意：看不到主对话历史
│     │     产出 → function_call: create_calendar_event({&quot;title&quot;:&quot;产品评审会&quot;, …})
│     ▼
│  ┌─ 工具 create_calendar_event 执行：返回 &quot;OK: created &apos;产品评审会&apos; at 2025-06-11T15:00:00 for 60 min&quot;
│  │     │
│  │     │  ③ 11:35:07  日历子智能体再调模型（in=1020 tokens）
│  │     │     上下文 = [system, 主管转交的话, 它自己的 function_call, 工具结果]
│  │     │     产出 → 文本：&quot;已为你创建「产品评审会」，时间：明天（2025-06-11）15:00，时长 1 小时。&quot;
│  │     └─ 这句文本作为 schedule_event 的返回值，回到主管的消息列表里
│  └───┘
│
│  ④ 11:35:08  主管第二次调模型（in=462 tokens）  ← 这就是你问的&quot;第 4 次&quot;
│     上下文 = [主管 system prompt, 用户原话, ①的 function_call, ③返回的工具结果]
│     产出 → 面向用户的最终回答：&quot;已经帮你安排好了：明天（2025-06-11）下午 3 点和张三的产品评审会，时长 1 小时。&quot;
▼
最终回复
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、4 次模型调用的账（来自 &lt;code&gt;captured.jsonl&lt;/code&gt; 的真实数字）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;时间&lt;/th&gt;
&lt;th&gt;谁在调&lt;/th&gt;
&lt;th&gt;请求上下文里有什么&lt;/th&gt;
&lt;th&gt;产出&lt;/th&gt;
&lt;th&gt;in&lt;/th&gt;
&lt;th&gt;out&lt;/th&gt;
&lt;th&gt;其中思考&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;①&lt;/td&gt;
&lt;td&gt;11:35:03&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;主管&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;system(主管) + 用户原话&lt;/td&gt;
&lt;td&gt;&lt;code&gt;function_call: schedule_event&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;347&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;②&lt;/td&gt;
&lt;td&gt;11:35:06&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;子智能体&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;system(日历) + 转交的请求&lt;/td&gt;
&lt;td&gt;&lt;code&gt;function_call: create_calendar_event&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;398&lt;/td&gt;
&lt;td&gt;584&lt;/td&gt;
&lt;td&gt;494&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;③&lt;/td&gt;
&lt;td&gt;11:35:07&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;子智能体&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;上面 4 项 + 它自己的工具调用 + 工具结果&lt;/td&gt;
&lt;td&gt;文本（给主管看）&lt;/td&gt;
&lt;td&gt;1020&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;④&lt;/td&gt;
&lt;td&gt;11:35:08&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;主管&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;system(主管) + 用户原话 + ①的调用 + ③的结果&lt;/td&gt;
&lt;td&gt;文本（给用户看）&lt;/td&gt;
&lt;td&gt;462&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;合计&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2227&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;720&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;端到端耗时约 &lt;strong&gt;5 秒&lt;/strong&gt;（4 次调用串行：1 次主管决策 + 2 次子智能体 + 1 次主管收尾）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;四、原始报文（Responses API，节选真实内容）&lt;/h2&gt;
&lt;h3&gt;① 主管第一次调用：请求&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;model&quot;: &quot;deepseek-v4-flash-vision-exp&quot;,
  &quot;input&quot;: [
    {&quot;type&quot;: &quot;message&quot;, &quot;role&quot;: &quot;system&quot;,
     &quot;content&quot;: &quot;你是个人助理主管。遇到日程类请求时，调用 schedule_event 交给日历助手，拿到结果后用一句自然语言回复用户。&quot;},
    {&quot;type&quot;: &quot;message&quot;, &quot;role&quot;: &quot;user&quot;,
     &quot;content&quot;: &quot;帮我约明天下午 3 点和张三开产品评审会，1 小时&quot;}
  ],
  &quot;tools&quot;: [
    {&quot;type&quot;: &quot;function&quot;, &quot;name&quot;: &quot;schedule_event&quot;,
     &quot;description&quot;: &quot;把用户的日程安排请求交给日历助手处理。入参 request 应是完整的一句话请求。&quot;,
     &quot;parameters&quot;: {&quot;type&quot;: &quot;object&quot;, &quot;properties&quot;: {&quot;request&quot;: {&quot;type&quot;: &quot;string&quot;}}, &quot;required&quot;: [&quot;request&quot;]}}
  ],
  &quot;max_output_tokens&quot;: 2048
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;code&gt;tools&lt;/code&gt; 里只有 &lt;code&gt;schedule_event&lt;/code&gt;——主管&lt;strong&gt;根本不知道&lt;/strong&gt; &lt;code&gt;create_calendar_event&lt;/code&gt; 的存在。这就是&quot;子智能体即工具&quot;的报文级证据。&lt;/p&gt;
&lt;h3&gt;① 主管第一次调用：响应&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;id&quot;: &quot;713dda7a-2723-4f52-b22a-26483d8ee7e9&quot;,
  &quot;model&quot;: &quot;deepseek-flash&quot;,
  &quot;status&quot;: &quot;completed&quot;,
  &quot;output&quot;: [
    {&quot;type&quot;: &quot;reasoning&quot;, &quot;status&quot;: &quot;completed&quot;,
     &quot;content&quot;: [{&quot;type&quot;: &quot;reasoning_text&quot;,
                  &quot;text&quot;: &quot;The user wants to schedule a meeting. I should call schedule_event with the complete request.&quot;}],
     &quot;encrypted_content&quot;: &quot;713dda7a-2723-4f52-b22a-26483d8ee7e9-0&quot;},
    {&quot;type&quot;: &quot;function_call&quot;, &quot;status&quot;: &quot;completed&quot;,
     &quot;name&quot;: &quot;schedule_event&quot;,
     &quot;arguments&quot;: &quot;{\&quot;request\&quot;: \&quot;帮我约明天下午 3 点和张三开产品评审会，时长 1 小时\&quot;}&quot;,
     &quot;call_id&quot;: &quot;call_00_Fsa6XTMl6dbQxpR4TFIb4432&quot;}
  ],
  &quot;usage&quot;: {&quot;input_tokens&quot;: 347, &quot;output_tokens&quot;: 75,
            &quot;output_tokens_details&quot;: {&quot;reasoning_tokens&quot;: 18}, &quot;total_tokens&quot;: 422}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键&lt;/strong&gt;：这一次响应里&lt;strong&gt;没有任何面向用户的文本&lt;/strong&gt;（&lt;code&gt;output&lt;/code&gt; 只有 thinking + function_call）。所以此刻主管&lt;strong&gt;还没有能力回答用户&lt;/strong&gt;——它连&quot;会到底开成没有&quot;都不知道。&lt;strong&gt;这就是必须还有第 4 次调用的根本原因。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;② 子智能体的上下文：只有两句话（上下文隔离的实证）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&quot;input&quot;: [
  {&quot;type&quot;: &quot;message&quot;, &quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;你是日历助手。收到日程请求后，必须调用 create_calendar_event 创建事件，然后用一句话确认结果（包含标题与时间）。&quot;},
  {&quot;type&quot;: &quot;message&quot;, &quot;role&quot;: &quot;user&quot;,   &quot;content&quot;: &quot;帮我约明天下午 3 点和张三开产品评审会，时长 1 小时&quot;}
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;子智能体&lt;strong&gt;看不到&lt;/strong&gt;主管的 system prompt，也看不到用户与主管的完整历史；它只拿到主管&quot;转交的那一句话&quot;（而且主管把原话改写成了带&quot;时长 1 小时&quot;的版本）。反过来，主管也看不到子智能体内部的推理与工具调用——&lt;strong&gt;只有 ③ 的最后一条文本会回去&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;③ 子智能体的第二次调用：请求（工具结果回灌）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&quot;input&quot;: [
  {&quot;type&quot;: &quot;message&quot;, &quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;你是日历助手。…&quot;},
  {&quot;type&quot;: &quot;message&quot;, &quot;role&quot;: &quot;user&quot;,   &quot;content&quot;: &quot;帮我约明天下午 3 点和张三开产品评审会，时长 1 小时&quot;},
  {&quot;type&quot;: &quot;function_call&quot;, &quot;name&quot;: &quot;create_calendar_event&quot;,
   &quot;arguments&quot;: &quot;{\&quot;title\&quot;: \&quot;产品评审会\&quot;, \&quot;start_iso\&quot;: \&quot;2025-06-11T15:00:00\&quot;, \&quot;duration_minutes\&quot;: 60}&quot;,
   &quot;call_id&quot;: &quot;call_00_…&quot;},
  {&quot;type&quot;: &quot;function_call_output&quot;, &quot;call_id&quot;: &quot;call_00_…&quot;,
   &quot;output&quot;: &quot;OK: created &apos;产品评审会&apos; at 2025-06-11T15:00:00 for 60 min&quot;}
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;响应（③）：&lt;code&gt;output_text = &quot;已为你创建「产品评审会」，时间：明天（2025-06-11）15:00，时长 1 小时。&quot;&lt;/code&gt; → 这句就是 &lt;code&gt;schedule_event&lt;/code&gt; 工具的返回值。&lt;/p&gt;
&lt;h3&gt;④ 主管第二次调用：请求（工具结果回到主管手里）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&quot;input&quot;: [
  {&quot;type&quot;: &quot;message&quot;, &quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;你是个人助理主管。…&quot;},
  {&quot;type&quot;: &quot;message&quot;, &quot;role&quot;: &quot;user&quot;,   &quot;content&quot;: &quot;帮我约明天下午 3 点和张三开产品评审会，1 小时&quot;},
  {&quot;type&quot;: &quot;reasoning&quot;, &quot;content&quot;: [{&quot;type&quot;: &quot;reasoning_text&quot;, &quot;text&quot;: &quot;The user wants to schedule a meeting. I should call schedule_event…&quot;}],
   &quot;encrypted_content&quot;: &quot;713dda7a-…-0&quot;},
  {&quot;type&quot;: &quot;function_call&quot;, &quot;name&quot;: &quot;schedule_event&quot;,
   &quot;arguments&quot;: &quot;{\&quot;request\&quot;: \&quot;帮我约明天下午 3 点和张三开产品评审会，时长 1 小时\&quot;}&quot;,
   &quot;call_id&quot;: &quot;call_00_Fsa6XTMl6dbQxpR4TFIb4432&quot;},
  {&quot;type&quot;: &quot;function_call_output&quot;, &quot;call_id&quot;: &quot;call_00_Fsa6XTMl6dbQxpR4TFIb4432&quot;,
   &quot;output&quot;: &quot;已为你创建「产品评审会」，时间：明天（2025-06-11）15:00，时长 1 小时。&quot;}
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这里的 &lt;code&gt;function_call_output&lt;/code&gt; 就是第 4 次调用的理由&lt;/strong&gt;：主管的消息列表现在以&quot;工具结果&quot;结尾，而不是以&quot;助手对用户说的话&quot;结尾。模型必须再跑一次，才能把这条工具结果&lt;strong&gt;变成一条面向用户的 &lt;code&gt;assistant&lt;/code&gt; 消息&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;④ 主管第二次调用：响应（最终回答）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;model&quot;: &quot;deepseek-flash&quot;,
  &quot;status&quot;: &quot;completed&quot;,
  &quot;output&quot;: [
    {&quot;type&quot;: &quot;message&quot;, &quot;role&quot;: &quot;assistant&quot;, &quot;status&quot;: &quot;completed&quot;, &quot;phase&quot;: &quot;final_answer&quot;,
     &quot;content&quot;: [{&quot;type&quot;: &quot;output_text&quot;,
                  &quot;text&quot;: &quot;已经帮你安排好了：明天（2025-06-11）下午 3 点和张三的产品评审会，时长 1 小时。&quot;}]}
  ],
  &quot;usage&quot;: {&quot;input_tokens&quot;: 462, &quot;output_tokens&quot;: 31,
            &quot;input_tokens_details&quot;: {&quot;cached_tokens&quot;: 256}, &quot;total_tokens&quot;: 493}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;五、第 4 次调用到底买到了什么&lt;/h2&gt;
&lt;p&gt;把 ① 和 ④ 对比着看最清楚——&lt;strong&gt;同一个主管、同一个 system prompt，唯一的差别是上下文末尾多了工具结果&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;① 派活前&lt;/th&gt;
&lt;th&gt;④ 收结果后&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;上下文末尾&lt;/td&gt;
&lt;td&gt;用户原话&lt;/td&gt;
&lt;td&gt;用户原话 + &lt;code&gt;function_call&lt;/code&gt; + &lt;code&gt;function_call_output&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;产出&lt;/td&gt;
&lt;td&gt;&lt;code&gt;function_call&lt;/code&gt;（没有文本）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;面向用户的文本&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;它知道&quot;办成了吗&quot;&lt;/td&gt;
&lt;td&gt;❌ 不知道&lt;/td&gt;
&lt;td&gt;✅ 知道（&quot;已为你创建…&quot;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;能不能对用户说话&lt;/td&gt;
&lt;td&gt;❌ 不能&lt;/td&gt;
&lt;td&gt;✅ 能&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;所以第 4 次买到的是四样东西（前三样是本例直接体现的，第四样是多智能体场景的关键）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;把内部结果翻译成&quot;人话&quot;&lt;/strong&gt;：子智能体返回的是给程序看的结果文本，主管负责组织成面向用户的一句话；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;闭环&lt;/strong&gt;：让消息列表以 &lt;code&gt;assistant&lt;/code&gt; 消息结尾（前端&quot;可以输入了&quot;、对话历史合法）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;决定要不要继续&lt;/strong&gt;：主管看到结果后可以选择再派一个子智能体、重试、或追问用户——&lt;strong&gt;这个&quot;下一步判断&quot;必须有模型参与&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合并多个子智能体的结果&lt;/strong&gt;：并行派了 3 个子智能体时，只有主管能把 3 份结果合成一个回答。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;六、次数公式（和官方文档的数字对得上）&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;一次用户请求的模型调用次数 ≈
    1（主管派活）
  + Σ 每个被调用的子智能体（1 次&quot;决定调不调工具&quot; + 1 次&quot;拿结果作答&quot;，或最少 1 次）
  + 1（主管收尾）
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;公式&lt;/th&gt;
&lt;th&gt;次数&lt;/th&gt;
&lt;th&gt;与官方文档对照&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;本例：1 个子智能体 + 1 次工具调用&lt;/td&gt;
&lt;td&gt;1 + 2 + 1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;官方「一次性请求」Subagents = 4 calls ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 个子智能体，各自直接作答（无工具）&lt;/td&gt;
&lt;td&gt;1 + 3 + 1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;官方「多领域」Subagents = 5 calls / ~9K tokens ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;子智能体每多一次工具调用&lt;/td&gt;
&lt;td&gt;每多 1 次工具调用 +1 次模型调用&lt;/td&gt;
&lt;td&gt;4 → 6 …&lt;/td&gt;
&lt;td&gt;官方没写，但机制决定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;对照：Handoffs 一次性请求&lt;/td&gt;
&lt;td&gt;交接 1 + 调工具 1 + 作答 1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;官方「一次性请求」Handoffs = 3 calls ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：Subagents 的第 4 次（推广到 N 个子智能体就是最后那 1 次）&lt;strong&gt;是这个模式的&quot;结构成本&quot;&lt;/strong&gt;：只要&quot;所有结果都必须回到主管、由主管对用户说话&quot;，就必须多一次汇总调用。它换来的是集中管控、结果合并和一致口径——&lt;strong&gt;当这些正好是你需要的，这 1 次就值；当任务简单、单一领域时，它就是纯浪费，该换成 Handoffs / Skills / Router（都是 3 次）。&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;七、自己复现（三步）&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 0) 准备：一个 Python 3.11 环境（需 langchain 1.x + langchain-openai + httpx）+ 一个隔离环境装 mitmproxy
python -m venv /tmp/mitmenv &amp;amp;&amp;amp; /tmp/mitmenv/bin/pip install mitmproxy

# 1) 起代理（CA 生成在 /tmp/subagents-demo/.mitmproxy/）
/tmp/mitmenv/bin/mitmdump -q -s demo/capture.py -p 8888 --set confdir=/tmp/subagents-demo/.mitmproxy

# 2) 让 demo 走代理跑（模型配置用环境变量，勿写进代码）
export CLOUD_MODEL_BASE_URL=https://api.deepseek.com
export CLOUD_MODEL_API_KEY=&amp;lt;你的 key&amp;gt;
export CLOUD_MODEL_NAME=deepseek-v4-flash-vision-exp
export CLOUD_MODEL_PROTOCOL=RESPONSES
HTTPS_PROXY=http://127.0.0.1:8888 SSL_CERT_FILE=/tmp/subagents-demo/.mitmproxy/mitmproxy-ca-cert.pem \
  python demo/demo.py

# 3) 把抓包汇总成人能读的清单
python demo/summarize.py demo/captured.jsonl
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;说明：&lt;code&gt;SSL_CERT_FILE&lt;/code&gt; 指向 mitmproxy 的 CA 证书，httpx/OpenAI SDK 才会信任代理的中间人证书；不加这个会报 &lt;code&gt;CERTIFICATE_VERIFY_FAILED&lt;/code&gt;。
本次抓包文件 &lt;code&gt;demo/captured.jsonl&lt;/code&gt; 只保存请求/响应&lt;strong&gt;正文&lt;/strong&gt;，不含任何请求头与密钥。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>12 实战：用 Router 搭多源知识库（先分诊，再并行查，最后汇总）</title><link>https://aicooleo.cn/blog/posts/multiagent-12-case-router/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-12-case-router/</guid><description>这就像医院的分诊台。你跟护士说&quot;我胸口疼，还头晕&quot;，护士不会把你塞给一个&quot;全能医生&quot;，而是把你分到心内科、神经内科两个诊室同时排队，最后你自己把两份诊断意见合起来看。路由（Router）就是那个分诊台：先把问题拆开，再并行派给对口的专科 …</description><pubDate>Sat, 29 Aug 2026 22:46:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：这就像医院的分诊台。你跟护士说&quot;我胸口疼，还头晕&quot;，护士不会把你塞给一个&quot;全能医生&quot;，而是把你分到心内科、神经内科两个诊室&lt;strong&gt;同时&lt;/strong&gt;排队，最后你自己把两份诊断意见合起来看。路由（Router）就是那个分诊台：先把问题拆开，再并行派给对口的专科 Agent，最后汇总成一份答案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;这个教程要解决什么&lt;/h2&gt;
&lt;p&gt;组织里的知识天然是分片的：代码在 GitHub，流程规范在 Notion，经验和踩坑在 Slack。用户问一句 &quot;How do I authenticate API requests?&quot;，答案可能同时散在这三个地方。&lt;/p&gt;
&lt;p&gt;最直觉的做法是：&lt;strong&gt;给一个大 Agent 挂上所有工具，让它自己挑&lt;/strong&gt;。三五个工具时能跑，规模一大就出事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;选错工具&lt;/strong&gt;：工具从 7 个涨到 30 个，模型会&quot;看着像就点&quot;，检索质量断崖式下跌。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提示词被摊薄&lt;/strong&gt;：一份 system prompt 要同时教它搜代码、读 Notion、聊 Slack，结果哪一块都不精。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上下文互相污染&lt;/strong&gt;：A 源返回的噪音挤占了 B 源的推理空间，token 还烧得更快。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;串行慢&lt;/strong&gt;：一个 Agent 依次查三个源，端到端延迟是三次检索之和。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;权限说不清&lt;/strong&gt;：Slack 的私下讨论、Notion 的 HR 文档，未必该出现在每一个回答里。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原文的方案是把&quot;一个什么都会的 Agent&quot;换成&quot;&lt;strong&gt;一个分诊台 + 几个专科 Agent&lt;/strong&gt;&quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;三个垂直领域（vertical）：GitHub（搜代码 / issue / PR）、Notion（搜内部文档 / wiki）、Slack（搜讨论 / 线程）。&lt;/li&gt;
&lt;li&gt;用户提问后，一次请求里完成三件事：&lt;strong&gt;分类（classify）→ 并行检索（parallel retrieval）→ 汇总（synthesis）&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也要先泼一盆冷水：&lt;strong&gt;别为了架构而架构&lt;/strong&gt;。如果只有一个知识源、工具不到 5 个，单 Agent 挂工具就够了，Router 只会多给你一层模型调用和一张图要维护。&lt;/p&gt;
&lt;h2&gt;整体架构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;graph LR
    A([用户问题 Query]) --&amp;gt; B[分诊 Router&amp;lt;br/&amp;gt;分类 Classify]
    B --&amp;gt;|并行分发 Send| C[GitHub Agent&amp;lt;br/&amp;gt;代码 / Issue / PR]
    B --&amp;gt;|并行分发 Send| D[Notion Agent&amp;lt;br/&amp;gt;内部文档 / Wiki]
    B --&amp;gt;|并行分发 Send| E[Slack Agent&amp;lt;br/&amp;gt;讨论 / 线程]
    C --&amp;gt; F[汇总 Synthesis&amp;lt;br/&amp;gt;归并 Reducer 收结果]
    D --&amp;gt; F
    E --&amp;gt; F
    F --&amp;gt; G([合并答案 Combined answer])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;① 左边只有&lt;strong&gt;一次分诊&lt;/strong&gt;：路由（Router）用一次模型调用把用户问题拆成一串&quot;面向某个知识源的子问题&quot;，而不是把原问题原样丢给三个 Agent。&lt;/p&gt;
&lt;p&gt;② 中间是&lt;strong&gt;扇出（fan-out）&lt;/strong&gt;：被点名的 Agent 通过 LangGraph 的 &lt;code&gt;Send&lt;/code&gt; 同时开跑；没被点名的（本例中的 Slack）根本不启动——省调用，也省延迟。&lt;/p&gt;
&lt;p&gt;③ 右边是&lt;strong&gt;先归并、再汇总&lt;/strong&gt;：归并函数（reducer）把并行结果拼成一个 list，随后一次&quot;汇总&quot;模型调用把它揉成不重复、有取舍的答案。&lt;/p&gt;
&lt;p&gt;再看并行时序：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;用户问题 ─▶ 分类（1 次模型调用）
             ├─▶ 知识源 A 检索 ─┐
             ├─▶ 知识源 B 检索 ─┼─▶ 归并（reducer）─▶ 汇总回答
             └─▶ 知识源 C 检索 ─┘        （1 次模型调用）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意这里的&quot;1 次模型调用&quot;只统计 Router 自己这一层。每个专科 Agent 内部还会自己调模型（先决定调哪个工具，再根据工具结果写答案），所以端到端通常是 &lt;code&gt;1 + 被点名 Agent 各自的 1~2 次 + 1&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/multidomain-router.png&quot; alt=&quot;Router 在多领域 multi-domain 场景下的执行示意：一次分诊 + 并行调用，共 5 次模型调用&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;上图来自官方《Multi-agent》总览页的对比实验：跨领域任务下 Router 共 5 次模型调用、并行执行，token 约 9K。原图出处：https://docs.langchain.com/oss/python/langchain/multi-agent&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;分步拆解&lt;/h2&gt;
&lt;h3&gt;§0 准备（原文 Setup 一节，压缩）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pip install langchain langgraph
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;import getpass, os

os.environ[&quot;LANGSMITH_TRACING&quot;] = &quot;true&quot;
os.environ[&quot;LANGSMITH_API_KEY&quot;] = getpass.getpass()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原文随后用 7 个标签页列了各家模型接法（OpenAI / Anthropic / Azure / Google Gemini / AWS Bedrock / HuggingFace / OpenRouter），这里不展开——&lt;strong&gt;换 Provider 只改 &lt;code&gt;init_chat_model&lt;/code&gt; 那一行&lt;/strong&gt;，后面代码完全不动。另外原文出现的 &lt;code&gt;gpt-5.5&lt;/code&gt;、&lt;code&gt;claude-sonnet-4-6&lt;/code&gt; 等模型名是它写作时的示例，照抄前请替换成你手头可用的模型。&lt;/p&gt;
&lt;h3&gt;§1 定义状态（state）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：先把数据契约定下来——主图状态长什么样、子 Agent 收什么、回什么。&lt;strong&gt;为什么&lt;/strong&gt;：并行节点之间不能靠互相传参通信，只能靠共享状态；契约定清楚了，&lt;code&gt;Send&lt;/code&gt; 和归并函数才有地方落。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from typing import Annotated, Literal, TypedDict
import operator


class AgentInput(TypedDict):
    &quot;&quot;&quot;每个子 Agent 的输入状态：只要一个问题。&quot;&quot;&quot;
    query: str


class AgentOutput(TypedDict):
    &quot;&quot;&quot;每个子 Agent 的输出。&quot;&quot;&quot;
    source: str
    result: str


class Classification(TypedDict):
    &quot;&quot;&quot;一条路由决策：调哪个 Agent、问它什么。&quot;&quot;&quot;
    source: Literal[&quot;github&quot;, &quot;notion&quot;, &quot;slack&quot;]
    query: str


class RouterState(TypedDict):
    query: str
    classifications: list[Classification]
    results: Annotated[list[AgentOutput], operator.add]  # 归并函数：并行结果累加
    final_answer: str
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;四个类型分工很清楚：&lt;code&gt;AgentInput&lt;/code&gt; 是发给专科 Agent 的&quot;精简挂号单&quot;（只有 &lt;code&gt;query&lt;/code&gt;，不带整张图的状态），&lt;code&gt;AgentOutput&lt;/code&gt; 是回执，&lt;code&gt;Classification&lt;/code&gt; 是一条路由决策，&lt;code&gt;RouterState&lt;/code&gt; 是主图状态。&lt;strong&gt;注意 &lt;code&gt;results&lt;/code&gt; 那一行的 &lt;code&gt;Annotated[..., operator.add]&lt;/code&gt;&lt;/strong&gt;——这是整篇最关键的一处声明，后面会专门讲。&lt;/p&gt;
&lt;h3&gt;§2 各知识源的工具&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：给每个垂直领域写工具。&lt;strong&gt;为什么&lt;/strong&gt;：工具就是专科医生的&quot;检查设备&quot;，专科 Agent 的能力上限基本由工具决定。&lt;/p&gt;
&lt;p&gt;原文一共 7 个工具：GitHub 三个（搜代码 / issue / PR）、Notion 两个（搜文档 / 取页面）、Slack 两个（搜消息 / 取线程）。生产环境里这些函数背后是真实的 API；教程里全是返回假数据的桩（stub）。下面只挑三个代表性写法（顺带一提：原文 &lt;code&gt;search_prs&lt;/code&gt; 的返回值写成了 &lt;code&gt;f&quot;PR #156 added JWT authentication...&quot;&lt;/code&gt;，里面并没有占位符，&lt;code&gt;f&lt;/code&gt; 前缀其实可以省——原文如此）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import tool


@tool
def search_code(query: str, repo: str = &quot;main&quot;) -&amp;gt; str:
    &quot;&quot;&quot;在 GitHub 仓库里搜代码。&quot;&quot;&quot;
    return f&quot;Found code matching &apos;{query}&apos; in {repo}: authentication middleware in src/auth.py&quot;


@tool
def search_issues(query: str) -&amp;gt; str:
    &quot;&quot;&quot;搜 GitHub 的 issue 和 pull request。&quot;&quot;&quot;
    return f&quot;Found 3 issues matching &apos;{query}&apos;: #142 (API auth docs), #89 (OAuth flow), #203 (token refresh)&quot;


# ...（略）另有 search_prs / search_notion / get_page / search_slack / get_thread，共 7 个
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;工具函数的 docstring 不是装饰，它就是&lt;strong&gt;给模型看的工具说明&lt;/strong&gt;——写清楚&quot;什么时候用我&quot;，比写清楚&quot;我做了什么&quot;更重要。&lt;/p&gt;
&lt;h3&gt;§3 专用 Agent&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：每个知识源建一个 Agent。&lt;strong&gt;为什么&lt;/strong&gt;：把&quot;领域知识&quot;下沉到各自的工具集 + system prompt 里，谁也不用迁就谁。三个 Agent 结构完全同构，只换工具和提示词。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.agents import create_agent
from langchain.chat_models import init_chat_model

model = init_chat_model(&quot;openai:gpt-5.5&quot;)

github_agent = create_agent(
    model,
    tools=[search_code, search_issues, search_prs],
    system_prompt=(
        &quot;You are a GitHub expert. Answer questions about code, &quot;
        &quot;API references, and implementation details by searching &quot;
        &quot;repositories, issues, and pull requests.&quot;
    ),
)
# 中文大意：GitHub 专家，只从仓库 / issue / PR 里找代码与实现细节的答案

# notion_agent / slack_agent 写法完全一样，只是 tools 与 system_prompt 不同
# ...（略）
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;§4 路由工作流&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：把四个步骤写成节点函数——分类、扇出、查 Agent、汇总。&lt;strong&gt;为什么&lt;/strong&gt;：这是整篇的核心，&lt;code&gt;Send&lt;/code&gt; 和归并函数都在这节露面。&lt;/p&gt;
&lt;p&gt;原文把工作流拆成四步：**分类（Classify）**决定叫谁、问什么；**路由（Route）**用 &lt;code&gt;Send&lt;/code&gt; 扇出；&lt;strong&gt;查 Agent&lt;/strong&gt;各自返回 &lt;code&gt;AgentOutput&lt;/code&gt;；**汇总（Synthesize）**合成答案。&lt;/p&gt;
&lt;p&gt;第一步，分类。用结构化输出（structured output）保证模型吐出的是合法结构，而不是一段需要正则去抠的 JSON：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pydantic import BaseModel, Field
from langgraph.graph import StateGraph, START, END
from langgraph.types import Send

router_llm = init_chat_model(&quot;openai:gpt-5.4-mini&quot;)


class ClassificationResult(BaseModel):
    &quot;&quot;&quot;把用户问题拆成若干「面向某个知识源的子问题」。&quot;&quot;&quot;
    classifications: list[Classification] = Field(
        description=&quot;List of agents to invoke with their targeted sub-questions&quot;
    )


def classify_query(state: RouterState) -&amp;gt; dict:
    &quot;&quot;&quot;分类：决定叫哪几个 Agent，并给每个 Agent 派一个定制子问题。&quot;&quot;&quot;
    structured_llm = router_llm.with_structured_output(ClassificationResult)

    result = structured_llm.invoke([
        {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: SYSTEM_PROMPT},
        {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: state[&quot;query&quot;]},
    ])

    return {&quot;classifications&quot;: result.classifications}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;SYSTEM_PROMPT&lt;/code&gt; 原文写得很长，核心就三句话（原文为英文，这里译成中文便于阅读）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;列出三个源各自擅长什么：github 管代码 / API / 实现细节，notion 管内部文档 / 流程 / wiki，slack 管团队讨论与非正式知识。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只返回真正相关的源&lt;/strong&gt;，并为每个源生成一个&quot;为该知识域定制&quot;的子问题。&lt;/li&gt;
&lt;li&gt;给了个例子：&lt;code&gt;How do I authenticate API requests?&lt;/code&gt; 只该拆给 github 和 notion，slack 不相关就直接省略。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第二步，扇出。这是全文最短也最值钱的函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def route_to_agents(state: RouterState) -&amp;gt; list[Send]:
    &quot;&quot;&quot;扇出：把每条分类变成一次 Send，谁被点到谁就跑。&quot;&quot;&quot;
    return [
        Send(c[&quot;source&quot;], {&quot;query&quot;: c[&quot;query&quot;]})
        for c in state[&quot;classifications&quot;]
    ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Send&lt;/code&gt; 的第一个参数是目标节点名，第二个参数是&lt;strong&gt;只发给那个节点的状态&lt;/strong&gt;。分类有几条，就发几路。&lt;/p&gt;
&lt;p&gt;第三步，查 Agent。每个节点拿到的是精简后的 &lt;code&gt;AgentInput&lt;/code&gt;，不是整张 &lt;code&gt;RouterState&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def query_github(state: AgentInput) -&amp;gt; dict:
    &quot;&quot;&quot;查 GitHub Agent。&quot;&quot;&quot;
    result = github_agent.invoke({
        &quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: state[&quot;query&quot;]}]
    })
    return {&quot;results&quot;: [{&quot;source&quot;: &quot;github&quot;, &quot;result&quot;: result[&quot;messages&quot;][-1].content}]}


# ...（略）query_notion / query_slack 写法一模一样，只换 agent 变量与 source 名
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意返回值：&lt;strong&gt;永远是包了一层的 list&lt;/strong&gt;。因为并行节点各自往 &lt;code&gt;results&lt;/code&gt; 里写，写的是&quot;自己那一份&quot;，拼起来交给归并函数去做。&lt;/p&gt;
&lt;p&gt;第四步，汇总：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def synthesize_results(state: RouterState) -&amp;gt; dict:
    &quot;&quot;&quot;汇总：等所有并行分支跑完，把结果揉成一个答案。&quot;&quot;&quot;
    if not state[&quot;results&quot;]:
        return {&quot;final_answer&quot;: &quot;No results found from any knowledge source.&quot;}

    formatted = [
        f&quot;**From {r[&apos;source&apos;].title()}:**\n{r[&apos;result&apos;]}&quot;
        for r in state[&quot;results&quot;]
    ]

    synthesis_response = router_llm.invoke([
        {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: (
            f&apos;把下面的检索结果汇总成对原始问题的回答：&quot;{state[&quot;query&quot;]}&quot;\n&apos;
            &quot;- 合并多源信息，不要重复\n&quot;
            &quot;- 突出最相关、最可执行的内容\n&quot;
            &quot;- 标出来源之间的分歧\n&quot;
            &quot;- 保持简洁、有条理&quot;
        )},
        {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;\n\n&quot;.join(formatted)},
    ])

    return {&quot;final_answer&quot;: synthesis_response.content}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个细节：&lt;strong&gt;空结果要兜底&lt;/strong&gt;（一个源都没命中时别让模型硬编）；&lt;strong&gt;提示词里带回原始问题&lt;/strong&gt;（&lt;code&gt;state[&quot;query&quot;]&lt;/code&gt;），否则模型容易对着检索片段自说自话。&lt;/p&gt;
&lt;h3&gt;§5 编译图&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：把节点和边接起来并 &lt;code&gt;compile()&lt;/code&gt;。&lt;strong&gt;为什么&lt;/strong&gt;：静态边负责&quot;谁接谁&quot;，条件边 + &lt;code&gt;Send&lt;/code&gt; 负责&quot;这次到底跑哪几个&quot;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;workflow = (
    StateGraph(RouterState)
    .add_node(&quot;classify&quot;, classify_query)
    .add_node(&quot;github&quot;, query_github)
    .add_node(&quot;notion&quot;, query_notion)
    .add_node(&quot;slack&quot;, query_slack)
    .add_node(&quot;synthesize&quot;, synthesize_results)
    .add_edge(START, &quot;classify&quot;)
    .add_conditional_edges(&quot;classify&quot;, route_to_agents, [&quot;github&quot;, &quot;notion&quot;, &quot;slack&quot;])
    .add_edge(&quot;github&quot;, &quot;synthesize&quot;)
    .add_edge(&quot;notion&quot;, &quot;synthesize&quot;)
    .add_edge(&quot;slack&quot;, &quot;synthesize&quot;)
    .add_edge(&quot;synthesize&quot;, END)
    .compile()
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三个 Agent 节点都连到 &lt;code&gt;synthesize&lt;/code&gt;，LangGraph 会自动等所有被触发的分支跑完才进汇总节点——这就是免费的 join 屏障。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;原文如此&lt;/strong&gt;：&lt;code&gt;add_conditional_edges&lt;/code&gt; 的第三个参数（&lt;code&gt;[&quot;github&quot;, &quot;notion&quot;, &quot;slack&quot;]&lt;/code&gt;）是静态路径映射，通常是给&quot;返回节点名字符串&quot;的路由函数用的；这里路由函数返回的是 &lt;code&gt;Send&lt;/code&gt; 对象，目标节点已经写在 &lt;code&gt;Send&lt;/code&gt; 里了，所以这个列表对本次执行并不起决定作用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;§6 使用&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：喂一个问题进去，看看它拆成了几路、答案长什么样。&lt;strong&gt;为什么&lt;/strong&gt;：分类结果是最值得观测的一环——它决定了后面所有成本和质量的走向。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result = workflow.invoke({
    &quot;query&quot;: &quot;How do I authenticate API requests?&quot;
})

print(&quot;Original query:&quot;, result[&quot;query&quot;])
print(&quot;\nClassifications:&quot;)
for c in result[&quot;classifications&quot;]:
    print(f&quot;  {c[&apos;source&apos;]}: {c[&apos;query&apos;]}&quot;)
print(&quot;\nFinal Answer:&quot;)
print(result[&quot;final_answer&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原文给出的预期输出里，分类只有 github 和 notion 两路（Slack 被判定不相关），最终答案把 JWT、OAuth2、API Key 三种方式分条列出，并指向 PR #156、issue #203 等具体线索。这正好演示了 Router 的价值：&lt;strong&gt;不是三个源都查一遍，而是只查该查的&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;§7 架构理解：四个阶段&lt;/h3&gt;
&lt;p&gt;原文把这套图拆成四个阶段来理解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分类阶段（classification phase）&lt;/strong&gt;：&lt;code&gt;classify_query&lt;/code&gt; 用结构化输出分析问题，路由智能就在这里。用 Pydantic 模型约束输出，比解析自由格式 JSON 可靠得多，也让路由逻辑显式可读；不相关的源直接不出现，而不是返回一个空查询。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;并行执行（parallel execution with Send）&lt;/strong&gt;：&lt;code&gt;route_to_agents&lt;/code&gt; 把分类映射成 &lt;code&gt;Send&lt;/code&gt;。例如分类是 &lt;code&gt;[{&quot;source&quot;: &quot;github&quot;, ...}, {&quot;source&quot;: &quot;notion&quot;, ...}]&lt;/code&gt;，就变成 &lt;code&gt;[Send(&quot;github&quot;, {&quot;query&quot;: &quot;...&quot;}), Send(&quot;notion&quot;, {&quot;query&quot;: &quot;...&quot;})]&lt;/code&gt;，两个 Agent 同时跑，各自只收到自己需要的那点状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果收集（result collection with reducers）&lt;/strong&gt;：每个 Agent 返回 &lt;code&gt;{&quot;results&quot;: [{&quot;source&quot;: &quot;github&quot;, &quot;result&quot;: &quot;...&quot;}]}&lt;/code&gt;，归并函数（Python 里就是 &lt;code&gt;operator.add&lt;/code&gt;）把这些 list 拼起来，汇成 &lt;code&gt;state[&quot;results&quot;]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;汇总阶段（synthesis phase）&lt;/strong&gt;：等所有并行分支结束（LangGraph 自动处理），对着原始问题把多源信息合并成一份不重复的答案。原文明确说明：这个教程里所有被选中的 Agent &lt;strong&gt;必须全部完成&lt;/strong&gt;才会进入汇总，没有&quot;先到先汇总&quot;的部分结果机制。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;§8 完整示例&lt;/h3&gt;
&lt;p&gt;原文最后给了一份把上面所有片段拼在一起的完整脚本（imports + state + 7 个工具 + 3 个 Agent + 5 个节点 + 编译 + &lt;code&gt;__main__&lt;/code&gt; 演示），可以直接跑通演示流程。这里不整段复制，链接见下一节。&lt;/p&gt;
&lt;h3&gt;§9 进阶：有状态路由&lt;/h3&gt;
&lt;p&gt;到目前为止，这个 Router 是**无状态（stateless）**的：每次请求独立处理，两次调用之间没有记忆。多轮对话要记忆，原文给了两条路。&lt;/p&gt;
&lt;h4&gt;路线一：把 Router 包成一个工具（推荐）&lt;/h4&gt;
&lt;p&gt;思路：让 Router 继续无状态，记忆交给外面那个对话 Agent。对话 Agent 挂一个 &lt;code&gt;search_knowledge_base&lt;/code&gt; 工具，需要查资料时才调 Router。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langgraph.checkpoint.memory import InMemorySaver


@tool
def search_knowledge_base(query: str) -&amp;gt; str:
    &quot;&quot;&quot;跨多个知识源（GitHub / Notion / Slack）检索。

    需要查代码、文档或团队讨论时用它。
    &quot;&quot;&quot;
    result = workflow.invoke({&quot;query&quot;: query})
    return result[&quot;final_answer&quot;]


conversational_agent = create_agent(
    model,
    tools=[search_knowledge_base],
    system_prompt=(
        &quot;You are a helpful assistant that answers questions about our organization. &quot;
        &quot;Use the search_knowledge_base tool to find information across our code, &quot;
        &quot;documentation, and team discussions.&quot;
    ),
    # 中文大意：组织知识助手，需要找信息就调 search_knowledge_base
    checkpointer=InMemorySaver(),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;checkpointer&lt;/code&gt; + &lt;code&gt;thread_id&lt;/code&gt; 负责记住对话，Router 只被当成一件&quot;一次性的查询工具&quot;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;config = {&quot;configurable&quot;: {&quot;thread_id&quot;: &quot;user-123&quot;}}
result = conversational_agent.invoke(
    {&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;How do I authenticate API requests?&quot;}]},
    config,
)
print(result[&quot;messages&quot;][-1].content)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：原文推荐大多数场景用这条路线。职责干净——Router 负责多源查询，对话 Agent 负责上下文与记忆。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;路线二：完整持久化&lt;/h4&gt;
&lt;p&gt;如果你需要 &lt;strong&gt;Router 自己记住状态&lt;/strong&gt;（比如&quot;上一轮查过什么&quot;要影响这一轮的路由决策），就给 Router 这一层加持久化，把消息历史存在图里。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;注意&lt;/strong&gt;：有状态路由会显著增加复杂度。跨轮路由到不同 Agent 时，因为各自提示词和语气不同，用户会觉得对话&quot;人格不一致&quot;。原文建议：如果真需要多轮里切换不同 Agent，优先考虑交接（handoffs）模式或子 Agent（subagents）模式，它们对这种场景的语义更清楚。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;关键机制专讲&lt;/h2&gt;
&lt;h3&gt;① 分类为什么值得单独一次模型调用？规则或 embedding 行不行？&lt;/h3&gt;
&lt;p&gt;值得，但不必永远如此。这次调用买的是三样东西：&lt;strong&gt;该查哪几个源&lt;/strong&gt;、&lt;strong&gt;每个源该问什么&lt;/strong&gt;、&lt;strong&gt;不该查谁&lt;/strong&gt;。第三条最容易被忽略——&quot;不查 Slack&quot;本身就是省钱省延迟的决策，规则很难写全。&lt;/p&gt;
&lt;p&gt;能不能替代？分情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;规则/关键词&lt;/strong&gt;：如果你的三个源边界非常硬（比如按产品线切分，问题里出现&quot;账单&quot;就只查账单库），一条 &lt;code&gt;if/elif&lt;/code&gt; 比模型调用更稳、更快、更便宜。&lt;strong&gt;能用规则就别用模型&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedding 相似度&lt;/strong&gt;：适合&quot;源很多、每个源有自己的描述语料&quot;的场景——把用户问题分别和三个源的画像做相似度，超过阈值就入选。它比规则泛化好，但没法生成&quot;定制子问题&quot;，只能决定&quot;查不查&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型分类&lt;/strong&gt;：当源之间语义重叠（&quot;认证&quot;既可能在代码里也可能在文档里）、还需要把问题改写成面向该源的子问题时，模型才是划算的。注意用结构化输出 + 枚举约束，别让它自由发挥。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;② &lt;code&gt;Send&lt;/code&gt; 怎么做到分发到&quot;数量不定&quot;的节点？&lt;/h3&gt;
&lt;p&gt;普通条件边只能回答&quot;下一步走哪一个分支&quot;，分支集合必须在编译期就写死在图里。&lt;code&gt;Send&lt;/code&gt; 换了个思路：&lt;strong&gt;路由函数返回的不是&quot;下一站&quot;，而是一份&quot;投递单列表&quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;return [Send(c[&quot;source&quot;], {&quot;query&quot;: c[&quot;query&quot;]}) for c in state[&quot;classifications&quot;]]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LangGraph 拿到这个 list 后，为每个 &lt;code&gt;Send&lt;/code&gt; 起一个独立的节点执行（同一个节点可以被投递多次），每个执行体拿到的是 &lt;code&gt;Send&lt;/code&gt; 里那份&lt;strong&gt;独立的状态&lt;/strong&gt;，而不是主状态。所以：3 条分类就并发 3 路，0 条分类就一路都不发（图直接走到 &lt;code&gt;synthesize&lt;/code&gt;，由空结果兜底），运行时决定的&quot;数量不定&quot;就实现了。拓扑上你还是只画了 3 个 Agent 节点 + 1 条条件边。&lt;/p&gt;
&lt;h3&gt;③ 为什么必须有归并函数（reducer）？&lt;/h3&gt;
&lt;p&gt;因为并行分支会&lt;strong&gt;同时&lt;/strong&gt;往同一个字段写。默认语义下，LangGraph 认为同一个 key 在一个 super-step 里被写多次是冲突，会直接报错——它没法猜你是想覆盖、想取最后一个，还是想拼接。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Annotated[list[AgentOutput], operator.add]&lt;/code&gt; 就是一次显式声明：&lt;strong&gt;这个字段的更新方式是&quot;把新 list 接到旧 list 后面&quot;&lt;/strong&gt;。于是每个 Agent 只管返回自己那一份 &lt;code&gt;{&quot;results&quot;: [...]}&lt;/code&gt;，归并交给声明去做，最后 &lt;code&gt;synthesize&lt;/code&gt; 拿到的是完整的列表。&lt;/p&gt;
&lt;p&gt;一句话记忆：&lt;strong&gt;并行写入的字段，必须能回答&quot;两个值怎么合&quot;&lt;/strong&gt;。同理，如果你的并行节点都往 &lt;code&gt;messages&lt;/code&gt; 写，就要用消息专用的归并方式，而不是默认覆盖。&lt;/p&gt;
&lt;h3&gt;④ 和子 Agent（subagents）模式差在哪？&lt;/h3&gt;
&lt;p&gt;两种模式都能&quot;派活给多个 Agent&quot;，区别在&lt;strong&gt;谁做决定、在哪做决定&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;子 Agent 模式&lt;/strong&gt;：主管 Agent 把子 Agent 当工具，模型在 ReAct 循环里一步一步决定&quot;现在调谁、拿着上一轮结果再调谁&quot;。控制权在 LLM 手里，灵活，但调用是&lt;strong&gt;一轮一轮串起来&lt;/strong&gt;的，主管还要额外一轮把结果收回来再决定下一步。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;路由模式&lt;/strong&gt;：分诊是一个&lt;strong&gt;独立的前置阶段&lt;/strong&gt;，一次性规划完&quot;要查哪几路、每路问什么&quot;，然后并行发射，最后统一汇总。控制权在开发者手里（你可以换提示词、加规则、改 schema），并行是图调度器保证的，不依赖模型自觉。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：选择标准是——需要&lt;strong&gt;专门的预处理、自定义路由逻辑、明确的并行控制&lt;/strong&gt;时用 Router；想让 &lt;strong&gt;LLM 动态决定&lt;/strong&gt;调谁时用子 Agent 模式。前者的代价是每次请求都多一次路由模型调用（Router 无状态，第二轮对话还得再分诊一次）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;⑤ 有状态路由怎么选？&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;需求&lt;/th&gt;
&lt;th&gt;选法&lt;/th&gt;
&lt;th&gt;代价&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;单轮问答、多源检索&lt;/td&gt;
&lt;td&gt;无状态 Router（本教程）&lt;/td&gt;
&lt;td&gt;无记忆，每轮都要重新分诊&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;多轮对话 + 需要查资料&lt;/td&gt;
&lt;td&gt;Router 包成工具 + 带 checkpointer 的对话 Agent（推荐）&lt;/td&gt;
&lt;td&gt;多一层对话 Agent 的调用开销&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;路由决策本身要用到历史检索结果&lt;/td&gt;
&lt;td&gt;Router 自身持久化&lt;/td&gt;
&lt;td&gt;复杂度陡增，跨轮 Agent 切换体验不一致&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;判断口诀：&lt;strong&gt;记忆属于对话，不属于检索&lt;/strong&gt;。绝大多数场景把记忆放在对话 Agent 那一层就够了；只有当&quot;上一轮查到的内容&quot;真的会改变&quot;这一轮查哪几个源&quot;时，才值得给 Router 上持久化。&lt;/p&gt;
&lt;h2&gt;完整代码在哪&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;官方原文（含完整可运行脚本，即原文 §8 Complete working example）：https://docs.langchain.com/oss/python/langchain/multi-agent/router-knowledge-base&lt;/li&gt;
&lt;li&gt;完整示例见原文 &lt;strong&gt;Complete working example&lt;/strong&gt; 一节，它把 state 定义、7 个工具、3 个 Agent、5 个节点、编译与演示输出全部串在一起。&lt;/li&gt;
&lt;li&gt;想横向对比四种多 Agent 模式（subagents / handoffs / skills / router）的调用次数与 token 开销，看官方总览页：https://docs.langchain.com/oss/python/langchain/multi-agent&lt;/li&gt;
&lt;li&gt;安装只需 &lt;code&gt;pip install langchain langgraph&lt;/code&gt;（或 &lt;code&gt;uv add langchain langgraph&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本笔记只摘取骨架与关键片段，可能存在与原文的措辞差异；模型名、返回的假数据都以原文为准，未在本机实际运行验证。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Router 的本质是三段式&lt;/strong&gt;：分类（decompose）→ 并行路由（route）→ 汇总（synthesize）。三段的职责不要混在一起写。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次分诊换来三样东西&lt;/strong&gt;：只查该查的源、给每个源定制子问题、让每个专科 Agent 的提示词和工具保持专精。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;并行靠 &lt;code&gt;Send&lt;/code&gt;，合并靠 reducer&lt;/strong&gt;。&lt;code&gt;Send&lt;/code&gt; 让&quot;跑几路&quot;由运行时决定；&lt;code&gt;Annotated[list[...], operator.add]&lt;/code&gt; 让并行结果能安全地写回同一个字段。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结构化输出是路由可靠性的保险&lt;/strong&gt;：用 Pydantic 模型约束分类结果，比解析自由格式 JSON 稳得多，也让路由逻辑显式可读。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无状态是好默认值&lt;/strong&gt;：需要多轮记忆时，优先把 Router 包成一个工具交给带 &lt;code&gt;checkpointer&lt;/code&gt; 的对话 Agent，而不是一上来就给 Router 加持久化。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;用 Router&lt;/strong&gt;：你有多个相互独立、边界清晰的知识源（代码库 / 文档库 / 聊天记录 / 各业务线数据库），问题经常需要跨源取证，你希望并行以压低延迟，并且想让路由逻辑显式可控、可观测、可写测试。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;别用 Router&lt;/strong&gt;：只有一个知识源，或者工具总数不到 5 个——单 Agent 挂工具更简单；问题只需要问一个源、不需要汇总——用一个 &lt;code&gt;Command&lt;/code&gt; 直接单路跳转就够；多轮里需要不同 Agent 轮流&quot;接管&quot;对话——用交接（handoffs）模式；想让 LLM 自己动态决定调谁、步步为营——用子 Agent（subagents）模式；能用 &lt;code&gt;if/elif&lt;/code&gt; 或一张映射表确定性地分流——那就写 Python，别调模型。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：这就是原文收尾给的选型口径——&lt;strong&gt;多个互相独立的知识源 + 想要低延迟的并行查询 + 想显式掌控路由逻辑&lt;/strong&gt;，才上 Router；更简单的动态工具选择用子 Agent 模式，需要 Agent 与用户按顺序对话用交接（handoffs）模式。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>11 实战：用 Skills 搭一个 SQL 助手（按需加载「查表手册」）</title><link>https://aicooleo.cn/blog/posts/multiagent-11-case-skills/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-11-case-skills/</guid><description>技能（Skill）就是一本放在书架上的说明书。Agent 脑子里只贴一张目录——&quot;销售分析手册&quot;&quot;库存管理手册&quot;，真要用哪本，再伸手抽出来翻；而不是一上来就把几十本手册全糊它脸上。</description><pubDate>Sat, 29 Aug 2026 20:22:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：技能（Skill）就是一本&lt;strong&gt;放在书架上的说明书&lt;/strong&gt;。Agent 脑子里只贴一张目录——&quot;销售分析手册&quot;&quot;库存管理手册&quot;，真要用哪本，再伸手抽出来翻；而不是一上来就把几十本手册全糊它脸上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;这个教程要解决什么&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：给一家大企业做一个&quot;帮你写 SQL&quot;的助手。这家公司每个业务线（vertical）一套库，或者干脆一个巨型库塞了几千张表。&lt;strong&gt;朴素做法&lt;/strong&gt;：把所有表结构（schema）都写进系统提示词，让模型&quot;看着办&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么不行&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;贵&lt;/strong&gt;：提示词每轮都要重发一遍，token（词元）按次计费，几千张表 = 每轮都在烧钱。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;慢&lt;/strong&gt;：输入越长，首字延迟越高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;吵&lt;/strong&gt;：眼前摆着两百张不相关的表，模型反而更容易选错表、编错字段。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协同烂&lt;/strong&gt;：所有业务线的表结构挤在同一个提示词里，谁改谁背锅，最后变成没人敢动的祖传字符串。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;：只加载&lt;strong&gt;当前这个问题真正需要的那 1～2 个技能&lt;/strong&gt;，其余一律不进上下文。这就是原文反复讲的&lt;strong&gt;渐进式披露（progressive disclosure）&lt;/strong&gt;。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;全量塞进系统提示&lt;/th&gt;
&lt;th&gt;渐进式披露（本教程）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;每轮 token&lt;/td&gt;
&lt;td&gt;所有表结构之和&lt;/td&gt;
&lt;td&gt;技能目录 + 命中的那一个技能&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;新增一条业务线&lt;/td&gt;
&lt;td&gt;改核心提示词，风险外溢&lt;/td&gt;
&lt;td&gt;加一个技能，互不影响&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;谁维护&lt;/td&gt;
&lt;td&gt;只有写提示词的人&lt;/td&gt;
&lt;td&gt;各业务线自己维护&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;代价&lt;/td&gt;
&lt;td&gt;——&lt;/td&gt;
&lt;td&gt;首次命中要多一次工具调用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 官方教程：&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/sql-agent&quot;&gt;SQL Agent tutorial（带查询执行、报错纠正、结果校验的完整 SQL Agent）&lt;/a&gt; —— 本教程只讲&quot;渐进式披露&quot;这一个模式，它能套到任何领域。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;整体架构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    Start([用户：帮我写 SQL&amp;lt;br/&amp;gt;查高价值客户]) --&amp;gt; SystemPrompt[Agent 看到技能目录 Skill Catalog：&amp;lt;br/&amp;gt;sales_analytics / inventory_management]
    SystemPrompt --&amp;gt; Decide{需要销售表结构吗}
    Decide --&amp;gt; LoadSkill[调工具 Tool：load_skill&amp;lt;br/&amp;gt;skill_name = sales_analytics]
    LoadSkill --&amp;gt; Schema[表结构 Schema 载入上下文：&amp;lt;br/&amp;gt;customers、orders + 业务规则]
    Schema --&amp;gt; WriteQuery[Agent 依据手册写 SQL]
    WriteQuery --&amp;gt; Response([返回符合业务规则的 SQL])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;① 常驻上下文的只有&lt;strong&gt;技能目录&lt;/strong&gt;（名字 + 一句简介），成本极低，作用相当于书架上的索引卡。
② &lt;strong&gt;要不要加载、加载哪一个，是模型自己决定的&lt;/strong&gt;——它发起一次工具调用（tool call），不是你在代码里写 if/else 去挑。
③ 技能全文是通过&lt;strong&gt;工具返回值&lt;/strong&gt;流回对话的（在 LangChain 里就是一条 &lt;code&gt;ToolMessage&lt;/code&gt;）。从那一刻起，这轮对话就&quot;读过这本书&quot;了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;渐进式披露的 token 账&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;第 1 轮：系统提示 + 技能目录（200 token）        ─▶ 模型决定加载哪个技能
第 2 轮：+ 被加载技能全文（+2000 token）          ─▶ 模型据手册写 SQL
未加载的技能：0 token（这就是省钱的地方）
如果 200 个技能全塞进去：2000 × 200 = 40 万 token  ─▶ 直接爆窗口
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原文还顺手列了三个好处：团队各写各的技能（自治，跟其他多 Agent 架构一样）；技能加到几十上百个也不会撑爆上下文；&lt;strong&gt;只有一个 Agent、一条对话线程&lt;/strong&gt;，历史简单，不用做跨 Agent 的消息转发。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：Anthropic 把这套做法总结成三层结构——&lt;strong&gt;元数据 → 核心内容 → 详细资源&lt;/strong&gt;，逐层按需下钻。原文认为，技能式的渐进式披露可以看成一种 &lt;strong&gt;RAG（Retrieval-Augmented Generation，检索增强生成）&lt;/strong&gt;：每个技能就是一个检索单元，只不过检索不是靠向量或关键词，而是靠&quot;模型自己调工具去翻&quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;分步拆解&lt;/h2&gt;
&lt;h3&gt;§0 环境准备（原文 Setup）&lt;/h3&gt;
&lt;p&gt;安装：&lt;code&gt;pip install langchain&lt;/code&gt;（uv 用户 &lt;code&gt;uv add langchain&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LangSmith 追踪&lt;/strong&gt;：强烈建议开。技能是&quot;偷偷往上下文里塞内容&quot;的机制，不上 trace，你根本不知道它到底加载了哪个技能、加载了几次。设两个环境变量 &lt;code&gt;LANGSMITH_TRACING=&quot;true&quot;&lt;/code&gt;、&lt;code&gt;LANGSMITH_API_KEY=&quot;...&quot;&lt;/code&gt; 即可（或在 Python 里写 &lt;code&gt;os.environ[...]&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;选模型&lt;/strong&gt;：统一用 &lt;code&gt;init_chat_model&lt;/code&gt;，换供应商只改一个字符串（API Key 同样走环境变量，如 &lt;code&gt;os.environ[&quot;OPENAI_API_KEY&quot;]&lt;/code&gt;）。原文给了 7 个供应商的 Tab，这里压成一张表；每个 Tab 里还并列了&quot;直接实例化 Model Class&quot;的写法（如 &lt;code&gt;ChatOpenAI(model=&quot;gpt-5.5&quot;)&lt;/code&gt;），效果等价，不再展开：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;供应商&lt;/th&gt;
&lt;th&gt;安装&lt;/th&gt;
&lt;th&gt;init_chat_model 写法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install -U &quot;langchain[openai]&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;gpt-5.5&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;langchain[anthropic]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;claude-sonnet-4-6&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;langchain[openai]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;azure_openai:gpt-5.5&quot;, azure_deployment=...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemini&lt;/td&gt;
&lt;td&gt;&lt;code&gt;langchain[google-genai]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;google_genai:gemini-3.7-flash&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Bedrock&lt;/td&gt;
&lt;td&gt;&lt;code&gt;langchain[aws]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;us.anthropic.claude-sonnet-4-6&quot;, model_provider=&quot;bedrock_converse&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HuggingFace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;langchain[huggingface]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;microsoft/Phi-3-mini-4k-instruct&quot;, model_provider=&quot;huggingface&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenRouter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;langchain-openrouter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;auto&quot;, model_provider=&quot;openrouter&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;§1 定义技能：技能就是一个&quot;有名字的说明书&quot;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：先把技能的数据结构定下来。每个技能三件套：&lt;code&gt;name&lt;/code&gt;（唯一标识，模型靠它点名）、&lt;code&gt;description&lt;/code&gt;（1～2 句简介，常驻系统提示）、&lt;code&gt;content&lt;/code&gt;（全文，加载时才进上下文）。&lt;strong&gt;为什么这么分&lt;/strong&gt;：这个&quot;目录轻、正文重&quot;的切分就是整个模式的地基——便宜的信息常驻，贵的信息按需。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from typing import TypedDict

class Skill(TypedDict):
    &quot;&quot;&quot;一个可以按需披露给 agent 的技能。&quot;&quot;&quot;
    name: str         # 技能唯一标识，load_skill 靠它点名
    description: str  # 1-2 句简介，写进系统提示词给模型看
    content: str      # 技能全文（表结构 + 业务规则），按需加载
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后准备技能清单，本教程就两个：销售分析、库存管理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SKILLS: list[Skill] = [
    {
        &quot;name&quot;: &quot;sales_analytics&quot;,
        &quot;description&quot;: &quot;销售数据分析（客户、订单、营收）的表结构与业务逻辑。&quot;,
        &quot;content&quot;: &quot;&quot;&quot;# 销售分析表结构
### customers
- customer_id (PRIMARY KEY)
# ...（略：name / signup_date / status / customer_tier）
### orders
- order_id / customer_id / order_date
- status (pending/completed/cancelled/refunded)
- total_amount / sales_region
# ...（略：order_items 表、业务规则、示例 SQL）
&quot;&quot;&quot;,
    },
    {&quot;name&quot;: &quot;inventory_management&quot;, &quot;description&quot;: &quot;库存管理…（略）&quot;, &quot;content&quot;: &quot;# ...（略）&quot;},
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;附：技能正文里到底写什么（原文 Accordion）&lt;/h3&gt;
&lt;p&gt;原文两个技能的正文结构完全一致，都是三段：&lt;strong&gt;表结构 Tables → 业务规则 Business Logic → 示例查询 Example Query&lt;/strong&gt;。以 &lt;code&gt;sales_analytics&lt;/code&gt; 为例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## 表结构 Tables
customers:   customer_id / name / email / signup_date / status / customer_tier
orders:      order_id / customer_id / order_date / status / total_amount / sales_region
order_items: item_id / order_id / product_id / quantity / unit_price / discount_percent

## 业务规则 Business Logic
活跃客户   = status = &apos;active&apos; AND signup_date &amp;lt;= CURRENT_DATE - INTERVAL &apos;90 days&apos;
营收口径   = 只算 status = &apos;completed&apos; 的订单；total_amount 已含折扣
客户生命周期价值 CLV = 该客户所有已完成订单金额之和
高价值订单 = total_amount &amp;gt; 1000

## 示例查询 Example Query（一条&quot;上季度营收 Top 10 客户&quot;的可照抄 SQL）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这才是技能真正的价值&lt;/strong&gt;：表结构数据库里能查到（甚至能自动生成），&lt;strong&gt;但&quot;营收只算 completed&quot;&quot;total_amount 已经扣过折扣&quot;这类口径，只有人知道&lt;/strong&gt;。示例 SQL 则相当于给模型一个能照着改的答案格式。&lt;/p&gt;
&lt;h3&gt;§2 技能加载工具：&lt;code&gt;load_skill&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：给模型一个&quot;取书&quot;的工具——传技能名，返回技能全文。&lt;strong&gt;为什么&lt;/strong&gt;：模型读不到你的 Python 变量，它只有工具。工具返回值会被包成一条 &lt;code&gt;ToolMessage&lt;/code&gt; 塞进对话，于是&quot;手册内容&quot;就合法地进了上下文。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import tool

@tool
def load_skill(skill_name: str) -&amp;gt; str:
    &quot;&quot;&quot;把某个技能的完整内容加载进 agent 的上下文。

    当你需要处理某类请求的详细信息时使用。

    Args:
        skill_name: 要加载的技能名
    &quot;&quot;&quot;
    for skill in SKILLS:
        if skill[&quot;name&quot;] == skill_name:
            return f&quot;Loaded skill: {skill_name}\n\n{skill[&apos;content&apos;]}&quot;

    # 没找到：把可选技能列出来，让模型自己纠正
    available = &quot;, &quot;.join(s[&quot;name&quot;] for s in SKILLS)
    return f&quot;Skill &apos;{skill_name}&apos; not found. Available skills: {available}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ 原文如此：这个工具的 docstring 里举例写的是 &lt;code&gt;&quot;expense_reporting&quot;&lt;/code&gt;、&lt;code&gt;&quot;travel_booking&quot;&lt;/code&gt;，而本教程压根没有这两个技能——明显是从别的示例抄过来的。示例名和实际技能清单对不上，模型容易被带偏，自己写的时候记得改。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;小细节&lt;/strong&gt;：找不到技能时别直接报错崩掉，而是把&quot;可用技能清单&quot;回给模型——这是让 Agent 自我纠错（self-correction）的标准套路。&lt;/p&gt;
&lt;h3&gt;§3 技能中间件：把&quot;目录&quot;塞进系统提示&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：写一个自定义中间件（middleware），在每次调用模型之前，把技能目录追加到系统提示词后面。&lt;strong&gt;为什么不用改 &lt;code&gt;system_prompt&lt;/code&gt;&lt;/strong&gt;：技能列表是动态的，写死在 &lt;code&gt;create_agent(system_prompt=...)&lt;/code&gt; 里就没法随着运行时变化。而中间件能在&lt;strong&gt;每次模型调用前&lt;/strong&gt;改请求，这正是它该干的活。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from typing import Callable
from langchain.agents.middleware import ModelRequest, ModelResponse, AgentMiddleware
from langchain.messages import SystemMessage

class SkillMiddleware(AgentMiddleware):
    &quot;&quot;&quot;把技能目录注入系统提示词的中间件。&quot;&quot;&quot;

    tools = [load_skill]  # 类变量注册工具，agent 就能看到 load_skill

    def __init__(self):
        # 由 SKILLS 生成&quot;目录&quot;文本：- **技能名**: 一句话简介
        self.skills_prompt = &quot;\n&quot;.join(
            f&quot;- **{s[&apos;name&apos;]}**: {s[&apos;description&apos;]}&quot; for s in SKILLS
        )

    def wrap_model_call(
        self, request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse]
    ) -&amp;gt; ModelResponse:
        &quot;&quot;&quot;同步路径：把技能目录追加到系统提示词。&quot;&quot;&quot;
        addendum = (
            f&quot;\n\n## Available Skills\n\n{self.skills_prompt}\n\n&quot;
            &quot;当你需要某类请求的详细信息时，调用 load_skill 工具。&quot;
        )
        # 注意是&quot;追加内容块&quot;，不是覆盖原来的系统提示
        new_content = list(request.system_message.content_blocks) + [
            {&quot;type&quot;: &quot;text&quot;, &quot;text&quot;: addendum}
        ]
        modified = request.override(system_message=SystemMessage(content=new_content))
        return handler(modified)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：本教程图省事，在 &lt;code&gt;__init__&lt;/code&gt; 里读技能清单。生产环境更推荐放进 &lt;code&gt;before_agent&lt;/code&gt; 这类节点式钩子，这样新增/修改技能后能定期刷新，不用重启进程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;§4 组装 Agent&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：把模型、系统提示、中间件、checkpointer 拼成一个 Agent。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(
    model,
    system_prompt=(
        &quot;你是一个 SQL 查询助手，帮用户针对业务数据库写查询。&quot;
    ),
    middleware=[SkillMiddleware()],   # 关键：挂上技能中间件
    checkpointer=InMemorySaver(),     # 记住对话，多轮才有上下文
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;checkpointer&lt;/code&gt; 的作用：按 &lt;code&gt;thread_id&lt;/code&gt; 存消息历史，下一轮接着聊；它也是后面自定义状态（state）能存下来的前提。&lt;/p&gt;
&lt;h3&gt;§5 验证渐进式披露真的发生了&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：问一个&quot;必须懂销售库才答得对&quot;的问题，然后&lt;strong&gt;看消息序列&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain_core.utils.uuid import uuid7

config = {&quot;configurable&quot;: {&quot;thread_id&quot;: str(uuid7())}}
result = agent.invoke(
    {&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: (
        &quot;写一条 SQL，找出上个月下单金额超过 $1000 的所有客户&quot;
    )}]},
    config,
)

for message in result[&quot;messages&quot;]:
    message.pretty_print()   # 逐条打印：Human / AI(工具调用) / Tool / AI(答案)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;期望看到的关键片段（原文输出，已截断）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;================= Ai Message =================
Tool Calls:
  load_skill (call_abc123)   Args: skill_name: sales_analytics
================= Tool Message =================
Loaded skill: sales_analytics
...（技能全文：表结构 + 业务规则）...
================= Ai Message =================
SELECT DISTINCT c.customer_id, c.name, c.email, c.customer_tier
FROM customers c JOIN orders o ON c.customer_id = o.customer_id
WHERE o.total_amount &amp;gt; 1000
  AND o.status = &apos;completed&apos;                      -- 出自技能里的业务规则
  AND o.order_date &amp;gt;= CURRENT_DATE - INTERVAL &apos;1 month&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;怎么判断成功了&lt;/strong&gt;：模型先发 &lt;code&gt;load_skill&lt;/code&gt; 工具调用 → 拿到 ToolMessage → 才写 SQL；而且 SQL 里出现了&quot;只算 completed&quot;&quot;金额 &amp;gt; 1000&quot;这类&lt;strong&gt;它原本不可能知道的口径&lt;/strong&gt;——说明手册真被读进去了。&lt;/p&gt;
&lt;h3&gt;§6 进阶：用自定义状态（state）加硬约束&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;要解决什么&lt;/strong&gt;：第 5 步全靠模型自觉。万一它不加载技能就直接写 SQL 呢？那就用 state 把&quot;已经加载过哪些技能&quot;记下来，再在别的工具里&lt;strong&gt;硬拦&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from typing import NotRequired
from langchain.agents.middleware import AgentState

class CustomState(AgentState):
    skills_loaded: NotRequired[list[str]]   # 记录已加载的技能名
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;load_skill&lt;/code&gt; 改成返回 &lt;code&gt;Command&lt;/code&gt;，一次同时更新&lt;strong&gt;消息&lt;/strong&gt;和&lt;strong&gt;状态&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langgraph.types import Command
from langchain.tools import tool, ToolRuntime
from langchain.messages import ToolMessage

@tool
def load_skill(skill_name: str, runtime: ToolRuntime) -&amp;gt; Command:
    &quot;&quot;&quot;把某个技能的完整内容加载进 agent 的上下文。&quot;&quot;&quot;
    for skill in SKILLS:
        if skill[&quot;name&quot;] == skill_name:
            return Command(update={
                &quot;messages&quot;: [ToolMessage(
                    content=f&quot;Loaded skill: {skill_name}\n\n{skill[&apos;content&apos;]}&quot;,
                    tool_call_id=runtime.tool_call_id,
                )],
                &quot;skills_loaded&quot;: [skill_name],   # 记一笔账
            })
    # 没找到同样要回一条 ToolMessage，否则工具调用没有结果
    return Command(update={&quot;messages&quot;: [ToolMessage(
        content=f&quot;Skill &apos;{skill_name}&apos; not found.&quot;,
        tool_call_id=runtime.tool_call_id)]})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再写一个&quot;只有加载过技能才准用&quot;的工具：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@tool
def write_sql_query(query: str, vertical: str, runtime: ToolRuntime) -&amp;gt; str:
    &quot;&quot;&quot;为某个业务线写入并校验 SQL 查询。必须先加载对应技能。&quot;&quot;&quot;
    skills_loaded = runtime.state.get(&quot;skills_loaded&quot;, [])
    if vertical not in skills_loaded:
        return (
            f&quot;Error: 你必须先加载 &apos;{vertical}&apos; 技能，&quot;
            f&quot;才能了解表结构再写查询。请调用 load_skill(&apos;{vertical}&apos;)。&quot;
        )
    return f&quot;SQL Query for {vertical}:\n\n```sql\n{query}\n```\n\n已通过校验&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后把中间件指向自定义状态、把新工具挂上（&lt;code&gt;system_prompt&lt;/code&gt;、checkpointer 都不用动）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class SkillMiddleware(AgentMiddleware[CustomState]):
    state_schema = CustomState                  # 声明扩展后的状态
    tools = [load_skill, write_sql_query]       # 暴露两个工具
    # ...其余实现不变
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;效果&lt;/strong&gt;：模型抢跑时会收到一条错误，提示它先去加载技能——用&quot;错误信息&quot;当方向盘，把它掰回来。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ 原文如此：&lt;code&gt;write_sql_query&lt;/code&gt; 号称 &quot;Query validated against ... schema&quot;，但它其实&lt;strong&gt;只检查了技能有没有加载&lt;/strong&gt;，对 SQL 本身没做任何校验，返回的那句&quot;已通过校验&quot;只是拼出来的字符串。别被这行输出骗了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;如果约束是&quot;只读 + 限定表&quot;怎么办&lt;/strong&gt;（以下为笔记补充，非原文内容）：思路一样，把校验写死在工具里，不靠提示词。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import re
ALLOWED_TABLES = {&quot;customers&quot;, &quot;orders&quot;, &quot;order_items&quot;}   # 表白名单

def guard(query: str) -&amp;gt; str | None:
    &quot;&quot;&quot;返回错误文案 = 拦截；返回 None = 放行。&quot;&quot;&quot;
    if re.search(r&quot;\b(insert|update|delete|drop|alter|truncate)\b&quot;, query, re.I):
        return &quot;Error: 只允许只读查询（SELECT）。&quot;
    used = set(re.findall(r&quot;\b(?:from|join)\s+([A-Za-z_]\w*)&quot;, query, re.I))
    if used - ALLOWED_TABLES:
        return f&quot;Error: 不允许访问这些表：{sorted(used - ALLOWED_TABLES)}&quot;
    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;一句话&lt;/strong&gt;：提示词负责&quot;引导&quot;，state + 工具校验负责&quot;兜底&quot;。真正不能越界的事（写库、删表、跨业务线读数据）永远要放在代码里拦。&lt;/p&gt;
&lt;h3&gt;实现变体：存哪、怎么发现、怎么加载（原文 Accordion）&lt;/h3&gt;
&lt;p&gt;本教程用&quot;内存里的 Python 字典 + 工具直接查表&quot;，但每个环节都能换：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;环节&lt;/th&gt;
&lt;th&gt;可选做法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;存储&lt;/td&gt;
&lt;td&gt;内存（本教程，最快）；文件系统（Claude Code 的路子，技能 = 目录 + 文件，用 &lt;code&gt;read_file&lt;/code&gt; 读）；远端（S3 / 数据库 / Notion / 内部 API）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;发现（模型怎么知道有哪些技能）&lt;/td&gt;
&lt;td&gt;系统提示里列目录（本教程）；扫目录；查技能注册中心；用一个工具动态列出来&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;加载策略&lt;/td&gt;
&lt;td&gt;一次全量加载（本教程）；分页加载；技能内部再检索（grep 相关章节）；分层下钻（先概览再细节）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;技能多大才值得&quot;按需加载&quot;&lt;/strong&gt;（原文自称是&quot;未校准的心智模型，请按你的系统实测调&quot;）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;小技能&lt;/strong&gt;（低于 1K 词元，约 750 个英文词）：直接放系统提示，还能吃 &lt;strong&gt;prompt caching&lt;/strong&gt;，又快又省。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中等技能&lt;/strong&gt;（1K–10K 词元）：本教程的甜区，按需加载最划算。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大技能&lt;/strong&gt;（超过 10K 词元，或超过上下文窗口的 5%–10%）：必须上分页 / 检索 / 分层，否则一次加载就吃掉一大块窗口。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代价也很直白：内存最快，但改技能要重新部署；文件或远端存储能热更新，但多一次 I/O 和出错面。&lt;/p&gt;
&lt;h3&gt;渐进式披露与上下文工程（原文 Accordion）&lt;/h3&gt;
&lt;p&gt;渐进式披露本质上是**上下文工程（context engineering）**的一个技巧：管理&quot;什么时候、让 Agent 看到什么&quot;。&lt;/p&gt;
&lt;p&gt;还能再进一步：&lt;strong&gt;按需加载 few-shot 示例&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户问：&quot;找出 6 个月没下单的客户&quot;&lt;/li&gt;
&lt;li&gt;Agent 先加载 &lt;code&gt;sales_analytics&lt;/code&gt; 技能（表结构到手）&lt;/li&gt;
&lt;li&gt;再按语义或标签捞 2～3 条相关示例 SQL（找流失客户、按日期过滤、customers join orders）&lt;/li&gt;
&lt;li&gt;Agent 拿着&quot;表结构 + 可照抄的范式&quot;写查询&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&quot;按需加载表结构 + 动态挑示例&quot;这套组合，就是能撑住大规模知识库、又让输出接地气的上下文工程套路。&lt;/p&gt;
&lt;h2&gt;关键机制专讲&lt;/h2&gt;
&lt;h3&gt;① 技能到底是什么？——提示词，不是代码&lt;/h3&gt;
&lt;p&gt;技能（skill）是&lt;strong&gt;一段自成体系的专业说明&lt;/strong&gt;：讲清楚这个领域有哪些表、字段什么含义、业务口径怎么算、最好照着什么格式写。它主要靠提示词影响行为，也可以顺带说明&quot;该用哪个工具&quot;&quot;示例代码长什么样&quot;。&lt;/p&gt;
&lt;p&gt;所以：&lt;strong&gt;技能不是插件、不是函数、不是新的执行单元&lt;/strong&gt;。它就是&quot;写给模型看的内部文档&quot;。原文说得很直白：技能的价值在于把&lt;strong&gt;团队里的隐性知识&lt;/strong&gt;（口径、坑、惯例）固化成模型能读的文本。&lt;/p&gt;
&lt;h3&gt;② &lt;code&gt;load_skill&lt;/code&gt; + 中间件：内容是怎么进上下文的？&lt;/h3&gt;
&lt;p&gt;两个角色，分工明确：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;中间件（&lt;code&gt;SkillMiddleware&lt;/code&gt;）负责&quot;目录&quot;&lt;/strong&gt;：在 &lt;code&gt;wrap_model_call&lt;/code&gt; 里把技能名 + 简介追加到系统提示的内容块（&lt;code&gt;content_blocks&lt;/code&gt;），再用 &lt;code&gt;request.override(system_message=...)&lt;/code&gt; 把改过的请求交下去。注意是&lt;strong&gt;追加&lt;/strong&gt;，不是覆盖原来的系统提示。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具（&lt;code&gt;load_skill&lt;/code&gt;）负责&quot;正文&quot;&lt;/strong&gt;：模型点名要哪本书，工具把全文当返回值交出去，框架会把它包成一条 &lt;code&gt;ToolMessage&lt;/code&gt; 追加进消息列表——&lt;strong&gt;从这一轮起，正文就一直躺在对话历史里了&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个容易忽略的点：中间件里的 &lt;code&gt;tools = [load_skill]&lt;/code&gt; 是&lt;strong&gt;类变量&lt;/strong&gt;，靠它把工具注册给 Agent；进阶版还要补一句 &lt;code&gt;state_schema = CustomState&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;③ 渐进式披露为什么省 token？代价是什么？&lt;/h3&gt;
&lt;p&gt;省钱的逻辑就是&quot;&lt;strong&gt;不用的不付钱&lt;/strong&gt;&quot;：常驻的只有目录（几十到几百 token），正文只在命中时才进上下文。&lt;/p&gt;
&lt;p&gt;代价有三个，都得认：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多一轮模型调用&lt;/strong&gt;：先决定加载、再写答案，首次命中某个技能时延迟明显变高（原文明确列了这条 trade-off）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可能加载错、或加载了不用&lt;/strong&gt;：模型选错技能就白花 token；而且一旦加载，正文会一直留在历史里，不会自动卸载。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流程控制弱&lt;/strong&gt;：基础版只能靠提示词引导，没法硬性要求&quot;必须先 A 后 B&quot;——要硬约束就得像 §6 那样自己写 state 和校验逻辑。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;④ 用 state 加业务约束：怎么落到代码里？&lt;/h3&gt;
&lt;p&gt;三件套：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;扩状态&lt;/strong&gt;：&lt;code&gt;class CustomState(AgentState)&lt;/code&gt; 加一个 &lt;code&gt;skills_loaded&lt;/code&gt; 字段；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记状态&lt;/strong&gt;：&lt;code&gt;load_skill&lt;/code&gt; 返回 &lt;code&gt;Command(update={&quot;messages&quot;: [...], &quot;skills_loaded&quot;: [...]})&lt;/code&gt;，消息和状态一起更新；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查状态&lt;/strong&gt;：别的工具里读 &lt;code&gt;runtime.state.get(&quot;skills_loaded&quot;, [])&lt;/code&gt;，不满足就直接返回错误文案。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;要点：&lt;strong&gt;约束要写成&quot;模型读得懂的错误信息&quot;&lt;/strong&gt;，而不是抛异常中断。告诉它&quot;你没加载技能，请先调 &lt;code&gt;load_skill(&apos;sales_analytics&apos;)&lt;/code&gt;&quot;，模型下一轮就会自己去补——这比硬报错友好得多。至于只读、表白名单这类&lt;strong&gt;安全边界&lt;/strong&gt;，就用代码拦（见 §6 的 &lt;code&gt;guard&lt;/code&gt;）。&lt;/p&gt;
&lt;h3&gt;⑤ 为什么这里不用多开 Agent？（与 Subagents 的区别）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;技能 Skills&lt;/th&gt;
&lt;th&gt;子 Agent Subagents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;上下文&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;共享同一条对话历史&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;每个子 Agent 独立上下文，只把结论回传给主 Agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;切换成本&lt;/td&gt;
&lt;td&gt;一次工具调用，线程不变&lt;/td&gt;
&lt;td&gt;启动一个新 Agent，消息要序列化/转发&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适合&lt;/td&gt;
&lt;td&gt;知识、规则类的&quot;查手册&quot;&lt;/td&gt;
&lt;td&gt;高噪声、可隔离、需要独立工具集的子任务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;调试&lt;/td&gt;
&lt;td&gt;一条线程看到底&lt;/td&gt;
&lt;td&gt;跨 Agent 追踪，链路更长&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;判断标准很简单：&lt;strong&gt;你要的是&quot;多一点知识&quot;，还是&quot;多一个干活的人&quot;？&lt;/strong&gt; 写 SQL 缺的是口径和表结构——这是知识问题，读本书就够了，没必要为它开一个 Agent、多维护一套上下文。原文也把&quot;单 Agent、单对话线程，历史更简单&quot;列为渐进式披露的收益之一。&lt;/p&gt;
&lt;h2&gt;完整代码在哪&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;原文（含完整可运行脚本）：https://docs.langchain.com/oss/python/langchain/multi-agent/skills-sql-assistant&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完整示例见原文 Complete example&lt;/strong&gt;：那个折叠块里是把技能定义、&lt;code&gt;load_skill&lt;/code&gt;、&lt;code&gt;SkillMiddleware&lt;/code&gt;、&lt;code&gt;create_agent&lt;/code&gt;、示例调用拼在一起的整段脚本。运行前按原文提示装 &lt;code&gt;langchain langchain-openai langgraph&lt;/code&gt;、配好 API Key，并把模型初始化换成你用的供应商。&lt;/li&gt;
&lt;li&gt;本文代码块是&lt;strong&gt;骨架节选&lt;/strong&gt;（长函数做了删减，标了 &lt;code&gt;# ...（略）&lt;/code&gt;），目的是让你看清结构，不保证逐字复制就能跑；跑通以原文完整脚本为准。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;技能 = 说明书，不是代码&lt;/strong&gt;。&lt;code&gt;name + description&lt;/code&gt; 常驻系统提示当目录，&lt;code&gt;content&lt;/code&gt; 全文按需加载。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;load_skill&lt;/code&gt; 负责取正文，中间件负责贴目录&lt;/strong&gt;；正文以 &lt;code&gt;ToolMessage&lt;/code&gt; 形式进入对话历史，中间件用 &lt;code&gt;wrap_model_call&lt;/code&gt; 追加系统提示。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渐进式披露就是&quot;不用的不付钱&quot;&lt;/strong&gt;：省 token、让团队各写各的技能、还能架住上百个技能；代价是多一轮模型调用，且加载后不会自动卸载。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;想加硬约束就上 state&lt;/strong&gt;：&lt;code&gt;skills_loaded&lt;/code&gt; 记账 + 工具里校验；安全边界（只读、限定表）永远写在代码里，别写在提示词里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;这类问题是&quot;缺知识&quot;，不是&quot;缺人手&quot;&lt;/strong&gt;——所以单 Agent + 技能就够了，不必上子 Agent。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;该用 Skills&lt;/strong&gt;：知识、规则、口径类内容量大又不常全用；多团队各自维护自己的领域文档；技能会持续增加、需要热更新；你要的是&quot;同一张嘴，知道得更多&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;别用 Skills&lt;/strong&gt;：技能很小（低于 1K 词元）——直接塞系统提示 + prompt caching 更简单；任务要的是&lt;strong&gt;不同的工具集或完全隔离的上下文&lt;/strong&gt;——那该用子 Agent；需要&lt;strong&gt;严格顺序或人工审批&lt;/strong&gt;的流程——提示词管不住，得靠图的显式节点或 state 校验；内容其实是确定性映射（查表就能算出来）——那就别叫模型，直接写 Python。&lt;/p&gt;
</content:encoded></item><item><title>10 实战：用 Handoffs 搭一套客服工单流转（一个 Agent + 状态机式步骤）</title><link>https://aicooleo.cn/blog/posts/multiagent-10-case-handoffs/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-10-case-handoffs/</guid><description>这套东西就像一个客服按工单流程走——人始终是同一个客服（一个 Agent），手里拿着一本《话术手册》（步骤配置表），翻到哪一页（state[&quot;current_step&quot;]），就只说那一页的话、只用那一页的权限（提示词 + 工具集）；说完在…</description><pubDate>Sat, 29 Aug 2026 09:57:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：这套东西就像&lt;strong&gt;一个客服按工单流程走&lt;/strong&gt;——人始终是同一个客服（一个 Agent），手里拿着一本《话术手册》（步骤配置表），翻到哪一页（&lt;code&gt;state[&quot;current_step&quot;]&lt;/code&gt;），就只说那一页的话、只用那一页的权限（提示词 + 工具集）；说完在工单上盖个章（工具里 &lt;code&gt;Command&lt;/code&gt; 写回状态），下一轮自动翻到下一页。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;这个教程要解决什么&lt;/h2&gt;
&lt;p&gt;场景很具体：一个&lt;strong&gt;设备报修客服&lt;/strong&gt;。用户上来说「我手机屏幕碎了」，但流程不能乱——你得先确认在不在保修期，再判断是硬件还是软件问题，最后才能决定给方案还是转人工。&lt;/p&gt;
&lt;p&gt;原文要建的 Agent 干四件事：先把&lt;strong&gt;保修信息&lt;/strong&gt;收齐才允许往下走；把问题分类成&lt;strong&gt;硬件（hardware）/ 软件（software）&lt;/strong&gt;；给出解决方案或&lt;strong&gt;升级给人工客服&lt;/strong&gt;；在&lt;strong&gt;多轮对话&lt;/strong&gt;之间保持流程状态不丢。&lt;/p&gt;
&lt;p&gt;一句话目标：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;用&lt;strong&gt;一个 Agent + 一个 &lt;code&gt;current_step&lt;/code&gt; 状态字段&lt;/strong&gt;，实现一套有先后顺序、还能回头改的客服工单流程——而不是拉三个 Agent 互相调用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这套模式原文叫&lt;strong&gt;状态机模式（state machine pattern）&lt;/strong&gt;，也归在**交接（handoff）&lt;strong&gt;这一类做法里。它和另一篇的&lt;/strong&gt;子 Agent 模式（subagents pattern）**最大的区别：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Subagents（子 Agent）&lt;/th&gt;
&lt;th&gt;Handoffs / 状态机（本篇）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;形态&lt;/td&gt;
&lt;td&gt;多个独立 Agent，被当成工具调用&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;一个&lt;/strong&gt; Agent，配置随状态变&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;「换人」的本质&lt;/td&gt;
&lt;td&gt;真的换了执行者（另一个 Agent 实例）&lt;/td&gt;
&lt;td&gt;换的是同一 Agent 的&lt;strong&gt;提示词 + 工具集&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;状态放哪&lt;/td&gt;
&lt;td&gt;各子 Agent 各管各的&lt;/td&gt;
&lt;td&gt;统一放在共享 state 里&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适合什么&lt;/td&gt;
&lt;td&gt;需要集中调度、任务能并行拆&lt;/td&gt;
&lt;td&gt;流程有明确阶段、要连续对话&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;原文对步骤（step）的定义一句话就够：每个「步骤」只是同一个底层 Agent 的&lt;strong&gt;另一种配置&lt;/strong&gt;（系统提示词 + 工具），按状态动态选出来而已。&lt;/p&gt;
&lt;h2&gt;整体架构&lt;/h2&gt;
&lt;p&gt;原文给的是一张流程图（保修 → 问题类型 → 处理）。这里换成更贴合代码的&lt;strong&gt;状态迁移图&lt;/strong&gt;，因为代码里 &lt;code&gt;current_step&lt;/code&gt; 就是三个状态在跳：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;stateDiagram-v2
    state &quot;保修登记 Warranty Collector&quot; as WC
    state &quot;问题分类 Issue Classifier&quot; as IC
    state &quot;解决方案 Resolution Specialist&quot; as RS

    [*] --&amp;gt; WC : 首轮默认步骤（current_step 缺省值）
    WC --&amp;gt; IC : record_warranty_status() 写回 current_step
    IC --&amp;gt; RS : record_issue_type() 写回 current_step
    RS --&amp;gt; [*] : provide_solution() / escalate_to_human()

    RS --&amp;gt; WC : go_back_to_warranty() 纠正保修信息
    RS --&amp;gt; IC : go_back_to_classification() 纠正问题类型

    note right of RS
        software → provide_solution；hardware 在保 → provide_solution；hardware 过保 → escalate_to_human
    end note
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① 整条流程只有三个状态，装在一个叫 &lt;code&gt;current_step&lt;/code&gt; 的字段里，Agent 始终是同一个；② 状态不是靠「路由器节点」推的，而是&lt;strong&gt;工具自己写回&lt;/strong&gt;的——&lt;code&gt;record_warranty_status&lt;/code&gt; 调一次，&lt;code&gt;current_step&lt;/code&gt; 就从保修登记变成问题分类；③ 最后一步是&lt;strong&gt;分叉点&lt;/strong&gt;：软件给排障步骤、硬件在保给保修维修、硬件过保才转人工，这个判断由提示词 + 当时挂载的工具集共同完成；④ 还有两条&lt;strong&gt;回退边&lt;/strong&gt;，用户说「我搞错了」时，Agent 可以把你送回上一站重填。&lt;/p&gt;
&lt;p&gt;再补一张图，看清「状态变量怎么驱动行为切换」这条链路：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;用户消息 ─▶ 中间件读 state[&quot;current_step&quot;] ─▶ 注入该步骤的提示词+工具集 ─▶ 模型回答
                  ▲                                                        │
                  └──────── 工具调用写回 state（进入下一步骤）◀─────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键点：&lt;strong&gt;中间件（middleware）是每轮对话的「配置注入器」&lt;/strong&gt;，它自己不做业务判断；业务判断全在那几个返回 &lt;code&gt;Command&lt;/code&gt; 的工具里。这条分工线划得很干净，是整篇教程最值得抄的地方。&lt;/p&gt;
&lt;h2&gt;分步拆解&lt;/h2&gt;
&lt;h3&gt;§0 环境准备与选模型&lt;/h3&gt;
&lt;p&gt;安装一行搞定，原文还建议开 &lt;strong&gt;LangSmith&lt;/strong&gt; 追踪（可观测平台，用来看 Agent 内部到底跑了什么）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install langchain
export LANGSMITH_TRACING=&quot;true&quot;
export LANGSMITH_API_KEY=&quot;...&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;选模型部分，原文用 Tabs 列了 7 个 provider，核心都是同一个 &lt;code&gt;init_chat_model&lt;/code&gt; 入口。⚠️ 原文里的模型名（&lt;code&gt;gpt-5.5&lt;/code&gt;、&lt;code&gt;claude-sonnet-4-6&lt;/code&gt;、&lt;code&gt;gemini-3.7-flash&lt;/code&gt; 等）是文档占位写法，实际用时换成你手上真实可用的模型 ID。&lt;/p&gt;
&lt;h4&gt;OpenAI&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;pip install -U &quot;langchain[openai]&quot;&lt;/code&gt; → &lt;code&gt;init_chat_model(&quot;gpt-5.5&quot;)&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;Anthropic&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;pip install -U &quot;langchain[anthropic]&quot;&lt;/code&gt; → &lt;code&gt;init_chat_model(&quot;claude-sonnet-4-6&quot;)&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;Azure&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;pip install -U &quot;langchain[openai]&quot;&lt;/code&gt; → &lt;code&gt;init_chat_model(&quot;azure_openai:gpt-5.5&quot;, azure_deployment=...)&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;Google Gemini&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;pip install -U &quot;langchain[google-genai]&quot;&lt;/code&gt; → &lt;code&gt;init_chat_model(&quot;google_genai:gemini-3.7-flash&quot;)&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;AWS Bedrock&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;pip install -U &quot;langchain[aws]&quot;&lt;/code&gt; → &lt;code&gt;init_chat_model(&quot;us.anthropic.claude-sonnet-4-6&quot;, model_provider=&quot;bedrock_converse&quot;)&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;HuggingFace&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;pip install -U &quot;langchain[huggingface]&quot;&lt;/code&gt; → &lt;code&gt;init_chat_model(..., model_provider=&quot;huggingface&quot;)&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;OpenRouter&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;pip install -U langchain-openrouter&lt;/code&gt; → &lt;code&gt;init_chat_model(&quot;auto&quot;, model_provider=&quot;openrouter&quot;)&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;§1 自定义 state：先定义「有哪几步」&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：继承 &lt;code&gt;AgentState&lt;/code&gt;，在共享状态（state）上加三个字段——当前步骤、保修状态、问题类型。
&lt;strong&gt;为什么&lt;/strong&gt;：状态机模式的全部魔力都压在 &lt;code&gt;current_step&lt;/code&gt; 上，它决定这一轮加载哪套提示词和工具；另外两个字段既是提示词的填充变量，也是依赖校验的依据。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.agents import AgentState
from typing_extensions import NotRequired
from typing import Literal

# 工作流里允许出现的步骤名，写成 Literal，拼错立刻报错
SupportStep = Literal[&quot;warranty_collector&quot;, &quot;issue_classifier&quot;, &quot;resolution_specialist&quot;]


class SupportState(AgentState):
    &quot;&quot;&quot;客服工作流的状态。&quot;&quot;&quot;

    current_step: NotRequired[SupportStep]          # 核心：当前处于哪一步
    warranty_status: NotRequired[Literal[&quot;in_warranty&quot;, &quot;out_of_warranty&quot;]]
    issue_type: NotRequired[Literal[&quot;hardware&quot;, &quot;software&quot;]]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;NotRequired&lt;/code&gt; 是给 &lt;code&gt;TypedDict&lt;/code&gt; 用的：第一轮进来时这些字段本来就不存在，所以不能声明成必填。&lt;/p&gt;
&lt;h3&gt;§2 用工具管理流程状态&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：写几个工具（tool），让 Agent 既能「记录信息」，又能「推进步骤」。
&lt;strong&gt;为什么&lt;/strong&gt;：这是本篇最核心的一招——工具不返回字符串，而是返回 &lt;code&gt;Command(update={...})&lt;/code&gt;，一次性把&lt;strong&gt;业务数据&lt;/strong&gt;和 &lt;strong&gt;&lt;code&gt;current_step&lt;/code&gt;&lt;/strong&gt; 都写回 state。工具成了流程的唯一推进器。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import tool, ToolRuntime
from langchain.messages import ToolMessage
from langgraph.types import Command

@tool
def record_warranty_status(
    status: Literal[&quot;in_warranty&quot;, &quot;out_of_warranty&quot;],
    runtime: ToolRuntime[None, SupportState],
) -&amp;gt; Command:
    &quot;&quot;&quot;记录客户保修状态，并流转到问题分类步骤。&quot;&quot;&quot;
    return Command(
        update={
            # 必须自己补一条 ToolMessage，模型才看得到工具执行结果
            &quot;messages&quot;: [
                ToolMessage(
                    content=f&quot;Warranty status recorded as: {status}&quot;,
                    tool_call_id=runtime.tool_call_id,
                )
            ],
            &quot;warranty_status&quot;: status,
            &quot;current_step&quot;: &quot;issue_classifier&quot;,   # 状态迁移就发生在这一行
        }
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;record_issue_type&lt;/code&gt; 完全对称：写 &lt;code&gt;issue_type&lt;/code&gt;，把 &lt;code&gt;current_step&lt;/code&gt; 推到 &lt;code&gt;resolution_specialist&lt;/code&gt;。而&lt;strong&gt;只干活、不改流程&lt;/strong&gt;的工具（&lt;code&gt;provide_solution&lt;/code&gt;、&lt;code&gt;escalate_to_human&lt;/code&gt;）返回普通字符串就够了，比如 &lt;code&gt;return f&quot;Solution provided: {solution}&quot;&lt;/code&gt;。注意区分：&lt;strong&gt;改状态的工具用 &lt;code&gt;Command&lt;/code&gt;，只干活的工具用字符串&lt;/strong&gt;。别把所有工具都写成 &lt;code&gt;Command&lt;/code&gt;，那是过度设计。&lt;/p&gt;
&lt;h3&gt;§3 步骤配置表&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：给三个步骤各配一份「提示词 + 工具集 + 前置依赖」，塞进一个字典。
&lt;strong&gt;为什么&lt;/strong&gt;：一张表看全流程，加步骤就是加一条 entry，改流程不用碰中间件代码。提示词里还能写 &lt;code&gt;{warranty_status}&lt;/code&gt; 这种占位符，运行时由中间件用 state 填充。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WARRANTY_COLLECTOR_PROMPT = &quot;&quot;&quot;你是客服，负责处理设备问题。

当前步骤：核实保修状态（Warranty verification）

这一步你要做：先友好打招呼 → 询问设备是否在保修期内 → 调用 record_warranty_status
记录答案并进入下一步。语气自然，一次只问一个问题，不要连珠炮。&quot;&quot;&quot;

# 另两份提示词结构相同，只是「已知信息」和「要做什么」不同（完整版见原文）
ISSUE_CLASSIFIER_PROMPT = &quot;&quot;&quot;……
当前步骤：问题分类（Issue classification）
已知信息：保修状态 = {warranty_status}
……（让客户描述问题 → 判断硬件/软件 → 调 record_issue_type）&quot;&quot;&quot;

RESOLUTION_SPECIALIST_PROMPT = &quot;&quot;&quot;……
当前步骤：解决方案（Resolution）
已知信息：保修状态 = {warranty_status}，问题类型 = {issue_type}
……（软件给排障步骤；硬件在保给保修维修；硬件过保转人工）&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ 原文如此：折叠块里的提示词写的是 &lt;code&gt;CURRENT STAGE:&lt;/code&gt;，而文末完整示例里写的是 &lt;code&gt;CURRENT STEP:&lt;/code&gt;，两处不一致；正文叙述统一用「步骤 / step」。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;把「步骤名 → 配置」映射成字典，这就是&lt;strong&gt;状态机的转移表&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 步骤配置：步骤名 → (提示词, 工具集, 前置必需字段)
STEP_CONFIG = {
    &quot;warranty_collector&quot;: {
        &quot;prompt&quot;: WARRANTY_COLLECTOR_PROMPT,
        &quot;tools&quot;: [record_warranty_status],
        &quot;requires&quot;: [],
    },
    &quot;issue_classifier&quot;: {
        &quot;prompt&quot;: ISSUE_CLASSIFIER_PROMPT,
        &quot;tools&quot;: [record_issue_type],
        &quot;requires&quot;: [&quot;warranty_status&quot;],          # 没拿到保修状态不许进来
    },
    &quot;resolution_specialist&quot;: {
        &quot;prompt&quot;: RESOLUTION_SPECIALIST_PROMPT,
        &quot;tools&quot;: [provide_solution, escalate_to_human],
        &quot;requires&quot;: [&quot;warranty_status&quot;, &quot;issue_type&quot;],
    },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这张表的好处原文列了四条：一眼看全流程、加步骤只加一条、&lt;code&gt;requires&lt;/code&gt; 把依赖关系显式化、提示词能用 state 变量做模板。多补一条：&lt;strong&gt;&lt;code&gt;tools&lt;/code&gt; 字段同时是一道权限墙&lt;/strong&gt;——保修登记那一步的 Agent，物理上就调不到 &lt;code&gt;provide_solution&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;§4 步骤式中间件：把配置注入进去&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：写一个 &lt;code&gt;@wrap_model_call&lt;/code&gt; 装饰的中间件，每次模型调用前读 &lt;code&gt;current_step&lt;/code&gt;，把对应的提示词和工具&lt;strong&gt;覆盖&lt;/strong&gt;进去。
&lt;strong&gt;为什么&lt;/strong&gt;：不这么做，就得为每一步创建一个 Agent 实例；用了中间件，一个实例走完全程。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from typing import Callable


@wrap_model_call
def apply_step_config(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -&amp;gt; ModelResponse:
    &quot;&quot;&quot;按当前步骤配置 Agent 行为。&quot;&quot;&quot;
    # 取当前步骤；首轮没有该字段，默认从保修登记开始
    current_step = request.state.get(&quot;current_step&quot;, &quot;warranty_collector&quot;)
    step_config = STEP_CONFIG[current_step]          # 查表
    # 前置依赖校验：该有的数据没有就直接报错，别让模型瞎猜
    for key in step_config[&quot;requires&quot;]:
        if request.state.get(key) is None:
            raise ValueError(f&quot;{key} must be set before reaching {current_step}&quot;)
    # 用 state 里的值渲染提示词模板
    system_prompt = step_config[&quot;prompt&quot;].format(**request.state)
    # 覆盖系统提示词与可用工具集：不新建 Agent，只改这一次调用的配置
    request = request.override(
        system_prompt=system_prompt,
        tools=step_config[&quot;tools&quot;],
    )
    return handler(request)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;中间件五步走：&lt;strong&gt;读步骤 → 查配置 → 校验依赖 → 渲染提示词 → 覆盖配置&lt;/strong&gt;。其中 &lt;code&gt;request.override()&lt;/code&gt; 是钥匙——它让你不新建 Agent 实例就能改行为。&lt;/p&gt;
&lt;h3&gt;§5 创建 Agent&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：把 state schema、中间件、检查点（checkpointer）组装起来。
&lt;strong&gt;为什么&lt;/strong&gt;：中间件负责「切配置」，检查点负责「把状态跨轮存住」——少了检查点，用户第二句话进来 &lt;code&gt;current_step&lt;/code&gt; 就丢了，整个流程直接失忆。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver

# 所有步骤用到的工具汇总注册；注册 ≠ 可用，实际可见范围由中间件裁剪
all_tools = [
    record_warranty_status,
    record_issue_type,
    provide_solution,
    escalate_to_human,
]

agent = create_agent(
    model,
    tools=all_tools,
    state_schema=SupportState,     # 用自定义 state
    middleware=[apply_step_config],
    checkpointer=InMemorySaver(),  # 跨轮次保持状态
)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;为什么需要 checkpointer？&lt;/strong&gt; 检查点（checkpoint）负责把 state 跨对话轮次保住。没有它，&lt;code&gt;current_step&lt;/code&gt; 会在两条用户消息之间丢失，工作流直接断掉。生产环境换成 Postgres / Redis 的持久化 checkpointer 即可，接口不变。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;§6 测试整条流程&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：用同一个 &lt;code&gt;thread_id&lt;/code&gt; 连发四条消息，模拟真实的多轮报修。
&lt;strong&gt;为什么&lt;/strong&gt;：状态机的正确性只有跨轮才看得出来，单轮调用测不出来。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.messages import HumanMessage
from langchain_core.utils.uuid import uuid7

thread_id = str(uuid7())
config = {&quot;configurable&quot;: {&quot;thread_id&quot;: thread_id}}   # 同一个 thread 才算同一通对话

# 第 1 轮：客户只说症状，流程还没到分类那一步
result = agent.invoke({&quot;messages&quot;: [HumanMessage(&quot;Hi, my phone screen is cracked&quot;)]}, config)

# 第 2 轮：回答保修问题 → record_warranty_status 写回 current_step = issue_classifier
result = agent.invoke({&quot;messages&quot;: [HumanMessage(&quot;Yes, it&apos;s still under warranty&quot;)]}, config)
print(f&quot;Current step: {result.get(&apos;current_step&apos;)}&quot;)

# 第 3 轮：描述问题 → record_issue_type 写回 current_step = resolution_specialist
# ...（原文还有第 4 轮：用户问 What should I do?，Agent 给出保修维修方案）
for msg in result[&quot;messages&quot;]:
    msg.pretty_print()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;预期走向：&lt;strong&gt;问保修 → 问症状并判定为硬件 → 给出保修维修指引&lt;/strong&gt;。原文提醒：&lt;code&gt;result[&apos;messages&apos;]&lt;/code&gt; 是&lt;strong&gt;全量历史&lt;/strong&gt;，别以为只有本轮消息。&lt;/p&gt;
&lt;h3&gt;§7 状态迁移到底怎么发生&lt;/h3&gt;
&lt;p&gt;把三轮对话里 state 和中间件的对应关系摊开看——这段是理解整个模式的钥匙：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;轮次&lt;/th&gt;
&lt;th&gt;进入该轮时的 state&lt;/th&gt;
&lt;th&gt;中间件注入的配置&lt;/th&gt;
&lt;th&gt;本轮的工具调用&lt;/th&gt;
&lt;th&gt;轮次结束后的 state&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Turn 1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;current_step&lt;/code&gt; 缺省&lt;/td&gt;
&lt;td&gt;保修提示词 + &lt;code&gt;[record_warranty_status]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无（只打招呼、问保修）&lt;/td&gt;
&lt;td&gt;仍是 &lt;code&gt;warranty_collector&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn 2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;warranty_collector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保修提示词 + &lt;code&gt;[record_warranty_status]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;record_warranty_status(&quot;in_warranty&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;current_step=issue_classifier&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn 3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;issue_classifier&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;分类提示词（已填入保修状态）+ &lt;code&gt;[record_issue_type]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;record_issue_type(&quot;hardware&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;current_step=resolution_specialist&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn 4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;resolution_specialist&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;解决方案提示词（填入两个变量）+ &lt;code&gt;[provide_solution, escalate_to_human]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;provide_solution(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不变（已到终点）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;工具写回 state 长这样，就是一次普通的 &lt;code&gt;Command&lt;/code&gt; 更新：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 第 2 轮：record_warranty_status 的产物
Command(update={
    &quot;warranty_status&quot;: &quot;in_warranty&quot;,
    &quot;current_step&quot;: &quot;issue_classifier&quot;,       # 状态迁移！
})
# 第 3 轮：record_issue_type 的产物
Command(update={
    &quot;issue_type&quot;: &quot;hardware&quot;,
    &quot;current_step&quot;: &quot;resolution_specialist&quot;,  # 状态迁移！
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原文的结论：&lt;strong&gt;工具通过更新 &lt;code&gt;current_step&lt;/code&gt; 驱动流程，中间件在下一轮响应并套用对应配置。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;§8 消息历史管理&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：再挂一个摘要中间件，历史太长时压缩早期消息。
&lt;strong&gt;为什么&lt;/strong&gt;：流程一步步往前推，消息只增不减；再叠上系统提示词和工具定义，很快撑爆上下文（context）窗口，成本也跟着涨。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.agents.middleware import SummarizationMiddleware

# 在 §5 的 create_agent 上，middleware 里再加一个摘要中间件
agent = create_agent(
    model, tools=all_tools, state_schema=SupportState,
    middleware=[
        apply_step_config,                     # 先切步骤配置
        SummarizationMiddleware(               # 再做历史压缩：用便宜的小模型摘要
            model=&quot;gpt-5.4-mini&quot;,
            trigger=(&quot;tokens&quot;, 4000),          # 超过 4000 token 才触发
            keep=(&quot;messages&quot;, 10),             # 最近 10 条原样保留
        ),
    ],
    checkpointer=InMemorySaver(),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;顺序有讲究：&lt;code&gt;apply_step_config&lt;/code&gt; 放前面，保证每次模型调用拿到的都是当前步骤的配置；摘要中间件跟在后面做「瘦身」。&lt;/p&gt;
&lt;h3&gt;§9 加灵活性：退回上一步&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;干什么&lt;/strong&gt;：让用户能改口——「我搞错了，其实过保了」。
&lt;strong&gt;为什么&lt;/strong&gt;：真实业务一定有人填错。但原文提醒：&lt;strong&gt;不是所有迁移都能回退&lt;/strong&gt;，比如退款已打出去就没法回滚。这套模式适合「主链路顺序推进 + 偶尔回退纠错」，不适合到处乱跳。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@tool
def go_back_to_warranty() -&amp;gt; Command:
    &quot;&quot;&quot;回到保修核实步骤。&quot;&quot;&quot;
    return Command(update={&quot;current_step&quot;: &quot;warranty_collector&quot;})

@tool
def go_back_to_classification() -&amp;gt; Command:
    &quot;&quot;&quot;回到问题分类步骤。&quot;&quot;&quot;
    return Command(update={&quot;current_step&quot;: &quot;issue_classifier&quot;})

# 把这两个工具挂到解决方案这一步上
STEP_CONFIG[&quot;resolution_specialist&quot;][&quot;tools&quot;].extend([
    go_back_to_warranty,
    go_back_to_classification,
])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;工具有了还不够，提示词里得说清什么时候用（原文在解决方案提示词里追加了这几句）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;如果客户表示之前提供的信息有误，使用：
- go_back_to_warranty 纠正保修状态
- go_back_to_classification 纠正问题类型
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后这句用户输入就能触发回退，Agent 会调 &lt;code&gt;go_back_to_warranty&lt;/code&gt; 重新走保修核实：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result = agent.invoke(
    {&quot;messages&quot;: [HumanMessage(&quot;Actually, I made a mistake - my device is out of warranty&quot;)]},
    config,
)  # 触发回退
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：如果你的工作流需要在大多数步骤之间任意跳转，那就该先问自己——是不是根本不需要「结构化工作流」。这个模式的适用面是：&lt;strong&gt;步骤有清晰的先后顺序，只是偶尔为了纠错往回退一格。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;关键机制专讲&lt;/h2&gt;
&lt;h3&gt;① 为什么是「一个 Agent + 状态」，而不是多个 Agent&lt;/h3&gt;
&lt;p&gt;Subagents 模式是&lt;strong&gt;真的换执行者&lt;/strong&gt;：每个子 Agent 有自己的提示词、工具、上下文和消息历史，主 Agent 把它们当工具调。代价是状态被打散在多处，跨子 Agent 的信息传递要靠主 Agent 转述。
状态机模式反过来：&lt;strong&gt;执行者只有一个，换的是它的配置&lt;/strong&gt;。好处很直接——消息历史天然是&lt;strong&gt;一条连续的线&lt;/strong&gt;，客服对话里「他刚才说过什么」不会丢；流程状态放在&lt;strong&gt;一个 state 里&lt;/strong&gt;，可视、可校验、可持久化；中间件是唯一的切换点，加日志、加埋点、加权限校验都只改一处。&lt;/p&gt;
&lt;p&gt;代价也要说清：&lt;strong&gt;同一时刻只有一个「人格」在跑&lt;/strong&gt;，没法并行处理两条支线，也做不了「一边查物流一边查退款」。选型因此很简单——流程是&lt;strong&gt;一条线、有阶段&lt;/strong&gt;，用本篇；任务是&lt;strong&gt;能拆开并行&lt;/strong&gt;的，去看 Subagents。&lt;/p&gt;
&lt;h3&gt;② 步骤配置表怎么设计&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;STEP_CONFIG&lt;/code&gt; 那三个 key 就是设计的三条线：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;prompt&lt;/code&gt;（这一步说什么）&lt;/strong&gt;：用第二人称讲清「当前步骤 + 已知信息 + 这一轮要做什么」。原文三份提示词结构完全一致，都是「当前步骤 / 客户已知信息 / 这一步你要做」三段式，非常值得抄。已知信息用 &lt;code&gt;{warranty_status}&lt;/code&gt; 占位符注入，模型就不必从历史里翻。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;tools&lt;/code&gt;（这一步能用什么）&lt;/strong&gt;：这是&lt;strong&gt;最小权限&lt;/strong&gt;的落地方式。分类步骤只挂 &lt;code&gt;record_issue_type&lt;/code&gt;，它连 &lt;code&gt;escalate_to_human&lt;/code&gt;（人工介入 human-in-the-loop 的入口）都看不见，也就不会在第 2 轮就急着把客户转人工。工具集窄，模型跑偏的空间就小。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;requires&lt;/code&gt;（进来之前必须有什么）&lt;/strong&gt;：把流程的&lt;strong&gt;前置条件&lt;/strong&gt;从注释变成代码。缺字段直接 &lt;code&gt;raise ValueError&lt;/code&gt;，属于 fail-fast——早报错好过让模型拿着 &lt;code&gt;None&lt;/code&gt; 编一个保修状态出来。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个容易踩的坑：&lt;code&gt;create_agent(tools=all_tools)&lt;/code&gt; 先注册全量工具，中间件再按步骤收窄。&lt;strong&gt;注册 ≠ 可用&lt;/strong&gt;，真正决定模型能看见哪些工具的，是 &lt;code&gt;request.override(tools=...)&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;③ 状态迁移在真实对话里怎么发生&lt;/h3&gt;
&lt;p&gt;把原文的 Turn 1/2/3 串起来看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Turn 1&lt;/strong&gt;：用户只说「我手机屏碎了」。此时 state 里连 &lt;code&gt;current_step&lt;/code&gt; 都没有，中间件走默认值 &lt;code&gt;warranty_collector&lt;/code&gt;，加载保修提示词和 &lt;code&gt;record_warranty_status&lt;/code&gt;。Agent 该做的就是&lt;strong&gt;打个招呼、问在不在保修期&lt;/strong&gt;——哪怕用户已经把症状说了，也不许提前分类。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Turn 2&lt;/strong&gt;：用户回「还在保修期」。这一轮开头用的&lt;strong&gt;还是&lt;/strong&gt;保修那套配置（因为状态还没变），模型顺着提示词调 &lt;code&gt;record_warranty_status(&quot;in_warranty&quot;)&lt;/code&gt;，工具返回的 &lt;code&gt;Command&lt;/code&gt; 把 &lt;code&gt;current_step&lt;/code&gt; 改成 &lt;code&gt;issue_classifier&lt;/code&gt;。&lt;strong&gt;迁移发生在这一轮的工具调用里。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Turn 3&lt;/strong&gt;：新一轮进来，中间件读到 &lt;code&gt;current_step=&quot;issue_classifier&quot;&lt;/code&gt;，换成分类提示词（此时 &lt;code&gt;{warranty_status}&lt;/code&gt; 有值可填了），工具换成 &lt;code&gt;[record_issue_type]&lt;/code&gt;。用户说「是摔裂的」，模型判定 &lt;code&gt;hardware&lt;/code&gt; 并写回，&lt;code&gt;current_step&lt;/code&gt; 推到 &lt;code&gt;resolution_specialist&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Turn 4&lt;/strong&gt;：用户问「那我该怎么办」。这一轮挂的是 &lt;code&gt;[provide_solution, escalate_to_human]&lt;/code&gt; 加填好两个变量的解决方案提示词，于是输出「在保 → 保修维修流程」，而不是转人工。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原文讲的是「下一轮生效」。补一个实现细节：&lt;code&gt;@wrap_model_call&lt;/code&gt; 包的是&lt;strong&gt;每一次模型调用&lt;/strong&gt;，不是每一轮对话；而 Agent 内部是个 ReAct 循环——所以同一轮里工具写回状态之后，&lt;strong&gt;紧接着的那次模型调用就已经能拿到新配置&lt;/strong&gt;了，跨轮持久化则交给 checkpointer。理解这点，才明白提示词里为什么值得写「先调用工具，再回话」。&lt;/p&gt;
&lt;h3&gt;④ 消息历史为什么必须管理&lt;/h3&gt;
&lt;p&gt;四轮对话看着不长，但每轮都在往 &lt;code&gt;messages&lt;/code&gt; 里追加：用户消息、AI 消息、AI 的 tool_calls、&lt;code&gt;ToolMessage&lt;/code&gt;……四轮下来十几条轻轻松松。再加上每轮都要塞进去的&lt;strong&gt;系统提示词&lt;/strong&gt;和&lt;strong&gt;工具定义&lt;/strong&gt;（工具 schema 同样占 token），真实请求体比看上去大得多。&lt;/p&gt;
&lt;p&gt;用 &lt;code&gt;SummarizationMiddleware&lt;/code&gt; 是最省事的一招：&lt;code&gt;trigger=(&quot;tokens&quot;, 4000)&lt;/code&gt; 表示「超过 4000 token 才动手」，&lt;code&gt;keep=(&quot;messages&quot;, 10)&lt;/code&gt; 表示「最近 10 条原样留着，更早的压成摘要」。既控住长度，又保住客服对话最需要的「刚才聊到哪了」。
&lt;strong&gt;注意一个交互&lt;/strong&gt;：摘要会改写历史消息，而 &lt;code&gt;current_step&lt;/code&gt; 这类&lt;strong&gt;流程状态并不在 messages 里&lt;/strong&gt;，它躺在 state 的独立字段中——所以&lt;strong&gt;历史被压缩了，流程也不会失忆&lt;/strong&gt;。把状态从对话历史里拎出来单独存，本身就提高了鲁棒性。&lt;/p&gt;
&lt;h3&gt;⑤ 「退回上一步」这类灵活性怎么实现&lt;/h3&gt;
&lt;p&gt;实现简单到不像话——加两个工具，&lt;code&gt;Command(update={&quot;current_step&quot;: &quot;xxx&quot;})&lt;/code&gt; 就完了。真正的功夫在两处：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;提示词里要写清什么时候回退&lt;/strong&gt;。工具存在不等于模型会用，原文特意在解决方案提示词里补了一段「如果客户表示信息有误，使用 go_back_to...」。&lt;strong&gt;工具的能力边界，一半靠工具集，一半靠提示词。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;要有意识地划定「可回退 / 不可回退」&lt;/strong&gt;。原文明确说不是所有迁移都合理，比如退款已处理就不能回退。工程上可以在回退工具里加校验（比如查一下工单状态），把规则从提示词里的「请勿」升级成代码里的「不许」。
顺带一提：回退只改 &lt;code&gt;current_step&lt;/code&gt;，&lt;strong&gt;之前收集到的旧数据还留在 state 里&lt;/strong&gt;（比如 &lt;code&gt;issue_type&lt;/code&gt; 仍是 &lt;code&gt;hardware&lt;/code&gt;）。回退后别忘了让目标步骤负责刷新它——这是这套模式最容易漏的一个坑。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;完整代码在哪&lt;/h2&gt;
&lt;p&gt;本篇只挑骨架讲，&lt;strong&gt;完整示例见原文 Complete example&lt;/strong&gt;（含全部导入、三份完整提示词、&lt;code&gt;STEP_CONFIG&lt;/code&gt;、中间件、&lt;code&gt;create_agent&lt;/code&gt; 和四轮测试脚本，可对照运行）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 官方教程：&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/handoffs-customer-support&quot;&gt;Build customer support with handoffs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;原文 Next steps 里的配套阅读：&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/handoffs&quot;&gt;Handoffs / 状态机模式总览&lt;/a&gt;、&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/subagents-personal-assistant&quot;&gt;Subagents 模式（对照组）&lt;/a&gt;、&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/middleware&quot;&gt;Middleware 中间件&lt;/a&gt;、&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/short-term-memory&quot;&gt;短期记忆与摘要&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一个 Agent，多套配置&lt;/strong&gt;。本质是按 &lt;code&gt;current_step&lt;/code&gt; 动态切换系统提示词 + 工具集，切换点在 &lt;code&gt;@wrap_model_call&lt;/code&gt; 中间件，&lt;code&gt;request.override()&lt;/code&gt; 是关键 API。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具驱动流程&lt;/strong&gt;。用返回 &lt;code&gt;Command(update={...})&lt;/code&gt; 的工具同时写业务数据和 &lt;code&gt;current_step&lt;/code&gt;——流程推进交给模型「决定调哪个工具」，而不是写死在代码分支里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一张配置表定义状态机&lt;/strong&gt;。&lt;code&gt;STEP_CONFIG&lt;/code&gt; 把「说什么（prompt）/ 能用什么（tools）/ 需要什么（requires）」收敛到一处，加步骤等于加一条 entry。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;检查点不能省&lt;/strong&gt;。没有 checkpointer，跨轮的 &lt;code&gt;current_step&lt;/code&gt; 会丢；再叠一个 &lt;code&gt;SummarizationMiddleware&lt;/code&gt; 控制上下文长度，且因为流程状态存在 state 字段而非消息里，压缩历史不会丢流程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回退要有边界&lt;/strong&gt;。加 &lt;code&gt;go_back_to_*&lt;/code&gt; 很容易，难的是判断哪些步骤允许回退，以及回退后旧数据由谁负责刷新。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;该用&lt;/strong&gt;：流程有清晰的先后阶段、需要跨多轮连续对话、每步该说什么话和该有哪些权限能提前写清楚——比如客服工单、开户 / KYC 信息收集、下单引导、故障排查向导。这类场景用一个 Agent + 状态机，比拉一堆 Agent 互相调用简单得多。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;别用&lt;/strong&gt;：任务能拆成互不依赖的并行支线（用 Subagents 或直接并行调工具）；步骤之间需要大量任意跳转（说明你其实没有一个「流程」，写个普通的状态判断函数更清楚）；每一步需要的知识与工具差异极大、上下文互不干扰（那不如拆成独立 Agent，各自维护上下文）。&lt;/p&gt;
</content:encoded></item><item><title>09 实战：用 Subagents 搭一个个人助理（主管 + 日历/邮件子智能体）</title><link>https://aicooleo.cn/blog/posts/multiagent-09-case-subagents/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-09-case-subagents/</guid><description>主管 Agent 就像公司前台——他自己不会排日程、也不会写邮件，手里只有两张&quot;服务台工单&quot;（排日程、发邮件）；用户说一句人话，他判断这单该转给哪个专家，专家办完事只回一句&quot;办好了&quot;，前台再把两句话拼成一句答复你。</description><pubDate>Fri, 28 Aug 2026 23:04:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：主管 Agent 就像公司前台——他自己不会排日程、也不会写邮件，手里只有两张&quot;服务台工单&quot;（排日程、发邮件）；用户说一句人话，他判断这单该转给哪个专家，专家办完事只回一句&quot;办好了&quot;，前台再把两句话拼成一句答复你。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;这个教程要解决什么&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：做一个个人助理，既要管日历（查空闲、建会议），又要管邮件（写正文、发通知）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么不能只用一个 Agent&lt;/strong&gt;：如果给它直接挂上全部底层工具，会同时踩三个坑——工具太像导致选错；每个 API 的格式要求（ISO 时间、邮箱地址）全塞进一段 prompt 会变成一锅粥；想单独优化&quot;邮件措辞&quot;却要回归整个 Agent。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一句话目标&lt;/strong&gt;：按领域把工具切开做成两个专职子智能体（subagent），让一个主管（supervisor）Agent 只面对两个&quot;高层工具&quot;做调度——主管做的是&lt;strong&gt;领域级路由&lt;/strong&gt;，不是 API 级选参。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;读完你能得到&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一套可复用的三层架构：底层刚性 API 工具 → 中层子智能体 → 顶层主管；&lt;/li&gt;
&lt;li&gt;&quot;子智能体即工具&quot;的具体写法（含工具描述怎么写才好路由）；&lt;/li&gt;
&lt;li&gt;在子智能体这一层挂人工介入（human-in-the-loop）、由主管层负责暂停/恢复的完整 interrupt / resume 流程，以及信息流裁剪的两种做法和一个高频翻车点。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：原文提到，如果你是从旧的 &lt;code&gt;langgraph-supervisor&lt;/code&gt; 包迁移过来的，官方另有一篇 before/after 迁移文档（含 interrupt 与 resume）：&lt;a href=&quot;https://docs.langchain.com/oss/python/migrate/langgraph-supervisor&quot;&gt;Migrate from langgraph-supervisor&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;整体架构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    U[&quot;用户 User&quot;] --&amp;gt;|&quot;自然语言请求&quot;| S[&quot;主管 Supervisor&amp;lt;br/&amp;gt;只看到 2 个高层工具&quot;]
    S --&amp;gt;|&quot;schedule_event&quot;| W1[&quot;工具 Tool：schedule_event&quot;]
    S --&amp;gt;|&quot;manage_email&quot;| W2[&quot;工具 Tool：manage_email&quot;]
    W1 --&amp;gt; C[&quot;日历子智能体 Calendar Agent&amp;lt;br/&amp;gt;独立 prompt + 日历工具&quot;]
    W2 --&amp;gt; E[&quot;邮件子智能体 Email Agent&amp;lt;br/&amp;gt;独立 prompt + 邮件工具&quot;]
    C --&amp;gt; C1[&quot;create_calendar_event&amp;lt;br/&amp;gt;get_available_time_slots&quot;]
    E --&amp;gt; E1[&quot;send_email&quot;]
    C -.-&amp;gt;|&quot;只回传最终一条消息&quot;| S
    E -.-&amp;gt;|&quot;只回传最终一条消息&quot;| S
    C1 -.-&amp;gt;|&quot;interrupt 暂停待审&quot;| H{&quot;人工介入&amp;lt;br/&amp;gt;Human-in-the-loop&quot;}
    E1 -.-&amp;gt;|&quot;interrupt 暂停待审&quot;| H
    H -.-&amp;gt;|&quot;approve / edit / reject&quot;| C1
    H -.-&amp;gt;|&quot;approve / edit / reject&quot;| E1
    S --&amp;gt;|&quot;汇总后的回答&quot;| U
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;① &lt;strong&gt;两次&quot;工具&quot;调用，含义不同&lt;/strong&gt;。主管手里的 &lt;code&gt;schedule_event&lt;/code&gt; / &lt;code&gt;manage_email&lt;/code&gt; 是&quot;部门转接单&quot;，不是真实 API；点开它，里面才会跑一整个子智能体的循环（自己选工具、自己调、自己总结）。&lt;/p&gt;
&lt;p&gt;② &lt;strong&gt;层的边界就是信息的边界&lt;/strong&gt;。主管看不到子智能体内部的中间推理和中间工具调用，只拿到&lt;strong&gt;最后一条消息&lt;/strong&gt;；反过来，子智能体默认也只看得到主管转给它的那一个 &lt;code&gt;request&lt;/code&gt; 字符串——两边都是隔离的。&lt;/p&gt;
&lt;p&gt;③ &lt;strong&gt;越往上越&quot;软&quot;，越往下越&quot;硬&quot;&lt;/strong&gt;。底层 API 工具要严格格式；中层子智能体负责把人话翻译成格式；顶层主管只负责&quot;这事归谁 + 结果怎么合并&quot;。人工介入挂在最下面的敏感动作上，但状态与暂停能力由最顶层持有。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一次跨领域请求的时序&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;用户 ──&quot;下周二下午 2 点开会，并邮件提醒设计团队&quot;──▶ 主管 Agent（Supervisor）
                                                       │ 选择工具（本质是&quot;选专家&quot;）
                        ┌──────────────────────────────┴──────────────────────────────┐
                        ▼                                                             ▼
                  schedule_event                                               manage_email
                        ▼                                                             ▼
        日历子智能体（一次独立调用，无状态）                    邮件子智能体（一次独立调用，无状态）
                        │ 只回传最终确认文本                                          │ 只回传最终确认文本
                        └──────────────────────────────┬──────────────────────────────┘
                                                       ▼
                                              主管 Agent 汇总两段结果
用户 ◀───────────────&quot;会议已定 + 提醒邮件已发&quot;──────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;说明：本文只保留骨架代码，不保证逐行可跑；完整可运行脚本见原文。另外，代码里的&lt;strong&gt;提示词字符串为方便中文读者已译为中文&lt;/strong&gt;——如果你要直接复制去跑，建议回原文取英文原版提示词。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;分步拆解&lt;/h2&gt;
&lt;h3&gt;§0 前置准备：装包、追踪、选模型（原文 Setup）&lt;/h3&gt;
&lt;p&gt;安装 &lt;code&gt;pip install langchain&lt;/code&gt;（或 &lt;code&gt;conda install langchain -c conda-forge&lt;/code&gt;）。想做调用追踪就配上 LangSmith：设环境变量 &lt;code&gt;LANGSMITH_TRACING=&quot;true&quot;&lt;/code&gt; 与 &lt;code&gt;LANGSMITH_API_KEY&lt;/code&gt;（原文给了 shell 的 &lt;code&gt;export&lt;/code&gt; 和 Python 的 &lt;code&gt;os.environ[...]&lt;/code&gt; 两种写法）。下面需要一个聊天模型对象（下文统一叫 &lt;code&gt;model&lt;/code&gt;）；原文用 7 个 Tab 列举各家接入方式，套路都是&quot;装 extra + 用 &lt;code&gt;init_chat_model&lt;/code&gt; 或具体 Model 类&quot;，这里压成一张表（换提供方只改这一行，其余代码不动）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;提供方&lt;/th&gt;
&lt;th&gt;安装&lt;/th&gt;
&lt;th&gt;初始化&lt;/th&gt;
&lt;th&gt;需要的环境变量&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install -U &quot;langchain[openai]&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;gpt-5.5&quot;)&lt;/code&gt;（等价 &lt;code&gt;ChatOpenAI(model=&quot;gpt-5.5&quot;)&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OPENAI_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install -U &quot;langchain[anthropic]&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;claude-sonnet-4-6&quot;)&lt;/code&gt;（等价 &lt;code&gt;ChatAnthropic(...)&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install -U &quot;langchain[openai]&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;azure_openai:gpt-5.5&quot;, azure_deployment=...)&lt;/code&gt;（等价 &lt;code&gt;AzureChatOpenAI(...)&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AZURE_OPENAI_API_KEY&lt;/code&gt; / &lt;code&gt;AZURE_OPENAI_ENDPOINT&lt;/code&gt; / &lt;code&gt;OPENAI_API_VERSION&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemini&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install -U &quot;langchain[google-genai]&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;google_genai:gemini-3.7-flash&quot;)&lt;/code&gt;（等价 &lt;code&gt;ChatGoogleGenerativeAI(...)&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GOOGLE_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Bedrock&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install -U &quot;langchain[aws]&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;us.anthropic.claude-sonnet-4-6&quot;, model_provider=&quot;bedrock_converse&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;按 AWS 文档配置凭证&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HuggingFace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install -U &quot;langchain[huggingface]&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;microsoft/Phi-3-mini-4k-instruct&quot;, model_provider=&quot;huggingface&quot;, temperature=0.7, max_tokens=1024)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HUGGINGFACEHUB_API_TOKEN&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenRouter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install -U &quot;langchain-openrouter&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;init_chat_model(&quot;auto&quot;, model_provider=&quot;openrouter&quot;)&lt;/code&gt;（等价 &lt;code&gt;ChatOpenRouter(model=&quot;auto&quot;)&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OPENROUTER_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;§1 定义底层工具（Define tools）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;这一步干什么&lt;/strong&gt;：先写最底下那层&quot;刚性&quot;工具——参数要求严格、没有商量余地的 API 封装。原文用桩函数（stub）代替真实的 Google Calendar / SendGrid 调用，只为把模式讲清楚。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import tool

@tool
def create_calendar_event(title: str, start_time: str,   # ISO：&quot;2024-01-15T14:00:00&quot;
                          end_time: str,                 # ISO：&quot;2024-01-15T15:00:00&quot;
                          attendees: list[str],          # 邮箱地址列表
                          location: str = &quot;&quot;) -&amp;gt; str:
    &quot;&quot;&quot;创建日历事件。要求严格的 ISO 时间格式。&quot;&quot;&quot;
    # 桩函数：真实项目里这里调 Google Calendar API / Outlook API
    return f&quot;Event created: {title} from {start_time} to {end_time} with {len(attendees)} attendees&quot;

@tool
def send_email(to: list[str], subject: str, body: str, cc: list[str] = []) -&amp;gt; str:
    &quot;&quot;&quot;通过邮件 API 发送邮件。要求邮箱地址格式正确。&quot;&quot;&quot;
    return f&quot;Email sent to {&apos;, &apos;.join(to)} - Subject: {subject}&quot;   # 桩函数：真实项目里调 SendGrid

@tool
def get_available_time_slots(attendees: list[str], date: str,   # ISO 格式：&quot;2024-01-15&quot;
                             duration_minutes: int) -&amp;gt; list[str]:
    &quot;&quot;&quot;查询指定日期内这些参与者的日历空闲情况。&quot;&quot;&quot;
    return [&quot;09:00&quot;, &quot;14:00&quot;, &quot;16:00&quot;]   # 桩函数：真实项目里查日历 API
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：&lt;code&gt;@tool&lt;/code&gt; 的 docstring &lt;strong&gt;就是&lt;/strong&gt;给模型看的工具描述（description），不是普通注释——它是路由信号的一部分，要写清楚&quot;什么时候用、输入长什么样&quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;§2 创建两个子智能体（Create specialized sub-agents）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;这一步干什么&lt;/strong&gt;：每一步都造一个&quot;只懂一个领域&quot;的小 Agent。两个子智能体是同一套模板：一个 &lt;code&gt;model&lt;/code&gt; + 一组&lt;strong&gt;本领域专属&lt;/strong&gt;的工具 + 一段&lt;strong&gt;本领域专属&lt;/strong&gt;的 system prompt。&lt;/p&gt;
&lt;h4&gt;日历子智能体&lt;/h4&gt;
&lt;p&gt;它负责把&quot;下周二下午 2 点&quot;这种人话翻译成精确的 ISO 时间，需要时先查空闲再建事件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from datetime import date
from langchain.agents import create_agent

CALENDAR_AGENT_PROMPT = (
    f&quot;今天是 {date.today().isoformat()}。 &quot;          # 关键：把今天日期注入 prompt，&quot;下周二&quot;才有参照系
    &quot;你是一个日历排期助理。把自然语言的排期请求（例如&apos;下周二下午 2 点&apos;）解析成 ISO 时间格式。&quot;
    &quot;需要时用 get_available_time_slots 查询空闲；用 create_calendar_event 创建事件。&quot;
    &quot;如果没有合适的时间段，停下来，并在答复中说明该时间不可用。&quot;
    &quot;在最终答复里始终确认你安排了什么。&quot;
)

calendar_agent = create_agent(model, tools=[create_calendar_event, get_available_time_slots],
                              system_prompt=CALENDAR_AGENT_PROMPT)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;单独测一下它。这套 &lt;code&gt;stream_events&lt;/code&gt; + &lt;code&gt;interleave&lt;/code&gt; 循环后面每一节都用，&lt;code&gt;version=&quot;v3&quot;&lt;/code&gt; 是 LangChain v1 的流式接口：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;query = &quot;下周二下午 2 点安排一个 1 小时的团队会议&quot;
stream = calendar_agent.stream_events(
    {&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: query}]}, version=&quot;v3&quot;,
)
for kind, item in stream.interleave(&quot;messages&quot;, &quot;tool_calls&quot;):
    if kind == &quot;messages&quot;:
        print(item.text, end=&quot;&quot;, flush=True)              # 逐 token 输出
    else:
        print(f&quot;\n工具调用: {item.tool_name}({item.input})&quot;)
        print(f&quot;工具结果: {item.output}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一次典型运行：先调 &lt;code&gt;get_available_time_slots(attendees=[], date=..., duration_minutes=60)&lt;/code&gt;，拿到 &lt;code&gt;[&quot;09:00&quot;, &quot;14:00&quot;, &quot;16:00&quot;]&lt;/code&gt;；再调 &lt;code&gt;create_calendar_event&lt;/code&gt;，参数被填成 &lt;code&gt;2024-06-18T14:00:00 → 2024-06-18T15:00:00&lt;/code&gt;；最后回一句&quot;已安排在 6 月 18 日下午 2 点，时长 1 小时&quot;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ 原文如此：原文正文写&quot;把 next Tuesday at 2pm 解析成 ISO 格式（&lt;code&gt;2024-01-16T14:00:00&lt;/code&gt;）&quot;，但同一节的 trace 输出里实际是 &lt;code&gt;2024-06-18T14:00:00&lt;/code&gt;。两处对不上，这里按 trace 记录。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;邮件子智能体&lt;/h4&gt;
&lt;p&gt;它只干一件事：从模糊的自然语言里推断收件人，写一封像样的邮件，然后发出去。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EMAIL_AGENT_PROMPT = (
    &quot;你是一个邮件助理。根据自然语言请求撰写专业的邮件。&quot;
    &quot;提取收件人信息，并写出合适的标题和正文。&quot;
    &quot;用 send_email 发送邮件，并在最终答复里始终确认你发出了什么。&quot;
)

email_agent = create_agent(model, tools=[send_email], system_prompt=EMAIL_AGENT_PROMPT)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发给它的请求可以很随意，比如&quot;提醒设计团队评审一下新的设计稿&quot;。跑起来你能看到：它自己从&quot;设计团队&quot;推断出 &lt;code&gt;design-team@example.com&lt;/code&gt;，生成了标题 &lt;code&gt;Reminder: Please Review the New Mockups&lt;/code&gt; 和一段正式正文，调用 &lt;code&gt;send_email&lt;/code&gt;，最后回一句&quot;提醒已发出&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这一步的要点&lt;/strong&gt;：每个子智能体的&lt;strong&gt;关注面很窄&lt;/strong&gt;——自己的工具 + 自己的 prompt。窄才能专，专才好调优。&lt;/p&gt;
&lt;h3&gt;§3 把子智能体包装成工具（Wrap sub-agents as tools）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;这一步干什么&lt;/strong&gt;：这是整个模式最关键的动作——把&quot;跑一整个子智能体&quot;包装成一个普通 &lt;code&gt;@tool&lt;/code&gt; 函数。主管从此看到的是 &lt;code&gt;schedule_event&lt;/code&gt; 这种&lt;strong&gt;高层能力&lt;/strong&gt;，而不是 &lt;code&gt;create_calendar_event&lt;/code&gt; 这种&lt;strong&gt;底层 API&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@tool
def schedule_event(request: str) -&amp;gt; str:
    &quot;&quot;&quot;用自然语言安排日历事件。用户想创建、修改或查询日历安排时用它；内部负责时间
    解析、空闲检查和事件创建。输入：自然语言的日程请求（例如&quot;下周二下午 2 点开会&quot;）。
    &quot;&quot;&quot;
    result = calendar_agent.invoke({&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: request}]})
    return result[&quot;messages&quot;][-1].text   # 只把最终答复交回主管

@tool
def manage_email(request: str) -&amp;gt; str:
    &quot;&quot;&quot;用自然语言发送邮件。用户想发通知、提醒或任何邮件沟通时用它；内部负责提取收件
    人、生成标题和撰写正文。输入：自然语言的邮件请求（例如&quot;给他们发个会议提醒&quot;）。
    &quot;&quot;&quot;
    result = email_agent.invoke({&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: request}]})
    return result[&quot;messages&quot;][-1].text
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个细节，原文特意强调过：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;工具描述决定路由质量&lt;/strong&gt;。主管唯一能看到的&quot;菜单&quot;就是这两个函数的 name + docstring，所以&quot;什么时候用我&quot;必须写明白。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只回传最后一条消息&lt;/strong&gt;。子智能体的中间推理、中间工具调用，主管一概不需要看——这也是后面&quot;信息流裁剪&quot;的基础。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;§4 创建主管智能体（Create the supervisor agent）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;这一步干什么&lt;/strong&gt;：主管是个&quot;很薄&quot;的 Agent。它的工具列表里只有上一步那两个包装函数，system prompt 也只描述&quot;你能做什么、怎么拆解、怎么协调&quot;，完全不需要懂任何 API 格式。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SUPERVISOR_PROMPT = (
    &quot;你是一个乐于助人的个人助理。你可以安排日历事件，也可以发送邮件。&quot;
    &quot;把用户的请求拆解成合适的工具调用，并协调这些结果。&quot;
    &quot;当一个请求涉及多个动作时，按需顺序或并行地使用多个工具。&quot;
)

supervisor_agent = create_agent(model, tools=[schedule_event, manage_email],   # 只有两个&quot;高层工具&quot;
                                system_prompt=SUPERVISOR_PROMPT)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;§5 跑起来：主管怎么调度（Use the supervisor）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;query = (
    &quot;下周二下午 2 点和设计团队开 1 小时的会，&quot;
    &quot;并发封邮件提醒他们评审新的设计稿。&quot;
)

stream = supervisor_agent.stream_events(
    {&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: query}]}, version=&quot;v3&quot;,
)
# 遍历方式同 §2；这一层的 tool_calls 里出现的，就是那两个&quot;子智能体工具&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;例 1：单领域请求&lt;/h4&gt;
&lt;p&gt;&quot;明天上午 9 点安排一个团队站会&quot; → 主管判定这是日历任务，只调 &lt;code&gt;schedule_event&lt;/code&gt;；日历子智能体自己解析时间、建事件、回一句确认；主管把这句确认换个说法回给用户。&lt;strong&gt;邮件子智能体全程没被唤醒。&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;例 2：跨领域请求&lt;/h4&gt;
&lt;p&gt;上面那段 query 会得到这样的执行轨迹（节选）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;工具调用: schedule_event(request=&quot;下周二下午 2 点和设计团队开 1 小时的会&quot;)
工具调用: manage_email(request=&quot;发邮件提醒设计团队评审新的设计稿&quot;)   ← 同一条回复里发出
工具结果: 会议已安排在 6 月 18 日 14:00–15:00；提醒邮件已发给设计团队 ...
最终答复: 会议已定在下周二 6 月 18 日 14:00–15:00；同时也已发出评审提醒邮件。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主管在&lt;strong&gt;一条模型回复里同时发出了两个工具调用&lt;/strong&gt;，运行时并行执行；两个子智能体各自干活，最后主管把两段结果合并成一段连贯答复。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：想做全链路排查（每一次模型调用的完整 prompt 和 response），原文挂了对应的 LangSmith trace 链接，照着跑一遍最直观。另外原文有一条 Note：主管默认是&lt;strong&gt;顺序&lt;/strong&gt;派发的——一个工具调用跑完才轮到下一个；但很多模型会在单次回复里一次性发多个工具调用，运行时就会并行执行。也可以显式配置并行派发，见 &lt;a href=&quot;https://reference.langchain.com/python/langgraph-supervisor/supervisor/create_supervisor&quot;&gt;&lt;code&gt;create_supervisor&lt;/code&gt; 参考文档&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;三层架构在说什么&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;底层&lt;/strong&gt;：刚性 API 工具，格式要求死板，没有智能；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中层&lt;/strong&gt;：子智能体，吃自然语言、吐自然语言确认，中间翻译成结构化 API 调用；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;顶层&lt;/strong&gt;：主管，面向&quot;能力&quot;而不是&quot;接口&quot;，负责路由与结果合成。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;好处是三件事：每层职责单一；加新领域（比如 CRM、数据库）不影响已有领域；每层可以独立测试和迭代。&lt;/p&gt;
&lt;h3&gt;§6 加人工介入（Human-in-the-loop review）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;这一步干什么&lt;/strong&gt;：发邮件、建日程这类动作发出去就收不回，值得让人点个头。LangChain 内置了中间件（middleware）来拦截工具调用，这里要拦的正是&lt;strong&gt;子智能体手里的工具&lt;/strong&gt;。两件事要同时做：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把 &lt;code&gt;create_calendar_event&lt;/code&gt; 和 &lt;code&gt;send_email&lt;/code&gt; 配成&quot;命中即中断（interrupt）&quot;，允许 &lt;code&gt;approve&lt;/code&gt; / &lt;code&gt;edit&lt;/code&gt; / &lt;code&gt;reject&lt;/code&gt; 三种决定；&lt;/li&gt;
&lt;li&gt;检查点（checkpoint）&lt;strong&gt;只加在顶层主管&lt;/strong&gt;——因为暂停/恢复的是主管这份状态。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver

calendar_agent = create_agent(
    model,
    tools=[create_calendar_event, get_available_time_slots],
    system_prompt=CALENDAR_AGENT_PROMPT,
    middleware=[HumanInTheLoopMiddleware(
        interrupt_on={&quot;create_calendar_event&quot;: True},   # 命中即暂停，等人工决定
        description_prefix=&quot;日历事件待审批&quot;,
    )],
)

# 邮件子智能体同理，只把拦截对象换成 send_email
email_agent = create_agent(
    model, tools=[send_email], system_prompt=EMAIL_AGENT_PROMPT,
    middleware=[HumanInTheLoopMiddleware(interrupt_on={&quot;send_email&quot;: True},
                                         description_prefix=&quot;外发邮件待审批&quot;)],
)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# 检查点只加在主管这一层：暂停与恢复用的都是主管的状态
supervisor_agent = create_agent(
    model,
    tools=[schedule_event, manage_email],
    system_prompt=SUPERVISOR_PROMPT,
    checkpointer=InMemorySaver(),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;跑同一条 query，这次会被拦下来。&lt;code&gt;thread_id&lt;/code&gt; 是检查点的键，interrupt 事件要收集起来备用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;config = {&quot;configurable&quot;: {&quot;thread_id&quot;: &quot;6&quot;}}   # 检查点按 thread_id 归档

interrupts = []
stream = supervisor_agent.stream_events(
    {&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: query}]},
    config, version=&quot;v3&quot;,        # 带上 thread_id，检查点才有地方落
)
# 遍历同 §2；这次多了&quot;有没有被拦住&quot;的判断
if stream.interrupted:
    for interrupt_ in stream.interrupts:
        interrupts.append(interrupt_)
        print(f&quot;\n已中断: {interrupt_.id}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此刻会拿到两条中断事件，分别对应&quot;日历事件待审批&quot;&quot;外发邮件待审批&quot;，每条里带着子智能体&lt;strong&gt;已经解析好&lt;/strong&gt;的工具参数（日历的 &lt;code&gt;title&lt;/code&gt; / &lt;code&gt;start_time&lt;/code&gt; / &lt;code&gt;attendees&lt;/code&gt;，邮件的 &lt;code&gt;to&lt;/code&gt; / &lt;code&gt;subject&lt;/code&gt; / &lt;code&gt;body&lt;/code&gt;）——正好拿去给人看。&lt;/p&gt;
&lt;p&gt;接着用 &lt;code&gt;Command&lt;/code&gt; 按中断 ID 逐条给决定。原文的演示是：日历那条直接批准，邮件那条改掉标题再放行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langgraph.types import Command

resume = {}
for interrupt_ in interrupts:
    if interrupt_.id == &quot;2b56f299be313ad8bc689eff02973f16&quot;:   # 邮件那条：改标题后放行
        edited_action = interrupt_.value[&quot;action_requests&quot;][0].copy()
        edited_action[&quot;args&quot;][&quot;subject&quot;] = &quot;设计稿提醒&quot;
        resume[interrupt_.id] = {&quot;decisions&quot;: [{&quot;type&quot;: &quot;edit&quot;, &quot;edited_action&quot;: edited_action}]}
    else:                                                     # 日历那条：直接批准
        resume[interrupt_.id] = {&quot;decisions&quot;: [{&quot;type&quot;: &quot;approve&quot;}]}

stream = supervisor_agent.stream_events(Command(resume=resume), config, version=&quot;v3&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;恢复后流程继续走完：日历建好、邮件按改后的标题发出（回执里 &lt;code&gt;Subject: 设计稿提醒&lt;/code&gt;），主管照旧汇总成一段答复。&lt;/p&gt;
&lt;h3&gt;§7 进阶：控制信息流（Control information flow）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;默认行为&lt;/strong&gt;：子智能体只拿到主管转来的那一个 &lt;code&gt;request&lt;/code&gt; 字符串。多数场景够用，但遇到&quot;那就按明天同一时间安排&quot;这种带指代的请求，子智能体就会一脸懵——它看不到之前的对话。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;做法一：给子智能体补上下文&lt;/strong&gt;。用 &lt;code&gt;ToolRuntime&lt;/code&gt; 读到主管的状态（state），把用户最初那句话捞出来，跟子请求拼在一起喂进去。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import tool, ToolRuntime

@tool
def schedule_event(request: str, runtime: ToolRuntime) -&amp;gt; str:
    &quot;&quot;&quot;用自然语言安排日历事件。&quot;&quot;&quot;
    # 从主管的 state 里取出用户最初那一句原话
    original_user_message = next(
        m for m in runtime.state[&quot;messages&quot;] if m.type == &quot;human&quot;)
    prompt = (f&quot;你正在协助处理下面这位用户的请求：\n\n{original_user_message.text}\n\n&quot;
              f&quot;你这次要完成的子任务：\n\n{request}&quot;)
    result = calendar_agent.invoke({&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: prompt}]})
    return result[&quot;messages&quot;][-1].text
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;做法二：控制回传给主管的内容&lt;/strong&gt;。主管不一定只需要一句人话，有时结构化结果更好用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import json

# ...（前面几行同 §3：拿到 result = calendar_agent.invoke(...)）
# 方案一：只回一句确认文本（上下文最省）
return result[&quot;messages&quot;][-1].text
# 方案二：回结构化数据，主管更容易做后续判断
# return json.dumps({&quot;status&quot;: &quot;success&quot;, &quot;event_id&quot;: &quot;evt_123&quot;,
#                    &quot;summary&quot;: result[&quot;messages&quot;][-1].text})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原文在这里给了一句&lt;strong&gt;重要提醒&lt;/strong&gt;：一定要在子智能体的 prompt 里强调&quot;最终消息要包含所有关键信息&quot;。一个常见翻车姿势是——子智能体老老实实调了工具，但最终答复里什么结果都没写，主管于是收到一个空壳，只能瞎编或者反问用户。&lt;/p&gt;
&lt;h2&gt;关键机制专讲&lt;/h2&gt;
&lt;h3&gt;① &quot;子智能体 = 被包装成工具&quot;到底意味着什么&lt;/h3&gt;
&lt;p&gt;这不是修辞，是三个实打实的工程约束：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无状态&lt;/strong&gt;。&lt;code&gt;calendar_agent.invoke(...)&lt;/code&gt; 每次都是一个全新的、只含一条 user 消息的会话；子智能体不记得上次干过什么，除非显式配置检查点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每次调用从零开始&lt;/strong&gt;。没有跨调用的记忆，也没有&quot;上次那个会议的 ID&quot;。要延续上下文，得自己在包装函数里把信息拼进 &lt;code&gt;prompt&lt;/code&gt;（见 §7 做法一）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上下文隔离&lt;/strong&gt;。主管的对话历史不会自动流进子智能体，子智能体的中间过程也不会自动流回主管。信息只能靠&lt;strong&gt;你手写的那个字符串&lt;/strong&gt;进出——这既是限制，也是这个模式最大的价值：主管的上下文窗口不会被两个领域的过程细节撑爆。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;② 主管如何决策&quot;派谁&quot;&lt;/h3&gt;
&lt;p&gt;主管的决策依据只有三样，全都在它能看到的范围内：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;工具名&lt;/strong&gt;：&lt;code&gt;schedule_event&lt;/code&gt; / &lt;code&gt;manage_email&lt;/code&gt;——语义直白最重要；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具描述（docstring）&lt;/strong&gt;：写清&quot;什么时候用我、输入长什么样&quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;system prompt 里的分工说明&lt;/strong&gt;：&quot;拆解请求、按需顺序或并行调用&quot;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;换句话说，&lt;strong&gt;主管的&quot;路由准确率&quot;是提示词工程问题，不是代码问题&lt;/strong&gt;。它做的是领域级判断（&quot;这是日程还是邮件&quot;），不是 API 级判断（&quot;该填 ISO 还是时间戳&quot;）。把这两层混在一起，正是单 Agent 方案会退化的原因。&lt;/p&gt;
&lt;h3&gt;③ 人工介入到底加在哪一层&lt;/h3&gt;
&lt;p&gt;这是最容易搞混的一点，记三句话：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;拦截的钩子挂在子智能体上&lt;/strong&gt;。&lt;code&gt;HumanInTheLoopMiddleware(interrupt_on={&quot;send_email&quot;: True})&lt;/code&gt; 配在 &lt;code&gt;email_agent&lt;/code&gt; 上——要被审的是它手里的 &lt;code&gt;send_email&lt;/code&gt;，不是主管手里的 &lt;code&gt;manage_email&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;暂停/恢复的能力挂在主管上&lt;/strong&gt;。&lt;code&gt;checkpointer=InMemorySaver()&lt;/code&gt; 只加在顶层；原文明确说这是&lt;strong&gt;必需&lt;/strong&gt;的，没有检查点就无法暂停再恢复。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中断会冒泡到最外层&lt;/strong&gt;。你在主管的 stream 上就能看到来自两个子智能体的 interrupt，只需按 &lt;code&gt;interrupt_.id&lt;/code&gt; 分别回决定：&lt;code&gt;approve&lt;/code&gt;（照原样执行）、&lt;code&gt;edit&lt;/code&gt;（改参数后执行）、&lt;code&gt;reject&lt;/code&gt;（不执行）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以调用方（前端/AI 应用）只需要面对一个入口：跑主管 → 收到中断列表 → 展示给人 → 收集决定 → &lt;code&gt;Command(resume=...)&lt;/code&gt; 继续跑。子智能体的存在对调用方是透明的。&lt;/p&gt;
&lt;h3&gt;④ 信息流怎么裁剪&lt;/h3&gt;
&lt;p&gt;核心原则：&lt;strong&gt;边界上只传结论，不传过程&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;往下的方向&lt;/strong&gt;：默认只传 &lt;code&gt;request&lt;/code&gt; 字符串（最省）；需要消歧时再补一句用户原话。不要图省事把整段对话历史灌给每个子智能体——那等于把隔离白做了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;往上的方向&lt;/strong&gt;：&lt;code&gt;return result[&quot;messages&quot;][-1].text&lt;/code&gt; 只回最终答复。中间的工具调用链、推理过程，主管不需要也不该看到。若希望主管做更精确的后续动作（比如&quot;把刚建的事件 ID 记下来&quot;），再改成回 JSON（§7 方案二）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个隐性成本&lt;/strong&gt;：裁得越狠，子智能体越&quot;失忆&quot;。所以每个子智能体的 prompt 里都要有一句&quot;最终答复必须包含全部关键信息&quot;——这是在裁剪和信息完整之间唯一的安全带。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;完整代码在哪&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 官方教程：&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/subagents-personal-assistant&quot;&gt;Build a personal assistant with subagents&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;完整示例见原文 &lt;strong&gt;Complete working example&lt;/strong&gt; 一节（原文放在一个默认折叠的 Expandable 代码块里，约 160 行，把 §1 定义工具 → §2 建子智能体 → §3 包装成工具 → §4 建主管 → §5 跑起来串成一个脚本）。本文只摘骨架片段，不重复粘贴。原文 Next steps 指向的下一步阅读：&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/handoffs&quot;&gt;handoffs&lt;/a&gt;（智能体之间直接对话）、&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/context-engineering&quot;&gt;context engineering&lt;/a&gt;（精细调信息流）、&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent&quot;&gt;multi-agent overview&lt;/a&gt;（横向比较各模式）。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;主管 + 子智能体 = 按领域切工具，再用一层薄薄的&quot;路由 Agent&quot;把它们串起来&lt;/strong&gt;。主管看不到底层 API，子智能体看不到全局对话——隔离是设计出来的，不是自然发生的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;包装函数是唯一的接口&lt;/strong&gt;。&lt;code&gt;schedule_event(request: str) -&amp;gt; str&lt;/code&gt; 这种签名就是这个模式的全部魔法：进去一句人话，出来一句结论。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提示词就是路由表&lt;/strong&gt;。工具名、工具描述、主管的 system prompt——主管&quot;派谁&quot;完全由这三样决定，调优要从这里下手。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;人工介入挂在敏感动作上，检查点挂顶层&lt;/strong&gt;。&lt;code&gt;HumanInTheLoopMiddleware&lt;/code&gt; 配在子智能体，&lt;code&gt;checkpointer&lt;/code&gt; 配在主管；中断会冒泡到最外层统一处理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;信息流默认是断的，需要就手动接&lt;/strong&gt;。往下补上下文用 &lt;code&gt;ToolRuntime&lt;/code&gt;，往上裁剪只回最终消息；同时别忘了让子智能体在最终答复里&quot;把话说全&quot;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;该用&lt;/strong&gt;：有多个&lt;strong&gt;界限清晰&lt;/strong&gt;的领域（日历、邮件、CRM、数据库、检索），每个领域内部工具多或逻辑绕；你希望有一个中心点统一控制流程和对外输出；子智能体不需要直接跟用户对话。—— 这时主管模式能明显降低单 Agent 的工具选择负担，也让每个领域可以独立迭代。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;别用&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;只有两三个工具&lt;/strong&gt;——直接一个 Agent 就够，多一层只是多一层延迟和调试成本；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要子智能体直接和用户聊&lt;/strong&gt;（比如&quot;转人工专家&quot;式体验）——用 &lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/handoffs&quot;&gt;handoff&lt;/a&gt; 交接模式，而不是把子智能体藏进工具里；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要智能体之间平级协作&lt;/strong&gt;（互相讨论、辩论、投票）——那是另一类多智能体模式，主管模式天生是星形结构，不适合平级通信。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一句话：&lt;strong&gt;主管模式买的是&quot;关注点分离&quot;和&quot;中心化控制&quot;，代价是多一跳模型调用和一套需要精心维护的提示词边界。&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>08 Custom workflow 自定义工作流：流程由你画，该用代码的地方就用代码</title><link>https://aicooleo.cn/blog/posts/multiagent-08-custom-workflow/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-08-custom-workflow/</guid><description>前四种模式都是&quot;用模型决定路由&quot;；Custom workflow 是&quot;路由由你决定，模型只干该干的活&quot;。用 LangGraph 把流程画成图：哪一步是普通函数、哪一步是一次 LLM 调用、哪一步是一个完整 Agent，全由你定。</description><pubDate>Fri, 28 Aug 2026 21:33:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：前四种模式都是&quot;&lt;strong&gt;用模型决定路由&lt;/strong&gt;&quot;；Custom workflow 是&quot;&lt;strong&gt;路由由你决定，模型只干该干的活&lt;/strong&gt;&quot;。用 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/overview&quot;&gt;LangGraph&lt;/a&gt; 把流程画成图：&lt;strong&gt;哪一步是普通函数、哪一步是一次 LLM 调用、哪一步是一个完整 Agent，全由你定&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;它到底是什么&lt;/h2&gt;
&lt;p&gt;原文定义：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 &lt;strong&gt;custom workflow&lt;/strong&gt; 架构里，你用 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/overview&quot;&gt;LangGraph&lt;/a&gt; &lt;strong&gt;定义自己的定制执行流&lt;/strong&gt;。你对图结构有&lt;strong&gt;完全的控制权&lt;/strong&gt;——包括顺序步骤、条件分支、循环和并行执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;graph LR
    A([输入]) --&amp;gt; B{{条件分支}}
    B --&amp;gt;|path_a| C[确定性步骤]
    B --&amp;gt;|path_b| D((Agent 步骤))
    C --&amp;gt; G([输出])
    D --&amp;gt; G
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① 左边进来先做一次&lt;strong&gt;条件判断&lt;/strong&gt;（可以是普通代码，也可以是一次模型调用）；② 两条路&lt;strong&gt;性质不同&lt;/strong&gt;：&lt;code&gt;path_a&lt;/code&gt; 走的是&lt;strong&gt;确定性步骤&lt;/strong&gt;（纯代码，比如查库、算数、格式转换），&lt;code&gt;path_b&lt;/code&gt; 走的是&lt;strong&gt;Agent 步骤&lt;/strong&gt;（带工具、要推理）；③ 两条路最后汇到同一个输出。&lt;strong&gt;这就是 Custom workflow 的精髓：按需混用&quot;代码&quot;和&quot;模型&quot;。&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;四个关键特征&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特征&lt;/th&gt;
&lt;th&gt;原文&lt;/th&gt;
&lt;th&gt;说人话&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;完全控制图结构&lt;/td&gt;
&lt;td&gt;Complete control over graph structure&lt;/td&gt;
&lt;td&gt;顺序、分支、循环、并行，随你连&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;混合&lt;/strong&gt;确定性与 Agent 行为&lt;/td&gt;
&lt;td&gt;Mix deterministic logic with agentic behavior&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;能用代码的地方就别用模型&lt;/strong&gt;（省钱、稳定、可测）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;支持顺序/分支/循环/并行&lt;/td&gt;
&lt;td&gt;Sequential steps, conditional branches, loops, parallel execution&lt;/td&gt;
&lt;td&gt;图能表达的东西它都能表达&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可以&lt;strong&gt;嵌套&lt;/strong&gt;其它模式&lt;/td&gt;
&lt;td&gt;Embed other patterns as nodes in your workflow&lt;/td&gt;
&lt;td&gt;把一整个多 Agent 系统塞进一个节点&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;原文对&quot;节点里能放什么&quot;说得很清楚：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你的工作流里&lt;strong&gt;每个节点&lt;/strong&gt;都可以是一个&lt;strong&gt;简单函数&lt;/strong&gt;、一次 &lt;strong&gt;LLM 调用&lt;/strong&gt;，或者一个&lt;strong&gt;带&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/tools&quot;&gt;工具&lt;/a&gt;的完整 &lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/agents&quot;&gt;Agent&lt;/a&gt;&lt;/strong&gt;。你还可以在工作流里&lt;strong&gt;组合其它架构&lt;/strong&gt;——例如把一整个多 Agent 系统嵌成一个节点。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这一条是本篇最值钱的设计原则，和工程常识完全一致——
&lt;strong&gt;&quot;该确定的事，永远不要交给模型&quot;&lt;/strong&gt;。校验、格式转换、金额计算、权限判断、字段映射：写成节点函数，可测、可复现、几乎零成本。模型只留给真正需要推理/判断的节点。
反过来说，如果你发现整个流程用 &lt;code&gt;if/match&lt;/code&gt; + 几个函数就能写清楚，&lt;strong&gt;那就别用 LangGraph&lt;/strong&gt;（这一点在第 &lt;code&gt;01&lt;/code&gt; 篇也提过）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;什么时候用它&lt;/h2&gt;
&lt;p&gt;原文三条：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;标准模式（subagents、skills 等）都不合适&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;你需要&lt;strong&gt;把确定性逻辑和 Agent 行为混在一起&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;你的场景需要&lt;strong&gt;复杂的路由或多阶段处理&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 &lt;strong&gt;官方实战教程&lt;/strong&gt;：&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/router-knowledge-base&quot;&gt;Build a multi-source knowledge base with routing&lt;/a&gt;。
原文明确说了：&lt;strong&gt;Router 模式本身就是 Custom workflow 的一个例子&lt;/strong&gt;——所以 &lt;code&gt;07&lt;/code&gt; 篇的实战教程，同时也是本篇最好的完整案例（本笔记的精讲版见 &lt;a href=&quot;/posts/multiagent-12-case-router/&quot;&gt;&lt;code&gt;12&lt;/code&gt;&lt;/a&gt;）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;最小实现：在任何 LangGraph 节点里直接调 Agent&lt;/h2&gt;
&lt;p&gt;原文点出了核心洞察：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;你可以在任何 LangGraph 节点里直接调用 LangChain Agent&lt;/strong&gt;——这样就同时拿到了自定义工作流的灵活性和预置 Agent 的便利性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;from langchain.agents import create_agent
from langgraph.graph import StateGraph, START, END

agent = create_agent(model=&quot;openai:gpt-5.5&quot;, tools=[...])

def agent_node(state: State) -&amp;gt; dict:
    &quot;&quot;&quot;一个 LangGraph 节点：里面调用一个 LangChain Agent。&quot;&quot;&quot;
    result = agent.invoke({
        &quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: state[&quot;query&quot;]}]
    })
    return {&quot;answer&quot;: result[&quot;messages&quot;][-1].content}

# 组一个最简单的工作流
workflow = (
    StateGraph(State)
    .add_node(&quot;agent&quot;, agent_node)
    .add_edge(START, &quot;agent&quot;)
    .add_edge(&quot;agent&quot;, END)
    .compile()
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这段代码在说什么&lt;/strong&gt;：① &lt;code&gt;create_agent&lt;/code&gt; 造出来的 Agent &lt;strong&gt;就是一个普通对象&lt;/strong&gt;，可以在任何函数里 &lt;code&gt;.invoke(...)&lt;/code&gt;；② 节点函数的契约是&quot;&lt;strong&gt;读 state → 返回要更新的字段&lt;/strong&gt;&quot;（这里是 &lt;code&gt;{&quot;answer&quot;: ...}&lt;/code&gt;）；③ 用 &lt;code&gt;StateGraph&lt;/code&gt; 把节点连起来，&lt;code&gt;compile()&lt;/code&gt; 之后就是一个可执行的工作流。&lt;strong&gt;&quot;Agent 即节点&quot;是这套架构的全部魔法。&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;实例：一个 RAG 管道（三种节点全用上）&lt;/h2&gt;
&lt;p&gt;原文给了一个 WNBA 数据助手的例子：先从知识库检索，还能去取最新新闻。它演示了&lt;strong&gt;三类节点&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;节点类型&lt;/th&gt;
&lt;th&gt;例子里的节点&lt;/th&gt;
&lt;th&gt;干什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;模型节点（Model node）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rewrite&lt;/td&gt;
&lt;td&gt;用&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/structured-output&quot;&gt;结构化输出&lt;/a&gt;把用户问题&lt;strong&gt;改写成更适合检索的查询&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;确定性节点（Deterministic node）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Retrieve&lt;/td&gt;
&lt;td&gt;做&lt;strong&gt;向量相似度检索&lt;/strong&gt;——&lt;strong&gt;完全不涉及 LLM&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent 节点（Agent node）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;td&gt;基于检索到的上下文推理，还能&lt;strong&gt;通过工具&lt;/strong&gt;去取额外信息&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;graph LR
    A([用户问题]) --&amp;gt; B{{改写 Rewrite}}
    B --&amp;gt; C[(检索 Retrieve)]
    C --&amp;gt; D((Agent))
    D --&amp;gt; E([回答])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① &quot;改写&quot;是一次便宜的模型调用（只为提升检索命中率）；② &quot;检索&quot;是纯代码，没有任何模型参与；③ &quot;推理 + 可能调工具&quot;才交给完整 Agent。&lt;strong&gt;整条链路只在必要处花钱。&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示（原文）&lt;/strong&gt;：你可以用 &lt;strong&gt;LangGraph 的 state 在工作流各步之间传信息&lt;/strong&gt;。这让流程里的每一部分都能&lt;strong&gt;读写结构化字段&lt;/strong&gt;，从而方便地在节点之间共享数据与上下文。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;完整代码（原文，已精简）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from typing import TypedDict
from pydantic import BaseModel
from langgraph.graph import StateGraph, START, END
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore

class State(TypedDict):
    question: str
    rewritten_query: str
    documents: list[str]
    answer: str

# —— 知识库与检索器（内容略，原文是 WNBA 名单/赛果/球员数据）——
embeddings = OpenAIEmbeddings()
vector_store = InMemoryVectorStore(embeddings)
vector_store.add_texts([...])
retriever = vector_store.as_retriever(search_kwargs={&quot;k&quot;: 5})

@tool
def get_latest_news(query: str) -&amp;gt; str:
    &quot;&quot;&quot;Get the latest WNBA news and updates.&quot;&quot;&quot;
    # 这里接你自己的新闻 API
    return &quot;Latest: The WNBA announced expanded playoff format for 2025...&quot;

agent = create_agent(model=&quot;openai:gpt-5.5&quot;, tools=[get_latest_news])
model = ChatOpenAI(model=&quot;gpt-5.5&quot;)

class RewrittenQuery(BaseModel):
    query: str

# ① 模型节点：改写查询（结构化输出）
def rewrite_query(state: State) -&amp;gt; dict:
    &quot;&quot;&quot;把用户问题改写成更适合检索的查询。&quot;&quot;&quot;
    system_prompt = &quot;&quot;&quot;Rewrite this query to retrieve relevant WNBA information.
The knowledge base contains: team rosters, game results with scores, and player statistics (PPG, RPG, APG).
Focus on specific player names, team names, or stat categories mentioned.&quot;&quot;&quot;
    response = model.with_structured_output(RewrittenQuery).invoke([
        {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: system_prompt},
        {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: state[&quot;question&quot;]},
    ])
    return {&quot;rewritten_query&quot;: response.query}

# ② 确定性节点：纯向量检索，无 LLM
def retrieve(state: State) -&amp;gt; dict:
    &quot;&quot;&quot;基于改写后的查询检索文档。&quot;&quot;&quot;
    docs = retriever.invoke(state[&quot;rewritten_query&quot;])
    return {&quot;documents&quot;: [doc.page_content for doc in docs]}

# ③ Agent 节点：带着检索结果推理，还能调工具
def call_agent(state: State) -&amp;gt; dict:
    &quot;&quot;&quot;用检索到的上下文生成回答。&quot;&quot;&quot;
    context = &quot;\n\n&quot;.join(state[&quot;documents&quot;])
    prompt = f&quot;Context:\n{context}\n\nQuestion: {state[&apos;question&apos;]}&quot;
    response = agent.invoke({&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: prompt}]})
    return {&quot;answer&quot;: response[&quot;messages&quot;][-1].content_blocks}

workflow = (
    StateGraph(State)
    .add_node(&quot;rewrite&quot;, rewrite_query)
    .add_node(&quot;retrieve&quot;, retrieve)
    .add_node(&quot;agent&quot;, call_agent)
    .add_edge(START, &quot;rewrite&quot;)
    .add_edge(&quot;rewrite&quot;, &quot;retrieve&quot;)
    .add_edge(&quot;retrieve&quot;, &quot;agent&quot;)
    .add_edge(&quot;agent&quot;, END)
    .compile()
)

result = workflow.invoke({&quot;question&quot;: &quot;Who won the 2024 WNBA Championship?&quot;})
print(result[&quot;answer&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这段代码在说什么&lt;/strong&gt;：① &lt;code&gt;State&lt;/code&gt; 是贯穿全流程的&lt;strong&gt;结构化数据袋&lt;/strong&gt;（问题 → 改写后查询 → 检索到的文档 → 回答）；② 三种节点的写法完全一样（都是 &lt;code&gt;state -&amp;gt; dict&lt;/code&gt;），区别只在&lt;strong&gt;里面干了什么&lt;/strong&gt;；③ &lt;code&gt;.add_edge()&lt;/code&gt; 把这些节点串成一条直线——要加分支就换 &lt;code&gt;add_conditional_edges&lt;/code&gt;，要加循环就把边连回去，要并行就扇出（见 &lt;code&gt;07&lt;/code&gt; 和 &lt;code&gt;12&lt;/code&gt;）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ℹ️ &lt;strong&gt;生产环境提醒（原文）&lt;/strong&gt;：别用 &lt;code&gt;InMemoryVectorStore&lt;/code&gt;，请换成持久化的向量库，例如 &lt;a href=&quot;https://docs.langchain.com/oss/python/integrations/vectorstores/valkey&quot;&gt;Valkey&lt;/a&gt;、&lt;a href=&quot;https://docs.langchain.com/oss/python/integrations/vectorstores/databricks_vector_search&quot;&gt;Databricks Vector Search&lt;/a&gt; 或 &lt;a href=&quot;https://docs.langchain.com/oss/python/integrations/vectorstores/mongodb_atlas&quot;&gt;MongoDB Atlas&lt;/a&gt;。完整清单见&lt;a href=&quot;https://docs.langchain.com/oss/python/integrations/vectorstores&quot;&gt;所有向量库&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;原文如此&lt;/strong&gt;：&lt;code&gt;call_agent&lt;/code&gt; 里返回的是 &lt;code&gt;response[&quot;messages&quot;][-1].content_blocks&lt;/code&gt;（内容块列表），而 &lt;code&gt;State&lt;/code&gt; 里声明的 &lt;code&gt;answer: str&lt;/code&gt;。原文就是这么写的，类型上并不严格一致；自己落地时建议统一（取 &lt;code&gt;.content&lt;/code&gt; 或把 State 改成块列表）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Custom workflow 与前四种模式的关系&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;        ┌───────────────────── 谁来决定&quot;下一步去哪&quot; ─────────────────────┐
        │                                                              │
   模型决定（Agentic）                                    你决定（Deterministic）
        │                                                              │
   ┌────┴─────┬──────────┬───────────┐                    ┌─────────────┴─────────────┐
 Subagents  Handoffs   Skills     Router              Custom workflow（LangGraph）
 （主管）    （状态机） （按需加载）（一次分诊）        顺序/分支/循环/并行 由代码连
        │         │          │           │                    │
        └─────────┴──────────┴───────────┘                    │
                       │                                     │
                       └────── 都可以被&quot;塞进&quot; ────────────────┘
                              Custom workflow 的一个节点里
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：把这张图记住，多 Agent 选型就不会乱——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要&lt;strong&gt;模型临场决策&lt;/strong&gt;（派谁、换谁、加载什么）→ 前四种模式。&lt;/li&gt;
&lt;li&gt;需要&lt;strong&gt;流程可控&lt;/strong&gt;（必须先 A 再 B、失败要重试、要循环直到达标）→ Custom workflow。&lt;/li&gt;
&lt;li&gt;两者不是竞争关系：&lt;strong&gt;生产系统常见形态是&quot;LangGraph 主干 + 局部 Agent 节点&quot;&lt;/strong&gt;——主干保证流程与可观测性，Agent 节点提供灵活判断。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一句话&lt;/strong&gt;：Custom workflow = &lt;strong&gt;用 LangGraph 自己画图&lt;/strong&gt;，节点可以是普通函数、一次 LLM 调用，或一个完整 Agent。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;四个特征&lt;/strong&gt;：完全控制图结构、混合确定性与 Agent 行为、支持顺序/分支/循环/并行、可以把其它模式当作节点嵌进来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;核心洞察&lt;/strong&gt;：&lt;strong&gt;在任何节点里都能直接 &lt;code&gt;.invoke()&lt;/code&gt; 一个 LangChain Agent&lt;/strong&gt;——&quot;Agent 即节点&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAG 实例演示了三类节点&lt;/strong&gt;：模型节点（改写）、确定性节点（检索）、Agent 节点（推理 + 工具），这正是&quot;该花的花、该省的省&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State 是粘合剂&lt;/strong&gt;：节点之间靠结构化的图状态传数据（&lt;code&gt;TypedDict&lt;/code&gt; + 每个节点返回要更新的字段）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;它是最贴近生产形态的模式&lt;/strong&gt;：流程与副作用由代码掌控，只有真正需要推理的位置才放模型。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;选它&lt;/strong&gt;：标准模式都不合适；需要把确定性逻辑与 Agent 混用；流程有分支/循环/重试/多阶段；需要把整个多 Agent 系统当成一个可观测、可恢复的图来跑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别选它&lt;/strong&gt;：需求正好命中前四种模式（别再手搓一遍）；流程简单到几个函数就能写完（用普通 Python，别上图）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;上一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;07 Router 路由&lt;/a&gt; ｜ &lt;strong&gt;回目录&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-overview/&quot;&gt;README&lt;/a&gt; ｜ &lt;strong&gt;实战案例&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-12-case-router/&quot;&gt;12 用 Router 搭多源知识库&lt;/a&gt;（Router 本身就是一个 custom workflow）&lt;/p&gt;
</content:encoded></item><item><title>07 Router 路由：先分诊，再并行开查，最后汇总</title><link>https://aicooleo.cn/blog/posts/multiagent-07-router/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-07-router/</guid><description>进来先&quot;分诊&quot;一次——判断这个问题属于哪个（或哪几个）领域，然后把请求并行扔给对应的专家 Agent，最后把几份结果合并成一个完整回答。它不聊天、不记仇（无状态），就是一个&quot;预处理步骤&quot;。</description><pubDate>Fri, 28 Aug 2026 15:16:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：&lt;strong&gt;进来先&quot;分诊&quot;一次&lt;/strong&gt;——判断这个问题属于哪个（或哪几个）领域，然后把请求&lt;strong&gt;并行&lt;/strong&gt;扔给对应的专家 Agent，最后把几份结果&lt;strong&gt;合并&lt;/strong&gt;成一个完整回答。它不聊天、不记仇（无状态），就是一个&quot;预处理步骤&quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;它到底是什么&lt;/h2&gt;
&lt;p&gt;原文定义：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 &lt;strong&gt;router&lt;/strong&gt; 架构里，&lt;strong&gt;一个路由步骤先给输入分类&lt;/strong&gt;，再把它定向到专门的 &lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/agents&quot;&gt;Agent&lt;/a&gt;。当你有若干个互不相同的 &lt;strong&gt;verticals（垂直领域）&lt;/strong&gt;——各自需要独立 Agent 的独立知识域——时，这个模式很有用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;graph LR
    A([查询]) --&amp;gt; B[路由 Router]
    B --&amp;gt; C[Agent A]
    B --&amp;gt; D[Agent B]
    B --&amp;gt; E[Agent C]
    C --&amp;gt; F[汇总 Synthesize]
    D --&amp;gt; F
    E --&amp;gt; F
    F --&amp;gt; G([合并后的回答])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① 入口只有&lt;strong&gt;一次分类&lt;/strong&gt;（Router 节点）；② 分类结果可以指向&lt;strong&gt;一个或多个&lt;/strong&gt; Agent（图中同时指向 A、B、C，即并行）；③ 所有专家的结果都汇到 &lt;code&gt;Synthesize&lt;/code&gt;（汇总）节点；④ 用户拿到的是&lt;strong&gt;一份合并后的回答&lt;/strong&gt;，而不是三份碎片。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;三个关键特征&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特征&lt;/th&gt;
&lt;th&gt;原文&lt;/th&gt;
&lt;th&gt;说人话&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;路由器&lt;strong&gt;分解&lt;/strong&gt;查询&lt;/td&gt;
&lt;td&gt;Router decomposes the query&lt;/td&gt;
&lt;td&gt;把&quot;一个大问题&quot;拆成&quot;分别该谁回答&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;调用&lt;strong&gt;零个或多个&lt;/strong&gt;专家 Agent，且&lt;strong&gt;并行&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zero or more specialized agents are invoked in parallel&lt;/td&gt;
&lt;td&gt;&quot;零个&quot;很关键——有时候判定&quot;不用查&quot;，直接答&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;结果被&lt;strong&gt;汇总&lt;/strong&gt;成连贯回答&lt;/td&gt;
&lt;td&gt;Results are synthesized into a coherent response&lt;/td&gt;
&lt;td&gt;必须有一步&quot;合并&quot;，否则用户收到三份互不相干的答案&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;什么时候用它&lt;/h2&gt;
&lt;p&gt;原文三条：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你有&lt;strong&gt;互不相同的垂直领域&lt;/strong&gt;（各自需要独立 Agent 的知识域）；&lt;/li&gt;
&lt;li&gt;需要&lt;strong&gt;并行&lt;/strong&gt;查询多个来源；&lt;/li&gt;
&lt;li&gt;想把这些结果&lt;strong&gt;汇总&lt;/strong&gt;成一个合并回答。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：Router 最舒服的场景是**&quot;知识源是分仓的&quot;&lt;strong&gt;——产品文档在 GitHub、需求在 Notion、讨论在 Slack（这正是官方教程的例子）。这类场景里&quot;该查哪几个仓&quot;是个&lt;/strong&gt;分类问题**，而不是&quot;多轮对话&quot;问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;最小可用实现：两种路由方式&lt;/h2&gt;
&lt;p&gt;原文给了两种机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/graph-api#command&quot;&gt;&lt;code&gt;Command&lt;/code&gt;&lt;/a&gt; → &lt;strong&gt;路由到单个&lt;/strong&gt;专家 Agent；&lt;/li&gt;
&lt;li&gt;用 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/graph-api#send&quot;&gt;&lt;code&gt;Send&lt;/code&gt;&lt;/a&gt; → &lt;strong&gt;并行扇出&lt;/strong&gt;到多个 Agent。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;方式一：单个 Agent（用 &lt;code&gt;Command&lt;/code&gt;）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from langgraph.types import Command

def classify_query(query: str) -&amp;gt; str:
    &quot;&quot;&quot;用 LLM 给 query 分类，决定该交给哪个 Agent。&quot;&quot;&quot;
    # 分类逻辑
    ...

def route_query(state: State) -&amp;gt; Command:
    &quot;&quot;&quot;根据分类结果路由到合适的 Agent。&quot;&quot;&quot;
    active_agent = classify_query(state[&quot;query&quot;])

    # 路由到被选中的那个 Agent
    return Command(goto=active_agent)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这段代码在说什么&lt;/strong&gt;：① &lt;code&gt;classify_query&lt;/code&gt; 是&quot;分诊&quot;；② &lt;code&gt;Command(goto=...)&lt;/code&gt; 是 LangGraph 里的&quot;直接跳转到某个节点&quot;；③ 一次只去一个地方——&lt;strong&gt;这是&quot;或&quot;的关系，不是&quot;且&quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;方式二：多个 Agent 并行（用 &lt;code&gt;Send&lt;/code&gt;）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from typing import TypedDict
from langgraph.types import Send

class ClassificationResult(TypedDict):
    query: str
    agent: str

def classify_query(query: str) -&amp;gt; list[ClassificationResult]:
    &quot;&quot;&quot;用 LLM 分类，决定要调起哪些 Agent（可能多个）。&quot;&quot;&quot;
    # 分类逻辑
    ...

def route_query(state: State):
    &quot;&quot;&quot;根据分类结果把请求扇出给相关 Agent。&quot;&quot;&quot;
    classifications = classify_query(state[&quot;query&quot;])

    # 并行扇出到被选中的各个 Agent
    return [
        Send(c[&quot;agent&quot;], {&quot;query&quot;: c[&quot;query&quot;]})
        for c in classifications
    ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这段代码在说什么&lt;/strong&gt;：① 分类结果是一个&lt;strong&gt;列表&lt;/strong&gt;（可能命中多个领域）；② 每个命中项生成一个 &lt;code&gt;Send(...)&lt;/code&gt;，&lt;code&gt;Send&lt;/code&gt; 的作用是&quot;&lt;strong&gt;往某个节点发一份独立的输入&lt;/strong&gt;&quot;，LangGraph 会&lt;strong&gt;并行&lt;/strong&gt;执行这些分支；③ 返回列表 = &lt;strong&gt;扇出（fan-out）&lt;/strong&gt;，这是 Router 相对 Handoffs 的最大优势。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：&lt;code&gt;Send&lt;/code&gt; 还解决了一个工程难题——&lt;strong&gt;分支数量在编译期不确定&lt;/strong&gt;。你可以理解为&quot;图上的边是运行时才生成的&quot;。并行分支的结果怎么合回一个列表？靠 &lt;strong&gt;reducer（归并函数）&lt;/strong&gt;，具体做法在 &lt;a href=&quot;/posts/multiagent-12-case-router/&quot;&gt;&lt;code&gt;12&lt;/code&gt; 篇的实战&lt;/a&gt;里有完整演示。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 &lt;strong&gt;官方实战教程&lt;/strong&gt;：&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/router-knowledge-base&quot;&gt;Build a multi-source knowledge base with routing&lt;/a&gt;——并行查 GitHub、Notion、Slack 三个来源，再汇总成连贯回答；涵盖状态定义、专用 Agent、&lt;code&gt;Send&lt;/code&gt; 并行执行与结果汇总（本笔记的精讲版见 &lt;a href=&quot;/posts/multiagent-12-case-router/&quot;&gt;&lt;code&gt;12&lt;/code&gt;&lt;/a&gt;）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;有状态 vs 无状态&lt;/h2&gt;
&lt;p&gt;原文把 Router 分成两种用法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#%E6%97%A0%E7%8A%B6%E6%80%81%E6%AF%8F%E6%9D%A5%E4%B8%80%E4%B8%AA%E8%AF%B7%E6%B1%82%E7%8B%AC%E7%AB%8B%E5%A4%84%E7%90%86&quot;&gt;&lt;strong&gt;无状态路由&lt;/strong&gt;&lt;/a&gt;：每个请求&lt;strong&gt;独立&lt;/strong&gt;处理；&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%E6%9C%89%E7%8A%B6%E6%80%81%E8%A6%81%E8%B7%A8%E8%BD%AE%E8%AE%B0%E4%BD%8F%E4%B8%8A%E4%B8%8B%E6%96%87&quot;&gt;&lt;strong&gt;有状态路由&lt;/strong&gt;&lt;/a&gt;：跨多次请求&lt;strong&gt;维护对话历史&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;无状态：每来一个请求独立处理&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：每个请求都被独立路由——&lt;strong&gt;调用之间没有记忆&lt;/strong&gt;。多轮会话请走&lt;a href=&quot;#%E6%9C%89%E7%8A%B6%E6%80%81%E8%A6%81%E8%B7%A8%E8%BD%AE%E8%AE%B0%E4%BD%8F%E4%B8%8A%E4%B8%8B%E6%96%87&quot;&gt;有状态路由&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Router vs Subagents（原文的 Tip，务必读）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;两种模式都能把工作派发给多个 Agent，但&lt;strong&gt;路由决策的产生方式不同&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Router&lt;/strong&gt;：一个&lt;strong&gt;专门的路由步骤&lt;/strong&gt;（通常是一次 LLM 调用或基于规则的逻辑）给输入分类并分发。&lt;strong&gt;路由器本身通常不维护对话历史、也不做多轮编排&lt;/strong&gt;——它是一个&lt;strong&gt;预处理步骤&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subagents&lt;/strong&gt;：一个&lt;strong&gt;主管 Agent 在持续对话中动态决定&lt;/strong&gt;要调哪些&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/subagents&quot;&gt;子智能体&lt;/a&gt;。主管维护上下文，可以&lt;strong&gt;跨回合&lt;/strong&gt;调用多个子智能体，编排复杂多步工作流。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;什么时候用 Router&lt;/strong&gt;：你有&lt;strong&gt;清晰的输入类别&lt;/strong&gt;，并且想要确定性的或轻量的分类。
&lt;strong&gt;什么时候用 Supervisor&lt;/strong&gt;：你需要&lt;strong&gt;灵活的、感知对话的编排&lt;/strong&gt;——由 LLM 根据不断演进的上下文决定下一步做什么。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：一句话分辨——&lt;strong&gt;Router 是&quot;进门分诊&quot;，Subagents 是&quot;全程陪聊的管家&quot;&lt;/strong&gt;。判断标准是&quot;这件事需要跨轮推进吗&quot;：需要 → Subagents；一次问清 → Router。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;有状态：要跨轮记住上下文&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：多轮对话场景下，你需要在多次调用之间维护上下文。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;做法一：工具包装（Tool wrapper）&lt;/h4&gt;
&lt;p&gt;原文说这是&lt;strong&gt;最简单的做法&lt;/strong&gt;：把无状态路由器&lt;strong&gt;包成一个工具&lt;/strong&gt;，让一个&quot;会聊天&quot;的 Agent 去调它。&lt;strong&gt;对话 Agent 负责记忆与上下文，路由器保持无状态&lt;/strong&gt;——这样就避开了&quot;在多个并行 Agent 之间管理对话历史&quot;的复杂度。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@tool
def search_docs(query: str) -&amp;gt; str:
    &quot;&quot;&quot;Search across multiple documentation sources.&quot;&quot;&quot;
    result = workflow.invoke({&quot;query&quot;: query})
    return result[&quot;final_answer&quot;]

# 会聊天的 Agent 把路由器当成一个工具来用
conversational_agent = create_agent(
    model,
    tools=[search_docs],
    prompt=&quot;You are a helpful assistant. Use search_docs to answer questions.&quot;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这段代码在说什么&lt;/strong&gt;：① 路由器还是那个无状态的路由器（&lt;code&gt;workflow.invoke&lt;/code&gt;）；② 记忆全在外层 &lt;code&gt;conversational_agent&lt;/code&gt; 上；③ 这其实是 &lt;code&gt;04&lt;/code&gt; Subagents 的&quot;一个子智能体&quot;特例——&lt;strong&gt;用一个会记事的 Agent 给无状态路由器补上记忆&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;做法二：完整持久化（Full persistence）&lt;/h4&gt;
&lt;p&gt;如果你需要&lt;strong&gt;路由器自己&lt;/strong&gt;维护状态，就用&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/short-term-memory&quot;&gt;持久化&lt;/a&gt;来保存消息历史。路由到某个 Agent 时，从状态里取出之前的消息，&lt;strong&gt;有选择地&lt;/strong&gt;放进该 Agent 的上下文——这正是&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/context-engineering&quot;&gt;上下文工程&lt;/a&gt;的一个杠杆。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;警告（原文）&lt;/strong&gt;：&lt;strong&gt;有状态路由器需要自定义历史管理。&lt;/strong&gt; 如果路由器在多个回合之间&lt;strong&gt;在不同 Agent 之间切换&lt;/strong&gt;，当这些 Agent 的语气或提示词不同时，用户会觉得对话&lt;strong&gt;不连贯&lt;/strong&gt;。并行调用的情况下，你需要在&lt;strong&gt;路由器层&lt;/strong&gt;维护历史（输入 + 汇总后的输出），并在路由逻辑里用上这份历史。&lt;strong&gt;不妨考虑改用 &lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;handoffs 模式&lt;/a&gt; 或 &lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;subagents 模式&lt;/a&gt;——它们在多轮对话上有更清晰的语义。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这条警告的潜台词是——&lt;strong&gt;别拿 Router 硬撑多轮对话&lt;/strong&gt;。Router 的强项是&quot;一次问清、多源并行、合并输出&quot;；一旦你要&quot;记住用户上轮说了什么、这轮该换个专家接着聊&quot;，语义就滑向 Handoffs/Subagents 了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;一张图看清三种&quot;派活&quot;方式的区别&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;【Router】一次分诊，并行执行，一次汇总 —— 无记忆
用户问题 ─▶ 分类（1 次模型调用）
              ├─▶ 知识源 A ─┐
              ├─▶ 知识源 B ─┼─▶ 汇总 ─▶ 合并回答
              └─▶ 知识源 C ─┘

【Subagents】主管全程在，逐个/并行派活，结果都回主管 —— 主管有记忆
用户 ◀──▶ 主管 ──▶ 子智能体 A（隔离上下文，无记忆）
              ├──▶ 子智能体 B
              └──▶ 子智能体 C   ← 每次调用都从零开始

【Handoffs】一个 Agent 换着身份陪用户走完全程 —— 串行、有状态
用户 ◀──▶ [步骤1 的 Agent] ──状态──▶ [步骤2 的 Agent] ──状态──▶ [步骤3 的 Agent]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一句话&lt;/strong&gt;：Router = &lt;strong&gt;先分类 → 并行分发（0..N 个专家）→ 汇总&lt;/strong&gt;，典型场景是&quot;分仓知识源&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两种路由机制&lt;/strong&gt;：&lt;code&gt;Command(goto=...)&lt;/code&gt; 路由到&lt;strong&gt;单个&lt;/strong&gt;；&lt;code&gt;Send(...)&lt;/code&gt; 列表实现&lt;strong&gt;动态并行扇出&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;三个特征&lt;/strong&gt;：分解查询、并行调用零或多个专家、结果汇总成连贯回答。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无状态是默认形态&lt;/strong&gt;：要做多轮，要么&lt;strong&gt;包成工具&lt;/strong&gt;给会聊天的 Agent 用（简单），要么做完整持久化（复杂，且官方建议改用 Handoffs/Subagents）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Router vs Subagents&lt;/strong&gt;：Router 是&lt;strong&gt;预处理式的一次分类&lt;/strong&gt;（类别清晰、要确定性/轻量）；Subagents 是&lt;strong&gt;跨回合的动态编排&lt;/strong&gt;（上下文演进、要灵活）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代价&lt;/strong&gt;：多一次路由调用（&lt;code&gt;03&lt;/code&gt; 篇里重复请求每轮都多这一次）；换来的是并行与明确的分类边界。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;选它&lt;/strong&gt;：输入类别清晰、有多个分仓知识源、要并行查、要合并成一个回答；或者你只想要一次&quot;确定性的轻量分类&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别选它&lt;/strong&gt;：需要多轮对话记忆（用 &lt;code&gt;05&lt;/code&gt; Handoffs 或 &lt;code&gt;04&lt;/code&gt; Subagents）；任务需要&quot;A 完了接着 B&quot;的多跳串联（Router 不支持多跳）；只是想让单 Agent 按需加载知识（用 &lt;code&gt;06&lt;/code&gt; Skills）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;上一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-06-skills/&quot;&gt;06 Skills 技能&lt;/a&gt; ｜ &lt;strong&gt;下一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-08-custom-workflow/&quot;&gt;08 Custom workflow 自定义工作流&lt;/a&gt; ｜ &lt;strong&gt;实战&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-12-case-router/&quot;&gt;12 用 Router 搭多源知识库&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>06 Skills 技能：一个 Agent + 一柜子按需翻的说明书</title><link>https://aicooleo.cn/blog/posts/multiagent-06-skills/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-06-skills/</guid><description>不拆 Agent，改拆知识。把&quot;专业提示词 + 领域知识&quot;打包成一个个&quot;技能&quot;，Agent 平时只知道技能目录（有哪几本手册），真需要时才用 load_skill 把它翻出来读——这就是渐进式披露（progressive disclosu…</description><pubDate>Thu, 27 Aug 2026 22:51:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：&lt;strong&gt;不拆 Agent，改拆知识&lt;/strong&gt;。把&quot;专业提示词 + 领域知识&quot;打包成一个个&quot;技能&quot;，Agent 平时只知道技能目录（有哪几本手册），真需要时才用 &lt;code&gt;load_skill&lt;/code&gt; 把它翻出来读——这就是&lt;strong&gt;渐进式披露（progressive disclosure）&lt;/strong&gt;，省的是上下文窗口。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;它到底是什么&lt;/h2&gt;
&lt;p&gt;原文定义：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 &lt;strong&gt;skills&lt;/strong&gt; 架构里，专门能力被打包成可调用的&quot;技能&quot;，用来增强 &lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/agents&quot;&gt;Agent&lt;/a&gt; 的行为。技能&lt;strong&gt;主要是提示词驱动的专长&lt;/strong&gt;，Agent 可以&lt;strong&gt;按需&lt;/strong&gt;调用它们。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 &lt;strong&gt;想要内置实现&lt;/strong&gt;：Deep Agents 自带 skills 支持，见 &lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/skills&quot;&gt;Deep Agents / Skills&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示（原文）&lt;/strong&gt;：这个模式在概念上和 &lt;a href=&quot;https://agentskills.io/&quot;&gt;Agent Skills&lt;/a&gt;、以及 Jeremy Howard 提出的 &lt;a href=&quot;https://llmstxt.org/&quot;&gt;llms.txt&lt;/a&gt; &lt;strong&gt;完全一致&lt;/strong&gt;——都是&quot;&lt;strong&gt;用工具调用实现文档的渐进式披露&lt;/strong&gt;&quot;。区别在于：skills 模式把这套做法用在&lt;strong&gt;专业提示词与领域知识&lt;/strong&gt;上，而不只是文档页。&lt;/p&gt;
&lt;p&gt;想要现成的、能提升 Agent 在 LangChain 生态任务上表现的技能，可以看 &lt;a href=&quot;https://github.com/langchain-ai/langchain-skills&quot;&gt;LangChain Skills 仓库&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;graph LR
    A[用户] --&amp;gt; B[Agent]
    B --&amp;gt; C[技能 A]
    B --&amp;gt; D[技能 B]
    B --&amp;gt; E[技能 C]
    B --&amp;gt; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① 只有一个 Agent（跟 &lt;code&gt;04&lt;/code&gt; Subagents 的&quot;多个人&quot;完全不同）；② Agent 身边挂着一排技能，&lt;strong&gt;用到哪个才去读哪个&lt;/strong&gt;；③ 回答直接由这个 Agent 给用户（&lt;code&gt;B --&amp;gt; A&lt;/code&gt; 这条回边），没有&quot;主管转述&quot;这一层。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;五个关键特征&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特征&lt;/th&gt;
&lt;th&gt;原文&lt;/th&gt;
&lt;th&gt;说人话&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;提示词驱动的专长&lt;/td&gt;
&lt;td&gt;Prompt-driven specialization：技能主要由&lt;strong&gt;专门的提示词&lt;/strong&gt;定义&lt;/td&gt;
&lt;td&gt;技能本质是&quot;一段很懂行的提示词&quot;，不一定有代码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;渐进式披露&lt;/td&gt;
&lt;td&gt;Progressive disclosure：技能&lt;strong&gt;按上下文或用户需要&lt;/strong&gt;才变得可用&lt;/td&gt;
&lt;td&gt;不用就不加载，省 token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;团队分布式&lt;/td&gt;
&lt;td&gt;Team distribution：不同团队可独立开发和维护技能&lt;/td&gt;
&lt;td&gt;一个技能一个文件/目录，天然适合分包&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;轻量组合&lt;/td&gt;
&lt;td&gt;Lightweight composition：技能比完整的子智能体&lt;strong&gt;简单&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;不用建 Agent、不用管状态，写个提示词就能加能力&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;引用感知&lt;/td&gt;
&lt;td&gt;Reference awareness：技能可以&lt;strong&gt;引用脚本、模板、其它资源&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;手册里能写&quot;需要时去读 &lt;code&gt;references/xxx.md&lt;/code&gt;、跑 &lt;code&gt;scripts/yyy.py&lt;/code&gt;&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：&lt;strong&gt;技能 ≠ 工具&lt;/strong&gt;。工具是&quot;能执行的动作&quot;（查订单、发邮件），技能是&quot;该怎么干这件事的知识&quot;（业务规则、表结构、话术、SOP）。技能往往&lt;strong&gt;告诉 Agent 什么时候该用哪个工具、怎么写参数&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;什么时候用它&lt;/h2&gt;
&lt;p&gt;原文给的三条：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你想要&lt;strong&gt;一个 Agent 拥有很多可能的专长&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;你&lt;strong&gt;不需要在技能之间强制约束&lt;/strong&gt;（技能之间是并列的，不分先后）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不同团队&lt;/strong&gt;需要独立开发各自的能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原文点的三个典型场景：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;技能是什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;编码助手&lt;/td&gt;
&lt;td&gt;不同语言、不同任务（写测试、重构、review）的技能&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;知识库&lt;/td&gt;
&lt;td&gt;不同领域（产品、法务、财务）的技能&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;创作助手&lt;/td&gt;
&lt;td&gt;不同体裁/格式（小红书文案、周报、邮件）的技能&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;反向判据&lt;/strong&gt;：如果你需要&lt;strong&gt;技能之间有顺序约束&lt;/strong&gt;（&quot;必须先做 A 才能做 B&quot;），那应该用 &lt;code&gt;05&lt;/code&gt; Handoffs；如果每个领域大到需要独立上下文和独立工具，那应该用 &lt;code&gt;04&lt;/code&gt; Subagents。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;最小可用实现：一个 &lt;code&gt;load_skill&lt;/code&gt; 工具&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import tool
from langchain.agents import create_agent

@tool
def load_skill(skill_name: str) -&amp;gt; str:
    &quot;&quot;&quot;Load a specialized skill prompt.

    Available skills:
    - write_sql: SQL query writing expert
    - review_legal_doc: Legal document reviewer

    Returns the skill&apos;s prompt and context.
    &quot;&quot;&quot;
    # 从文件/数据库里把技能内容读出来
    ...

agent = create_agent(
    model=&quot;gpt-5.5&quot;,
    tools=[load_skill],
    system_prompt=(
        &quot;You are a helpful assistant. &quot;
        &quot;You have access to two skills: &quot;
        &quot;write_sql and review_legal_doc. &quot;
        &quot;Use load_skill to access them.&quot;
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这段代码在说什么&lt;/strong&gt;：① 全部实现就一个工具 &lt;code&gt;load_skill&lt;/code&gt;；② &lt;strong&gt;系统提示里只放&quot;技能目录&quot;&lt;/strong&gt;（有哪两个技能、各自干什么）——这就是全部的常驻成本；③ 技能正文（可能几千 token）躺在文件或数据库里，&lt;strong&gt;只有真被调用时才进上下文&lt;/strong&gt;；④ 工具的 docstring 里必须写清可用技能清单，否则模型不知道该请求哪个。&lt;/p&gt;
&lt;h3&gt;跟 Subagents 比，差在哪&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;Skills（本篇）&lt;/th&gt;
&lt;th&gt;Subagents（&lt;code&gt;04&lt;/code&gt;）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;有几个 Agent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1 个&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 个主管 + N 个子智能体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;上下文隔离&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;没有&lt;/strong&gt;，技能加载后就在主上下文里&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;强隔离&lt;/strong&gt;，子智能体各跑各的窗口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;谁能跟用户对话&lt;/td&gt;
&lt;td&gt;这个唯一的 Agent&lt;/td&gt;
&lt;td&gt;只有主管&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;要写多少代码&lt;/td&gt;
&lt;td&gt;一个 &lt;code&gt;load_skill&lt;/code&gt; 工具&lt;/td&gt;
&lt;td&gt;每个子智能体一个 &lt;code&gt;create_agent&lt;/code&gt; + 一个 &lt;code&gt;@tool&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;token 账单形状&lt;/td&gt;
&lt;td&gt;加载后&lt;strong&gt;每轮都要重读&lt;/strong&gt;（&lt;code&gt;03&lt;/code&gt; 里的 15K）&lt;/td&gt;
&lt;td&gt;每次调用独立、总量更省&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适合&lt;/td&gt;
&lt;td&gt;知识多但&lt;strong&gt;流程简单&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;领域多、工具多、要隔离&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;用 token 账看&quot;渐进式披露&quot;到底省了什么&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;方案 A：全部塞进系统提示（图省事）
  第 1 轮： 系统提示 200 + 技能A 2000 + 技能B 2000 + 技能C 2000 = 6200 token
  第 2 轮： 再读一遍 6200 token        ← 每轮都在为&quot;用不到的知识&quot;付钱

方案 B：渐进式披露（Skills 模式）
  第 1 轮： 系统提示 200 + 技能目录 100 = 300 token  ──▶ 模型决定加载&quot;技能A&quot;
  第 2 轮： + 技能A 正文 2000         = 2300 token  ──▶ 模型据手册干活
  技能B、技能C： 0 token                            ← 省的就是这部分
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：注意这套账的&lt;strong&gt;代价&lt;/strong&gt;：① 加载技能&lt;strong&gt;多花一次模型调用&lt;/strong&gt;（模型得先决定加载什么）；② 加载过的技能会&lt;strong&gt;留在上下文里&lt;/strong&gt;，后面每一轮都要重读（&lt;code&gt;03&lt;/code&gt; 篇多领域场景里 Skills 的 15K token 就是这么来的）；③ 模型可能&lt;strong&gt;加载错技能&lt;/strong&gt;或忘记加载——所以技能目录的&lt;strong&gt;描述质量&lt;/strong&gt;决定了这个模式成败。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 &lt;strong&gt;官方实战教程&lt;/strong&gt;：&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/skills-sql-assistant&quot;&gt;Build a SQL assistant with on-demand skills&lt;/a&gt;（本笔记的精讲版见 &lt;a href=&quot;/posts/multiagent-11-case-skills/&quot;&gt;&lt;code&gt;11&lt;/code&gt;&lt;/a&gt;）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;三种进阶玩法（原文「Extending the pattern」）&lt;/h2&gt;
&lt;p&gt;写自定义实现时，可以从三个方向扩展基础技能模式：&lt;/p&gt;
&lt;h3&gt;1. 动态注册工具（Dynamic tool registration）&lt;/h3&gt;
&lt;p&gt;把渐进式披露和&lt;strong&gt;状态管理&lt;/strong&gt;结合：&lt;strong&gt;技能加载时顺便注册新工具&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;原文的例子：加载一个 &lt;code&gt;database_admin&lt;/code&gt; 技能，既注入专门上下文，&lt;strong&gt;又注册数据库相关工具&lt;/strong&gt;（backup、restore、migrate）。机制和其它多 Agent 模式一样——&lt;strong&gt;工具改状态，从而动态改变 Agent 的能力&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这条最实用的场景是&lt;strong&gt;权限收敛&lt;/strong&gt;——平时 Agent 手里只有少数安全工具，加载&quot;管理员技能&quot;后才挂上危险工具。危险能力&lt;strong&gt;不是常驻&lt;/strong&gt;的，而是被&quot;技能加载&quot;这个动作显式解锁的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2. 层级技能（Hierarchical skills）&lt;/h3&gt;
&lt;p&gt;技能可以&lt;strong&gt;定义其它技能&lt;/strong&gt;，形成树状结构，产生嵌套专长。&lt;/p&gt;
&lt;p&gt;原文的例子：加载 &lt;code&gt;data_science&lt;/code&gt; 技能后，可以让 &lt;code&gt;pandas_expert&lt;/code&gt;、&lt;code&gt;visualization&lt;/code&gt;、&lt;code&gt;statistical_analysis&lt;/code&gt; 这些&lt;strong&gt;子技能&lt;/strong&gt;变得可用；每个子技能又能被独立按需加载。这种层级做法把庞大的知识库组织成&lt;strong&gt;逻辑分组&lt;/strong&gt;，可以按需发现、按需加载。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：层级技能本质上就是&lt;strong&gt;知识库的目录结构&lt;/strong&gt;——先加载&quot;章&quot;，再按需加载&quot;节&quot;。它解决的是&quot;技能太多、目录本身就很长&quot;的问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3. 引用感知（Reference awareness）&lt;/h3&gt;
&lt;p&gt;原文说得很精炼：&lt;strong&gt;每个技能虽然只有一段提示词，但这段提示词可以引用其它资源的位置，并说明什么时候该去用它们&lt;/strong&gt;。当这些资源变得相关时，Agent 会知道&quot;这些文件存在&quot;，并按需把它们读进内存来完成任务。这同样遵循渐进式披露，限制上下文窗口里的信息量。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文，结合 Deep Agents 的实现）：这就是&quot;技能 = 一个目录 + 一份 &lt;code&gt;SKILL.md&lt;/code&gt; + 若干参考文件/脚本&quot;的由来。&lt;code&gt;SKILL.md&lt;/code&gt; 里写&quot;遇到复杂查询时读 &lt;code&gt;references/query-cookbook.md&lt;/code&gt;、需要批量导入时跑 &lt;code&gt;scripts/import.py&lt;/code&gt;&quot;——&lt;strong&gt;手册里夹着书签&lt;/strong&gt;，Agent 自己决定什么时候翻到那一页。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一句话&lt;/strong&gt;：Skills = &lt;strong&gt;单 Agent + 一个 &lt;code&gt;load_skill&lt;/code&gt; 工具&lt;/strong&gt; + 一堆躺在外部、按需加载的提示词/知识。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;五个特征&lt;/strong&gt;：提示词驱动、渐进式披露、团队分布式、轻量组合、引用感知。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;核心收益&lt;/strong&gt;：常驻上下文里只放&quot;技能目录&quot;，正文&lt;strong&gt;按需付费&lt;/strong&gt;；加能力不用加 Agent。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;核心代价&lt;/strong&gt;：加载多一次调用；加载后每轮重复读；可能加载错——&lt;strong&gt;技能描述就是你的&quot;检索质量&quot;&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;三种进阶&lt;/strong&gt;：动态注册工具（技能解锁工具/权限）、层级技能（技能树）、引用感知（手册里带书签，二级资源再按需读）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;和 Subagents 的关键区别&lt;/strong&gt;：Skills &lt;strong&gt;不隔离上下文&lt;/strong&gt;，它省的是&quot;没用到就别读&quot;；Subagents 隔离上下文，它省的是&quot;总量别累积&quot;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;选它&lt;/strong&gt;：一个 Agent 要覆盖很多并列专长（多语言、多领域、多文体）；不想为每个领域建 Agent；团队要各自贡献能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别选它&lt;/strong&gt;：技能之间有强制顺序（用 &lt;code&gt;05&lt;/code&gt; Handoffs）；每个领域上下文极大或工具极多、需要隔离（用 &lt;code&gt;04&lt;/code&gt; Subagents）；需要先分诊再并行（用 &lt;code&gt;07&lt;/code&gt; Router）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;上一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;05 Handoffs 交接&lt;/a&gt; ｜ &lt;strong&gt;下一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;07 Router 路由&lt;/a&gt; ｜ &lt;strong&gt;实战&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-11-case-skills/&quot;&gt;11 用 Skills 搭 SQL 助手&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>05 Handoffs 交接：靠状态切换行为，像客服转接电话</title><link>https://aicooleo.cn/blog/posts/multiagent-05-handoffs/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-05-handoffs/</guid><description>用一个状态变量（比如 current_step）记住&quot;现在走到哪一步了&quot;，工具调用负责改这个变量，系统每次读它来决定&quot;这一轮该用哪套提示词、哪些工具、甚至换成哪个 Agent&quot;。用户始终在跟&quot;同一个对话框&quot;说话，但背后的行为已经换了人。</description><pubDate>Thu, 27 Aug 2026 20:39:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：&lt;strong&gt;用一个状态变量（比如 &lt;code&gt;current_step&lt;/code&gt;）记住&quot;现在走到哪一步了&quot;，工具调用负责改这个变量，系统每次读它来决定&quot;这一轮该用哪套提示词、哪些工具、甚至换成哪个 Agent&quot;&lt;/strong&gt;。用户始终在跟&quot;同一个对话框&quot;说话，但背后的行为已经换了人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;它到底是什么&lt;/h2&gt;
&lt;p&gt;原文定义：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 &lt;strong&gt;handoffs&lt;/strong&gt; 架构里，行为会&lt;strong&gt;根据状态动态变化&lt;/strong&gt;。核心机制是：&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/tools&quot;&gt;工具&lt;/a&gt;更新一个&lt;strong&gt;跨回合持久化&lt;/strong&gt;的状态变量（例如 &lt;code&gt;current_step&lt;/code&gt; 或 &lt;code&gt;active_agent&lt;/code&gt;），系统读取这个变量来调整行为——要么套用不同的配置（系统提示词、工具集），要么&lt;strong&gt;路由到另一个 &lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/agents&quot;&gt;Agent&lt;/a&gt;&lt;/strong&gt;。这个模式既支持&lt;strong&gt;不同 Agent 之间的交接&lt;/strong&gt;，也支持&lt;strong&gt;同一个 Agent 内部的动态配置切换&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示（原文）&lt;/strong&gt;：&lt;strong&gt;handoffs&lt;/strong&gt; 这个词是 &lt;a href=&quot;https://openai.github.io/openai-agents-python/handoffs/&quot;&gt;OpenAI&lt;/a&gt; 提出的，指的是&lt;strong&gt;用工具调用（例如 &lt;code&gt;transfer_to_sales_agent&lt;/code&gt;）在 Agent 或状态之间转移控制权&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;两句人话：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;传统写法里&quot;换 Agent&quot;要写 &lt;code&gt;if/else&lt;/code&gt; 或画图连线；Handoffs 把这件事变成&lt;strong&gt;模型自己调一个工具&lt;/strong&gt;（&lt;code&gt;transfer_to_sales&lt;/code&gt;），于是&quot;什么时候换人&quot;由模型判断，&quot;换成谁&quot;由状态决定。&lt;/li&gt;
&lt;li&gt;它跟 &lt;code&gt;04&lt;/code&gt; Subagents 最大的区别：&lt;strong&gt;这里是&quot;当前这个 Agent&quot;直接跟用户对话&lt;/strong&gt;，不是&quot;主管汇总后再转述&quot;。用户能感知到&quot;换了个专家在跟我聊&quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;一个完整的交接长什么样&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant U as 用户
    participant A as Agent
    participant S as 工作流状态
    U-&amp;gt;&amp;gt;A: &quot;我手机摔坏了&quot;
    Note over A,S: 步骤：获取保修状态&amp;lt;br/&amp;gt;工具：record_warranty_status
    A--&amp;gt;&amp;gt;U: &quot;设备还在保修期内吗？&quot;
    U-&amp;gt;&amp;gt;A: &quot;在保修期内&quot;
    A-&amp;gt;&amp;gt;S: record_warranty_status(&quot;in_warranty&quot;)
    Note over A,S: 步骤：问题分类&amp;lt;br/&amp;gt;工具：record_issue_type
    A--&amp;gt;&amp;gt;U: &quot;能描述一下具体问题吗？&quot;
    U-&amp;gt;&amp;gt;A: &quot;屏幕裂了&quot;
    A-&amp;gt;&amp;gt;S: record_issue_type(&quot;hardware&quot;)
    Note over A,S: 步骤：给出解决方案&amp;lt;br/&amp;gt;工具：provide_solution、escalate_to_human
    A--&amp;gt;&amp;gt;U: &quot;保修维修流程是这样的…&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① 同一个 Agent 面对用户，但&lt;strong&gt;每一轮的&quot;身份/工具/提示词&quot;都不同&lt;/strong&gt;；② 区别由状态里的&quot;当前步骤&quot;决定（获取保修状态 → 问题分类 → 给出方案）；③ 步骤的推进&lt;strong&gt;靠工具调用写状态&lt;/strong&gt;（&lt;code&gt;record_warranty_status&lt;/code&gt;、&lt;code&gt;record_issue_type&lt;/code&gt;）——不是靠模型&quot;记得&quot;，而是靠状态&lt;strong&gt;锁死&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;四个关键特征&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特征&lt;/th&gt;
&lt;th&gt;原文&lt;/th&gt;
&lt;th&gt;说人话&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;状态驱动行为&lt;/td&gt;
&lt;td&gt;State-driven behavior：行为随状态变量（&lt;code&gt;current_step&lt;/code&gt; / &lt;code&gt;active_agent&lt;/code&gt;）变化&lt;/td&gt;
&lt;td&gt;换人格、换工具、换提示词，都看这一个变量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;工具驱动迁移&lt;/td&gt;
&lt;td&gt;Tool-based transitions：工具更新状态变量以在状态间移动&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;推进流程的主动权在工具手里&lt;/strong&gt;，不靠模型自觉&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;直接与用户交互&lt;/td&gt;
&lt;td&gt;Direct user interaction：每个状态的配置&lt;strong&gt;直接&lt;/strong&gt;处理用户消息&lt;/td&gt;
&lt;td&gt;用户面对的就是&quot;当前这个专家&quot;，不需要主管转述&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;状态持久&lt;/td&gt;
&lt;td&gt;Persistent state：状态跨对话回合存活&lt;/td&gt;
&lt;td&gt;用户中途走开再回来，流程还在原地&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;什么时候用它&lt;/h2&gt;
&lt;p&gt;原文的判断标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你需要&lt;strong&gt;强制顺序约束&lt;/strong&gt;（&quot;只有满足前置条件才解锁能力&quot;）；&lt;/li&gt;
&lt;li&gt;Agent 需要在&lt;strong&gt;不同状态下直接跟用户对话&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;你在做&lt;strong&gt;多阶段会话流程&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原文特别点名了它的主场：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这个模式在&lt;strong&gt;客服场景&lt;/strong&gt;里尤其有价值——你需要按特定顺序收集信息，例如&lt;strong&gt;先拿到保修 ID，才处理退款&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：Handoffs 的本质是&lt;strong&gt;把业务流程的状态机搬进对话&lt;/strong&gt;。判断要不要用它，就问一句：&quot;这件事有没有&apos;必须先 A 再 B&apos;的规定？&quot;有，就用它；没有，Skills（&lt;code&gt;06&lt;/code&gt;）可能更轻。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;最小可用实现：一个工具 + 一个状态字段&lt;/h2&gt;
&lt;p&gt;原文的核心机制：工具返回一个 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/graph-api#command&quot;&gt;&lt;code&gt;Command&lt;/code&gt;&lt;/a&gt; 来更新状态，从而触发&quot;进入新步骤 / 换成新 Agent&quot;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import tool
from langchain.messages import ToolMessage
from langgraph.types import Command

@tool
def transfer_to_specialist(runtime) -&amp;gt; Command:
    &quot;&quot;&quot;Transfer to the specialist agent.&quot;&quot;&quot;
    return Command(
        update={
            &quot;messages&quot;: [
                ToolMessage(  # 必须补一条工具回执，见下方说明
                    content=&quot;Transferred to specialist&quot;,
                    tool_call_id=runtime.tool_call_id,
                )
            ],
            &quot;current_step&quot;: &quot;specialist&quot;,  # ← 这一行就是&quot;交接&quot;：触发行为切换
        }
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;为什么要塞一条 &lt;code&gt;ToolMessage&lt;/code&gt;？（原文的 Note）&lt;/strong&gt;
当 LLM 调用一个工具时，它&lt;strong&gt;期待一个响应&lt;/strong&gt;。带匹配 &lt;code&gt;tool_call_id&lt;/code&gt; 的 &lt;code&gt;ToolMessage&lt;/code&gt; 完成了这次&quot;请求—响应&quot;闭环——&lt;strong&gt;没有它，对话历史就变成畸形（malformed）的&lt;/strong&gt;。只要你的交接工具改了 messages，这条就是必需的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 &lt;strong&gt;官方实战教程&lt;/strong&gt;：&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/handoffs-customer-support&quot;&gt;Build customer support with handoffs&lt;/a&gt;（本笔记的精讲版见 &lt;a href=&quot;/posts/multiagent-10-case-handoffs/&quot;&gt;&lt;code&gt;10&lt;/code&gt;&lt;/a&gt;）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;两种实现路线&lt;/h2&gt;
&lt;p&gt;原文给了两条路：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;路线&lt;/th&gt;
&lt;th&gt;做法&lt;/th&gt;
&lt;th&gt;什么时候选&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;#%E8%B7%AF%E7%BA%BF%E4%B8%80%E5%8D%95-agent--%E4%B8%AD%E9%97%B4%E4%BB%B6%E7%94%A8%E9%85%8D%E7%BD%AE%E5%88%87%E6%8D%A2%E8%A1%8C%E4%B8%BA&quot;&gt;&lt;strong&gt;单 Agent + 中间件&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;一个 Agent，靠中间件在每次模型调用前动态改系统提示词和工具集&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;绝大多数场景&lt;/strong&gt;（原文推荐：更简单）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;#%E8%B7%AF%E7%BA%BF%E4%BA%8C%E5%A4%9A-agent-%E5%AD%90%E5%9B%BE%E7%94%A8-commandparent-%E6%8D%A2%E8%8A%82%E7%82%B9&quot;&gt;&lt;strong&gt;多 Agent 子图&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;多个独立 Agent 作为图里的不同节点，交接工具用 &lt;code&gt;Command.PARENT&lt;/code&gt; 指定下一个执行哪个节点&lt;/td&gt;
&lt;td&gt;需要&lt;strong&gt;定制的 Agent 实现&lt;/strong&gt;（例如某个节点本身就是带反思/检索的复杂图）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;路线一：单 Agent + 中间件（用配置切换行为）&lt;/h3&gt;
&lt;p&gt;机制（原文）：&lt;strong&gt;一个 Agent 根据状态改变行为&lt;/strong&gt;。中间件拦截每次模型调用，动态调整系统提示词与可用工具；工具则更新状态变量以触发迁移。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import ToolRuntime, tool
from langchain.messages import ToolMessage
from langgraph.types import Command

@tool
def record_warranty_status(status: str, runtime: ToolRuntime[None, SupportState]) -&amp;gt; Command:
    &quot;&quot;&quot;Record warranty status and transition to next step.&quot;&quot;&quot;
    return Command(
        update={
            &quot;messages&quot;: [
                ToolMessage(
                    content=f&quot;Warranty status recorded: {status}&quot;,
                    tool_call_id=runtime.tool_call_id,
                )
            ],
            &quot;warranty_status&quot;: status,
            &quot;current_step&quot;: &quot;specialist&quot;,  # 更新状态 → 触发进入下一步
        }
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;完整示例：用中间件做客服（原文，已精简）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;from typing import Callable
from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from langchain.tools import tool, ToolRuntime
from langchain.messages import ToolMessage
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command

# 1) 定义带 current_step 的状态
class SupportState(AgentState):
    &quot;&quot;&quot;记录当前处于哪一步。&quot;&quot;&quot;
    current_step: str = &quot;triage&quot;
    warranty_status: str | None = None

# 2) 工具通过 Command 更新 current_step
@tool
def record_warranty_status(status: str, runtime: ToolRuntime[None, SupportState]) -&amp;gt; Command:
    &quot;&quot;&quot;Record warranty status and transition to next step.&quot;&quot;&quot;
    return Command(update={
        &quot;messages&quot;: [
            ToolMessage(
                content=f&quot;Warranty status recorded: {status}&quot;,
                tool_call_id=runtime.tool_call_id,
            )
        ],
        &quot;warranty_status&quot;: status,
        &quot;current_step&quot;: &quot;specialist&quot;,   # ← 迁移到下一步
    })

# 3) 中间件：按 current_step 套用不同配置（提示词 + 工具集）
@wrap_model_call
def apply_step_config(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -&amp;gt; ModelResponse:
    &quot;&quot;&quot;根据 current_step 配置 Agent 行为。&quot;&quot;&quot;
    step = request.state.get(&quot;current_step&quot;, &quot;triage&quot;)

    configs = {
        &quot;triage&quot;: {
            &quot;prompt&quot;: &quot;Collect warranty information...&quot;,
            &quot;tools&quot;: [record_warranty_status],
        },
        &quot;specialist&quot;: {
            &quot;prompt&quot;: &quot;Provide solutions based on warranty: {warranty_status}&quot;,
            &quot;tools&quot;: [provide_solution, escalate],
        },
    }

    config = configs[step]
    request = request.override(                      # 改写这次模型调用的配置
        system_prompt=config[&quot;prompt&quot;].format(**request.state),
        tools=config[&quot;tools&quot;],
    )
    return handler(request)

# 4) 创建带中间件的 Agent
agent = create_agent(
    model,
    tools=[record_warranty_status, provide_solution, escalate],
    state_schema=SupportState,
    middleware=[apply_step_config],
    checkpointer=InMemorySaver(),   # 关键：状态要跨回合持久化
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这段代码在说什么&lt;/strong&gt;：① 状态里加一个 &lt;code&gt;current_step&lt;/code&gt;（初始 &lt;code&gt;triage&lt;/code&gt;）；② 工具通过 &lt;code&gt;Command&lt;/code&gt; 改状态 → 相当于&quot;按下流程按钮&quot;；③ 中间件&lt;strong&gt;每次模型调用前&lt;/strong&gt;读 &lt;code&gt;current_step&lt;/code&gt;，从 &lt;code&gt;configs&lt;/code&gt; 表里取出这一步该用的提示词和工具，用 &lt;code&gt;request.override(...)&lt;/code&gt; 换掉；④ &lt;code&gt;checkpointer&lt;/code&gt; 负责让状态跨回合活着——&lt;strong&gt;没有它，下一轮就&quot;失忆&quot;回到 triage&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这段代码最值得学的是**&quot;行为配置表&quot;这个结构**——&lt;code&gt;configs = {步骤: {prompt, tools}}&lt;/code&gt;。它把&quot;流程&quot;变成了数据，加一步就是加一个字典条目，不用改控制流。这比一堆 &lt;code&gt;if step == ...&lt;/code&gt; 清晰得多。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;路线二：多 Agent 子图（用 &lt;code&gt;Command.PARENT&lt;/code&gt; 换节点）&lt;/h3&gt;
&lt;p&gt;机制（原文）：&lt;strong&gt;多个独立 Agent 作为图里的不同节点&lt;/strong&gt;。交接工具用 &lt;code&gt;Command.PARENT&lt;/code&gt; 指定下一个该执行哪个节点。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;警告（原文）&lt;/strong&gt;：子图式交接需要仔细做&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/context-engineering&quot;&gt;上下文工程&lt;/a&gt;。不同于单 Agent 中间件（消息历史自然流动），你&lt;strong&gt;必须显式决定哪些消息在 Agent 之间传递&lt;/strong&gt;。搞错了，接收方会拿到&lt;strong&gt;畸形的对话历史或膨胀的上下文&lt;/strong&gt;。见下文「上下文工程」。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;from langchain.messages import AIMessage, ToolMessage
from langchain.tools import tool, ToolRuntime
from langgraph.types import Command

@tool
def transfer_to_sales(runtime: ToolRuntime) -&amp;gt; Command:
    &quot;&quot;&quot;Transfer to the sales agent.&quot;&quot;&quot;
    # 1) 找到触发本次交接的那条 AIMessage
    last_ai_message = next(
        msg for msg in reversed(runtime.state[&quot;messages&quot;]) if isinstance(msg, AIMessage)
    )
    # 2) 造一条&quot;交接回执&quot;，把这次工具调用闭环
    transfer_message = ToolMessage(
        content=&quot;Transferred to sales agent&quot;,
        tool_call_id=runtime.tool_call_id,
    )
    return Command(
        goto=&quot;sales_agent&quot;,              # 下一个节点
        update={
            &quot;active_agent&quot;: &quot;sales_agent&quot;,
            &quot;messages&quot;: [last_ai_message, transfer_message],  # 只带这两条，别带全量历史
        },
        graph=Command.PARENT,            # 告诉父图：跳到另一个节点
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;完整示例：销售 / 支持双 Agent（原文，已精简骨架）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;from typing import Literal
from langchain.agents import AgentState, create_agent
from langchain.messages import AIMessage, ToolMessage
from langchain.tools import tool, ToolRuntime
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command
from typing_extensions import NotRequired

# 1) 状态里记录&quot;当前活跃的是哪个 Agent&quot;
class MultiAgentState(AgentState):
    active_agent: NotRequired[str]

# 2) 两个方向的交接工具（transfer_to_sales / transfer_to_support）
#    实现同上：取 AIMessage + 造 ToolMessage + goto + Command.PARENT

# 3) 各自带&quot;换人&quot;工具的 Agent
sales_agent = create_agent(
    model=&quot;google_genai:gemini-3.6-flash&quot;,
    tools=[transfer_to_support],
    system_prompt=&quot;You are a sales agent... If asked about technical issues, transfer to the support agent.&quot;,
)
support_agent = create_agent(
    model=&quot;google_genai:gemini-3.6-flash&quot;,
    tools=[transfer_to_sales],
    system_prompt=&quot;You are a support agent... If asked about pricing, transfer to the sales agent.&quot;,
)

# 4) 每个 Agent 一个节点
def call_sales_agent(state: MultiAgentState) -&amp;gt; Command:
    return sales_agent.invoke(state)

def call_support_agent(state: MultiAgentState) -&amp;gt; Command:
    return support_agent.invoke(state)

# 5) 路由：最后一条 AIMessage 没有工具调用 → 结束；否则回到 active_agent
def route_after_agent(state: MultiAgentState) -&amp;gt; Literal[&quot;sales_agent&quot;, &quot;support_agent&quot;, &quot;__end__&quot;]:
    messages = state.get(&quot;messages&quot;, [])
    if messages:
        last_msg = messages[-1]
        if isinstance(last_msg, AIMessage) and not last_msg.tool_calls:
            return &quot;__end__&quot;        # 没有待办工具调用 = 这一轮说完了
    active = state.get(&quot;active_agent&quot;, &quot;sales_agent&quot;)
    return active if active else &quot;sales_agent&quot;

def route_initial(state: MultiAgentState) -&amp;gt; Literal[&quot;sales_agent&quot;, &quot;support_agent&quot;]:
    return state.get(&quot;active_agent&quot;) or &quot;sales_agent&quot;

# 6) 组图
builder = StateGraph(MultiAgentState)
builder.add_node(&quot;sales_agent&quot;, call_sales_agent)
builder.add_node(&quot;support_agent&quot;, call_support_agent)
builder.add_conditional_edges(START, route_initial, [&quot;sales_agent&quot;, &quot;support_agent&quot;])
builder.add_conditional_edges(&quot;sales_agent&quot;, route_after_agent, [&quot;sales_agent&quot;, &quot;support_agent&quot;, END])
builder.add_conditional_edges(&quot;support_agent&quot;, route_after_agent, [&quot;sales_agent&quot;, &quot;support_agent&quot;, END])
graph = builder.compile()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这段代码在说什么&lt;/strong&gt;：① 状态里的 &lt;code&gt;active_agent&lt;/code&gt; 是&quot;现在轮到谁&quot;；② 每个 Agent 的工具里都装着&quot;转给对面&quot;的交接工具；③ 交接工具用 &lt;code&gt;goto&lt;/code&gt; 直接跳到另一个节点（&lt;code&gt;graph=Command.PARENT&lt;/code&gt; 让父图接管跳转）；④ &lt;code&gt;route_after_agent&lt;/code&gt; 负责判断&quot;这一轮说完了就结束，否则继续活跃 Agent&quot;——&lt;strong&gt;这段路由逻辑是双 Agent 模式的&quot;传送带&quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示（原文，强烈建议先看这条）&lt;/strong&gt;：
&lt;strong&gt;大多数 handoffs 场景用「单 Agent + 中间件」就够了——它更简单&lt;/strong&gt;。只有当你需要&lt;strong&gt;定制的 Agent 实现&lt;/strong&gt;时（比如某个节点本身就是一个带反思或检索步骤的复杂图），才上「多 Agent 子图」。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;两种路线对比（补充解读）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;【单 Agent + 中间件】一个 Agent，像&quot;演员换戏服&quot;
用户 ──▶ Agent（state.current_step = triage）
             │ 工具写状态
             ▼
         Agent（读到 current_step = specialist）
             │ 中间件自动换上 specialist 的提示词 + 工具
             ▼
         同一个 Agent 继续跟用户聊   ← 消息历史天然连续，不用手工搬

【多 Agent 子图】多个 Agent，像&quot;接力棒&quot;
用户 ──▶ sales_agent 节点
             │ 调 transfer_to_support 工具
             ▼  Command(goto=&quot;support_agent&quot;, graph=Command.PARENT)
         support_agent 节点
             │ ← 只拿到 [AIMessage(工具调用), ToolMessage(交接回执)] 两条
             ▼
         继续对话   ← 消息历史要你手工挑，挑错就畸形
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;上下文工程（子图交接的关键）&lt;/h2&gt;
&lt;p&gt;原文用了一整节讲这件事，因为&lt;strong&gt;这里是新手最容易翻车的地方&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;交接时怎么处理上下文&lt;/h3&gt;
&lt;p&gt;在 Agent 之间交接时，你必须保证&lt;strong&gt;对话历史依然合法&lt;/strong&gt;。LLM 期待&quot;工具调用&quot;和&quot;工具响应&quot;成对出现，所以用 &lt;code&gt;Command.PARENT&lt;/code&gt; 交接时，必须&lt;strong&gt;同时带上两样东西&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;包含工具调用的那条 &lt;code&gt;AIMessage&lt;/code&gt;&lt;/strong&gt;（触发本次交接的消息）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一条确认交接的 &lt;code&gt;ToolMessage&lt;/code&gt;&lt;/strong&gt;（这次工具调用的人工响应）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;少了这个配对，接收方会看到一段不完整的对话，可能报错或产生意料之外的行为。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下面的例子假设&lt;strong&gt;只调用了交接工具&lt;/strong&gt;（没有并行工具调用）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@tool
def transfer_to_sales(runtime: ToolRuntime) -&amp;gt; Command:
    # 取触发本次交接的 AI 消息
    last_ai_message = runtime.state[&quot;messages&quot;][-1]

    # 造一条人工工具响应，补全配对
    transfer_message = ToolMessage(
        content=&quot;Transferred to sales agent&quot;,
        tool_call_id=runtime.tool_call_id,
    )

    return Command(
        goto=&quot;sales_agent&quot;,
        update={
            &quot;active_agent&quot;: &quot;sales_agent&quot;,
            # 只传这两条，不要传子智能体的全部历史
            &quot;messages&quot;: [last_ai_message, transfer_message],
        },
        graph=Command.PARENT,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;为什么不把子智能体的所有消息都传过去？（原文的 Note）&lt;/strong&gt;
你确实可以把子智能体的完整对话塞进交接里，但这通常会&lt;strong&gt;制造问题&lt;/strong&gt;：接收方会被无关的内部推理搞糊涂，token 成本也无谓上升。&lt;strong&gt;只传&quot;交接那一对&quot;消息&lt;/strong&gt;，可以让父图的上下文保持聚焦在高层协调上。如果接收方确实需要更多背景，&lt;strong&gt;考虑在 &lt;code&gt;ToolMessage&lt;/code&gt; 的内容里给子智能体的工作做个总结&lt;/strong&gt;，而不是把原始消息历史整包递过去。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;把控制权交回用户&lt;/h3&gt;
&lt;p&gt;原文的一条硬要求：&lt;strong&gt;当你要把控制权交回用户时（结束这个 Agent 的回合），确保最后一条消息是 &lt;code&gt;AIMessage&lt;/code&gt;&lt;/strong&gt;。这既保持了对话历史的合法性，也向用户界面&lt;strong&gt;发出&quot;Agent 干完了&quot;的信号&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这条看起来琐碎，实际上是&lt;strong&gt;前端体验的开关&lt;/strong&gt;——很多 UI 是靠&quot;最后一条是 AI 消息且没有工具调用&quot;来判断&quot;可以显示输入框了&quot;。你如果结束回合时最后一条是 &lt;code&gt;ToolMessage&lt;/code&gt;，前端会一直转圈等下一轮。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;落地时要考虑的几件事&lt;/h2&gt;
&lt;p&gt;原文列的三条设计考量：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;考量&lt;/th&gt;
&lt;th&gt;原文&lt;/th&gt;
&lt;th&gt;说人话&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;上下文过滤策略&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;每个 Agent 收到完整历史、过滤后的片段，还是摘要？不同角色的 Agent 可能需要不同上下文&lt;/td&gt;
&lt;td&gt;交接时&lt;strong&gt;别偷懒整包转发&lt;/strong&gt;，否则又贵又乱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;工具的语义边界&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;交接工具只更新路由状态，还是&lt;strong&gt;也产生副作用&lt;/strong&gt;？例如 &lt;code&gt;transfer_to_sales()&lt;/code&gt; 是否&lt;strong&gt;还要&lt;/strong&gt;建一张支持工单，还是那应该是另一个动作&lt;/td&gt;
&lt;td&gt;一个工具只干一件事，别把&quot;转接&quot;和&quot;建单&quot;揉在一起&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Token 效率&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;在&quot;上下文完整&quot;和&quot;token 成本&quot;之间权衡；对话越长，摘要和选择性传递越重要&lt;/td&gt;
&lt;td&gt;长会话必须有裁剪/摘要策略（参考 &lt;code&gt;03&lt;/code&gt; 的账）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;机制一句话&lt;/strong&gt;：工具返回 &lt;code&gt;Command&lt;/code&gt; &lt;strong&gt;改状态&lt;/strong&gt;（&lt;code&gt;current_step&lt;/code&gt; / &lt;code&gt;active_agent&lt;/code&gt;）→ 系统读状态&lt;strong&gt;换配置或换 Agent&lt;/strong&gt;；状态跨回合持久。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;四个特征&lt;/strong&gt;：状态驱动行为、工具驱动迁移、直接与用户交互、状态持久。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;必须补 &lt;code&gt;ToolMessage&lt;/code&gt;&lt;/strong&gt;：只要交接工具改了 messages，就要用匹配的 &lt;code&gt;tool_call_id&lt;/code&gt; 闭环，否则对话历史畸形。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优先选单 Agent + 中间件&lt;/strong&gt;：&lt;code&gt;configs = {步骤: {prompt, tools}}&lt;/code&gt; + &lt;code&gt;wrap_model_call&lt;/code&gt; + &lt;code&gt;checkpointer&lt;/code&gt; 三板斧；只有需要定制 Agent 实现时才用多 Agent 子图。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子图交接要手工管上下文&lt;/strong&gt;：交接时必须带上 &lt;code&gt;AIMessage&lt;/code&gt;（工具调用）+ &lt;code&gt;ToolMessage&lt;/code&gt;（交接回执）这一对，&lt;strong&gt;只传这一对&lt;/strong&gt;，不要整包子智能体历史；结束回合时最后一条必须是 &lt;code&gt;AIMessage&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;它最擅长的事&lt;/strong&gt;：强制顺序约束 + 多阶段会话（客服：先保修、再分类、再方案）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;选它&lt;/strong&gt;：流程有&quot;必须先 A 再 B&quot;的硬约束；用户要跨阶段持续对话；客服/工单/审批这类多阶段会话。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别选它&lt;/strong&gt;：需要并行处理多个领域（用 &lt;code&gt;04&lt;/code&gt; Subagents 或 &lt;code&gt;07&lt;/code&gt; Router）；多领域大上下文（Handoffs 串行，&lt;code&gt;03&lt;/code&gt; 里它是下策）；只是想让单个 Agent 按需加载知识（用 &lt;code&gt;06&lt;/code&gt; Skills）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;上一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;04 Subagents 子智能体&lt;/a&gt; ｜ &lt;strong&gt;下一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-06-skills/&quot;&gt;06 Skills 技能&lt;/a&gt; ｜ &lt;strong&gt;实战&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-10-case-handoffs/&quot;&gt;10 用 Handoffs 搭客服工单流转&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>04 Subagents 子智能体：主管派活，专家干活</title><link>https://aicooleo.cn/blog/posts/multiagent-04-subagents/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-04-subagents/</guid><description>主管 Agent（supervisor）手里有一份&quot;外包名单&quot;，每个专家（子智能体）被包装成一个工具。主管决定派谁、给什么输入、怎么合并结果；专家干完把结果交回主管，由主管对用户说话。这是最通用、最好理解的一种多 Agent 打法。</description><pubDate>Thu, 27 Aug 2026 10:48:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：&lt;strong&gt;主管 Agent（supervisor）手里有一份&quot;外包名单&quot;，每个专家（子智能体）被包装成一个工具&lt;/strong&gt;。主管决定派谁、给什么输入、怎么合并结果；专家干完把结果交回主管，由主管对用户说话。这是最通用、最好理解的一种多 Agent 打法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;它到底是什么&lt;/h2&gt;
&lt;p&gt;原文定义：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 &lt;strong&gt;subagents&lt;/strong&gt; 架构里，一个中心的&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/agents&quot;&gt;主 Agent&lt;/a&gt;（常被称为 &lt;strong&gt;supervisor 主管&lt;/strong&gt;）通过&lt;strong&gt;把子智能体当&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/tools&quot;&gt;工具&lt;/a&gt;调用&lt;/strong&gt;来协调它们。主 Agent 决定调哪个子智能体、给什么输入、怎么合并结果。&lt;strong&gt;默认情况下子智能体是无状态的&lt;/strong&gt;——它们不记得过去的交互，所有对话记忆都由主 Agent 维护。这带来了&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/context-engineering&quot;&gt;上下文&lt;/a&gt;隔离：每次子智能体调用都跑在一个干净的上下文窗口里，避免主对话的上下文膨胀。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;两句人话补充：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无状态不是缺陷，是设计&lt;/strong&gt;：子智能体每次调用都是&quot;新人报到&quot;，只拿到本次任务描述。这样它不会被主对话里的杂事污染——这正是 &lt;code&gt;03&lt;/code&gt; 篇里 Subagents token 最省的原因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;但也可以&quot;带着上下文&quot;去&lt;/strong&gt;：如果子智能体需要接着主 Agent 已做的工作干（比如接着改一个没修完的 bug），你可以把主 Agent 的历史消息传进去，见下文「子智能体输入」。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 &lt;strong&gt;想要内置实现&lt;/strong&gt;：Deep Agents 自带 subagents 支持，见 &lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/subagents&quot;&gt;Deep Agents / Subagents&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;graph LR
    A[用户] --&amp;gt; B[主管 Agent]
    B --&amp;gt; C[子智能体 A]
    B --&amp;gt; D[子智能体 B]
    B --&amp;gt; E[子智能体 C]
    C --&amp;gt; B
    D --&amp;gt; B
    E --&amp;gt; B
    B --&amp;gt; F[回复用户]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① 用户的每一句话都只进主管；② 主管按需要把一个或多个任务&lt;strong&gt;分发&lt;/strong&gt;给子智能体（A/B/C 互不来往）；③ 每个子智能体的结果&lt;strong&gt;必须回到主管&lt;/strong&gt;（C→B、D→B、E→B 这三条回边是这个模式的灵魂）；④ 最后由主管统一回复用户。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;五个关键特征&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特征&lt;/th&gt;
&lt;th&gt;原文&lt;/th&gt;
&lt;th&gt;说人话&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;集中式控制&lt;/td&gt;
&lt;td&gt;Centralized control：所有路由都经过主 Agent&lt;/td&gt;
&lt;td&gt;主管是唯一入口和唯一出口，好管控、好审计&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;不与用户直接交互&lt;/td&gt;
&lt;td&gt;No direct user interaction：子智能体把结果返回主 Agent，而不是给用户&lt;/td&gt;
&lt;td&gt;用户感知不到&quot;背后有几个专家&quot;；&lt;strong&gt;但&lt;/strong&gt;可以在子智能体内部用 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/interrupts#pause-using-interrupt&quot;&gt;interrupt&lt;/a&gt; 让用户参与&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;子智能体即工具&lt;/td&gt;
&lt;td&gt;Subagents via tools：子智能体是通过工具被调用的&lt;/td&gt;
&lt;td&gt;这句话是全部实现的核心：&lt;strong&gt;包装成 &lt;code&gt;@tool&lt;/code&gt; 就是一个子智能体&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可并行执行&lt;/td&gt;
&lt;td&gt;Parallel execution：主 Agent 可以在同一个回合里调多个子智能体&lt;/td&gt;
&lt;td&gt;一次问三个领域不需要串行等三次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;无状态（默认）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;（原文正文）由主 Agent 维护全部对话记忆&lt;/td&gt;
&lt;td&gt;隔离强、成本稳定，代价是每次都重新推导&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;主管 ≠ 路由（原文的 Note）&lt;/strong&gt;：
&lt;strong&gt;主管 Agent&lt;/strong&gt; 是一个&lt;strong&gt;完整的 Agent&lt;/strong&gt;——它维护对话上下文，并在&lt;strong&gt;多个回合&lt;/strong&gt;里动态决定该调哪些子智能体。
&lt;strong&gt;Router 路由&lt;/strong&gt;通常只是&lt;strong&gt;一次分类步骤&lt;/strong&gt;，把请求分发出去，不维护持续的对话状态。
一句话：&lt;strong&gt;主管会聊天、会记事儿、会连续决策；路由只负责&quot;看一眼、指个路&quot;&lt;/strong&gt;（详见 &lt;code&gt;07&lt;/code&gt; 篇）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;什么时候用它&lt;/h2&gt;
&lt;p&gt;原文给的判断标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你手上有&lt;strong&gt;多个互不相同的领域&lt;/strong&gt;（比如日历、邮件、CRM、数据库）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子智能体不需要直接跟用户对话&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;你想要&lt;strong&gt;集中式的工作流控制&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以及一句很重要的&quot;反向提醒&quot;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果只是少量&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/tools&quot;&gt;工具&lt;/a&gt;的简单场景，&lt;strong&gt;用一个&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/agents&quot;&gt;单 Agent&lt;/a&gt; 就够了&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示（原文）&lt;/strong&gt;：&lt;strong&gt;需要在子智能体里跟用户交互怎么办？&lt;/strong&gt; 子智能体通常把结果返回主管而不是直接跟用户对话，但你可以在子智能体内部用 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/interrupts#pause-using-interrupt&quot;&gt;interrupt&lt;/a&gt; &lt;strong&gt;暂停执行、收集用户输入&lt;/strong&gt;。适用于子智能体需要澄清问题或要审批才能继续的场景——主管仍是指挥者，但子智能体可以在任务中途向用户要信息。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;最小可用实现：三行核心思想&lt;/h2&gt;
&lt;p&gt;原文的&quot;核心机制&quot;就一段代码——&lt;strong&gt;把子智能体包成工具&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import tool
from langchain.agents import create_agent

# 1) 先建一个子智能体（它自己也是一个完整的 Agent）
subagent = create_agent(model=&quot;google_genai:gemini-3.6-flash&quot;, tools=[...])

# 2) 把它包成一个工具：工具名 + 描述就是给主管看的&quot;外包说明&quot;
@tool(&quot;research&quot;, description=&quot;Research a topic and return findings&quot;)
def call_research_agent(query: str):
    result = subagent.invoke({&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: query}]})
    return result[&quot;messages&quot;][-1].content   # 只把最终结论交回主管

# 3) 主管 Agent：工具清单里挂着这个子智能体
main_agent = create_agent(model=&quot;google_genai:gemini-3.6-flash&quot;, tools=[call_research_agent])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这段代码在说什么&lt;/strong&gt;：① 子智能体本身就是 &lt;code&gt;create_agent&lt;/code&gt; 造出来的普通 Agent；② &lt;code&gt;@tool&lt;/code&gt; 装饰器把它变成主管可调用的工具，&lt;strong&gt;工具名和描述是主管决策的唯一依据&lt;/strong&gt;（所以别起 &lt;code&gt;helper&lt;/code&gt;、&lt;code&gt;do_stuff&lt;/code&gt; 这种名字）；③ 只把 &lt;code&gt;messages[-1].content&lt;/code&gt;（最终回答）返回给主管——中间的工具调用、推理过程都被&quot;关在&quot;子智能体的上下文里。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 &lt;strong&gt;官方实战教程&lt;/strong&gt;：&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/subagents-personal-assistant&quot;&gt;Build a personal assistant with subagents&lt;/a&gt;（本笔记的精讲版见 &lt;a href=&quot;/posts/multiagent-09-case-subagents/&quot;&gt;&lt;code&gt;09&lt;/code&gt;&lt;/a&gt;）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;五个要拿主意的设计决策&lt;/h2&gt;
&lt;p&gt;原文把实现 Subagents 时的选择整理成一张表，下面逐项展开：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;决策点&lt;/th&gt;
&lt;th&gt;可选方案&lt;/th&gt;
&lt;th&gt;一句话建议&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;同步 vs 异步&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;同步（阻塞）vs 异步（后台任务）&lt;/td&gt;
&lt;td&gt;主管&lt;strong&gt;需要结果才能继续&lt;/strong&gt; → 同步；用户&lt;strong&gt;不该干等&lt;/strong&gt; → 异步&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;工具模式&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;一智能体一工具 vs 单一派发工具&lt;/td&gt;
&lt;td&gt;要精细控制 → 一智能体一工具；要&quot;约定优于配置&quot; → 单一 &lt;code&gt;task&lt;/code&gt; 工具&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;子智能体规格&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;系统提示枚举 vs Enum 约束 vs 工具化发现&lt;/td&gt;
&lt;td&gt;少于 10 个用前两种；多/动态用第三种&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;子智能体输入&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;只给任务描述 vs 给完整上下文&lt;/td&gt;
&lt;td&gt;默认隔离；要&quot;接着干&quot;就给历史&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;子智能体输出&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;只回结果 vs 回完整会话历史&lt;/td&gt;
&lt;td&gt;默认回精简结果；需要额外状态用 &lt;code&gt;Command&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;决策一：同步还是异步&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方式&lt;/th&gt;
&lt;th&gt;主管的行为&lt;/th&gt;
&lt;th&gt;最适合&lt;/th&gt;
&lt;th&gt;代价&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;同步 Sync&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;等子智能体做完才继续&lt;/td&gt;
&lt;td&gt;主管需要结果才能往下走&lt;/td&gt;
&lt;td&gt;简单，但会把对话&lt;strong&gt;卡住&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;异步 Async&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;子智能体在后台跑，主管继续聊&lt;/td&gt;
&lt;td&gt;独立任务，用户不该干等&lt;/td&gt;
&lt;td&gt;响应快，但实现更复杂&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示（原文）&lt;/strong&gt;：别和 Python 的 &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; 搞混。这里的&quot;异步&quot;指&lt;strong&gt;主管启动一个后台作业（通常跑在另一个进程或服务里）然后不阻塞地继续&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;同步（默认行为）&lt;/h3&gt;
&lt;p&gt;默认情况下子智能体调用是同步的：主管等每个子智能体做完才继续。&lt;strong&gt;当下一个动作依赖子智能体结果时，用同步。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant U as 用户
    participant M as 主管 Agent
    participant R as 调研子智能体
    U-&amp;gt;&amp;gt;M: &quot;东京天气怎么样？&quot;
    M-&amp;gt;&amp;gt;R: research(&quot;东京天气&quot;)
    Note over M: 阻塞等待结果…
    R--&amp;gt;&amp;gt;M: &quot;当前 72°F，晴&quot;
    M--&amp;gt;&amp;gt;U: &quot;东京现在 72°F，晴天&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① 主管把任务交给调研子智能体后&lt;strong&gt;什么都不做，就等&lt;/strong&gt;；② 子智能体返回结论；③ 主管再基于结论回答用户。用户在这段时间里看不到任何东西。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;什么时候用同步（原文）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主管需要子智能体的结果才能组织回答；&lt;/li&gt;
&lt;li&gt;任务之间有&lt;strong&gt;顺序依赖&lt;/strong&gt;（例如：取数据 → 分析 → 回答）；&lt;/li&gt;
&lt;li&gt;子智能体失败时&lt;strong&gt;应该&lt;/strong&gt;阻塞主管的回答（比如涉及合规、金额）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代价（原文）&lt;/strong&gt;：实现简单（调了就等）；但&lt;strong&gt;所有子智能体做完之前用户看不到任何响应&lt;/strong&gt;；长任务会把对话冻住。&lt;/p&gt;
&lt;h3&gt;异步（后台作业）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;当子智能体的工作与主对话流程相互独立&lt;/strong&gt;时用异步：主管甩出一个后台任务，自己继续陪用户聊。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant U as 用户
    participant M as 主管 Agent
    participant J as 作业系统
    participant C as 合同审查子智能体
    U-&amp;gt;&amp;gt;M: &quot;帮我审一下这份并购合同&quot;
    M-&amp;gt;&amp;gt;J: run_agent(&quot;legal_reviewer&quot;, task)
    J-&amp;gt;&amp;gt;C: 启动 Agent
    J--&amp;gt;&amp;gt;M: job_id: &quot;job_123&quot;
    M--&amp;gt;&amp;gt;U: &quot;已开始审查（job_123）&quot;
    Note over C: 正在审 150+ 页…
    U-&amp;gt;&amp;gt;M: &quot;进度怎么样？&quot;
    M-&amp;gt;&amp;gt;J: check_status(job_id)
    J--&amp;gt;&amp;gt;M: &quot;running&quot;
    M--&amp;gt;&amp;gt;U: &quot;还在审…&quot;
    U-&amp;gt;&amp;gt;M: &quot;好了吗？&quot;
    M-&amp;gt;&amp;gt;J: check_status(job_id)
    J--&amp;gt;&amp;gt;M: &quot;completed&quot;
    M-&amp;gt;&amp;gt;J: get_result(job_id)
    J--&amp;gt;&amp;gt;M: 合同分析结果
    M--&amp;gt;&amp;gt;U: &quot;审查完成：[结论]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① 主管&lt;strong&gt;不等待&lt;/strong&gt;，立刻给用户一个作业号；② 用户随时可以问进度；③ 作业完成后主管取结果再汇报。&lt;strong&gt;用户全程不被阻塞&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;什么时候用异步（原文）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;子智能体的工作与主对话流程&lt;strong&gt;相互独立&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;用户应该能在后台干活时&lt;strong&gt;继续聊天&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;你想并行跑多个独立任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&quot;三工具&quot;模式（原文）&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start job（启动）&lt;/strong&gt;：踢出后台任务，返回一个 job ID；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check status（查状态）&lt;/strong&gt;：返回当前状态（pending / running / completed / failed）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get result（取结果）&lt;/strong&gt;：取回已完成的结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;作业完成后怎么通知用户（原文）&lt;/strong&gt;：一种做法是&lt;strong&gt;弹一条通知&lt;/strong&gt;，用户点击后向 Agent 发一条 &lt;code&gt;HumanMessage&lt;/code&gt;，例如&quot;检查 job_123 并总结结果&quot;。这样&quot;推&quot;就变成了&quot;拉&quot;，不需要 Agent 主动打断用户。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;决策二：工具模式——一智能体一工具，还是一个大 &lt;code&gt;task&lt;/code&gt; 工具&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;最适合&lt;/th&gt;
&lt;th&gt;代价&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;#%E4%B8%80%E6%99%BA%E8%83%BD%E4%BD%93%E4%B8%80%E5%B7%A5%E5%85%B7&quot;&gt;&lt;strong&gt;一智能体一工具&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;需要对每个子智能体的输入输出做&lt;strong&gt;精细控制&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;配置更多，但更可定制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;#%E5%8D%95%E4%B8%80%E6%B4%BE%E5%8F%91%E5%B7%A5%E5%85%B7task-%E6%A8%A1%E5%BC%8F&quot;&gt;&lt;strong&gt;单一派发工具&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;智能体很多、团队分布式开发、&quot;约定优于配置&quot;&lt;/td&gt;
&lt;td&gt;组合更简单，但每个子智能体可定制的空间更小&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;一智能体一工具&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;graph LR
    A[用户] --&amp;gt; B[主管 Agent]
    B --&amp;gt; C[子智能体 A]
    B --&amp;gt; D[子智能体 B]
    B --&amp;gt; E[子智能体 C]
    C --&amp;gt; B
    D --&amp;gt; B
    E --&amp;gt; B
    B --&amp;gt; F[回复用户]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：主管的工具清单里&lt;strong&gt;平铺着三个工具&lt;/strong&gt;，每个工具对应一个子智能体，各自可以有自己独立的入参和返回值设计。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import tool
from langchain.agents import create_agent

# 每个子智能体 = 一个独立工具（入参/返回值都能单独设计）
subagent = create_agent(model=&quot;...&quot;, tools=[...])

@tool(&quot;subagent_name&quot;, description=&quot;subagent_description&quot;)
def call_subagent(query: str):
    result = subagent.invoke({&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: query}]})
    return result[&quot;messages&quot;][-1].content

main_agent = create_agent(model=&quot;...&quot;, tools=[call_subagent])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主管在判断&quot;这个任务符合某个子智能体的描述&quot;时调用它，拿到结果后继续编排。想要细粒度控制上下文，见下文「上下文工程」。&lt;/p&gt;
&lt;h3&gt;单一派发工具（&lt;code&gt;task&lt;/code&gt; 模式）&lt;/h3&gt;
&lt;p&gt;另一种做法：&lt;strong&gt;只暴露一个参数化工具&lt;/strong&gt;，按名字去调起&quot;临时子智能体&quot;（ephemeral sub-agent）来干独立任务。&lt;/p&gt;
&lt;p&gt;原文对机制的描述很精确：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任务描述（&lt;code&gt;description&lt;/code&gt;）会作为&lt;strong&gt;一条 human message&lt;/strong&gt; 传给子智能体；&lt;/li&gt;
&lt;li&gt;子智能体的&lt;strong&gt;最后一条消息&lt;/strong&gt;作为工具结果返回；&lt;/li&gt;
&lt;li&gt;和&quot;一智能体一工具&quot;不同，这是&lt;strong&gt;基于约定&lt;/strong&gt;的：一个 &lt;code&gt;task&lt;/code&gt; 工具搞定所有子智能体。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;什么时候用它（原文）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;想把 Agent 的开发&lt;strong&gt;分散到多个团队&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;需要把复杂任务&lt;strong&gt;隔离到独立上下文窗口&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;想在不改协调者代码的前提下&lt;strong&gt;可扩展地加新 Agent&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;偏好&quot;约定优于配置&quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原文也点明了代价：&lt;strong&gt;它用&quot;上下文工程上的灵活性&quot;换了&quot;组合的简单性和强上下文隔离&quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;graph LR
    A[用户] --&amp;gt; B[主管 Agent]
    B --&amp;gt; C{&quot;task 工具&amp;lt;br/&amp;gt;agent_name, description&quot;}
    C --&amp;gt;|research| D[调研 Agent]
    C --&amp;gt;|writer| E[写作 Agent]
    C --&amp;gt;|reviewer| F[审查 Agent]
    D --&amp;gt; C
    E --&amp;gt; C
    F --&amp;gt; C
    C --&amp;gt; B
    B --&amp;gt; G[回复用户]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① 主管只认识&lt;strong&gt;一个&lt;/strong&gt; &lt;code&gt;task&lt;/code&gt; 工具；② 靠 &lt;code&gt;agent_name&lt;/code&gt; 参数做&lt;strong&gt;按名分发&lt;/strong&gt;（相当于运行时查表）；③ 结果先回到 &lt;code&gt;task&lt;/code&gt; 工具，再回到主管——多了一层&quot;派发器&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键特征（原文）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单一 task 工具&lt;/strong&gt;：一个参数化工具，可按名字调起任何已注册的子智能体；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;约定式调用&lt;/strong&gt;：按名字选 Agent，任务作为 human message 传入，最后一条消息作为工具结果返回；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;团队分布式&lt;/strong&gt;：不同团队可以独立开发和部署各自的 Agent；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent 发现&lt;/strong&gt;：可以通过系统提示列出可用 Agent，也可以通过&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/multi-agent/skills-sql-assistant&quot;&gt;渐进式披露&lt;/a&gt;（用工具按需加载 Agent 信息）来发现。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示（原文，很有洞察）&lt;/strong&gt;：这种模式有个有趣的特性——&lt;strong&gt;子智能体的能力可能和主 Agent 完全一样&lt;/strong&gt;。这时候调子智能体的&lt;strong&gt;首要理由其实是&quot;上下文隔离&quot;&lt;/strong&gt;：让复杂的多步任务跑在独立的上下文窗口里，不去污染主对话历史；子智能体自主干完，只交回一份精简总结，主线程始终保持聚焦和轻量。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;附：带任务派发器的 Agent 注册表（原文完整示例）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;from langchain.tools import tool
from langchain.agents import create_agent

# 不同团队开发的子智能体
research_agent = create_agent(model=&quot;gpt-5.5&quot;, prompt=&quot;You are a research specialist...&quot;)
writer_agent = create_agent(model=&quot;gpt-5.5&quot;, prompt=&quot;You are a writing specialist...&quot;)

# 可用子智能体的注册表（&quot;按名派活&quot;的核心）
SUBAGENTS = {
    &quot;research&quot;: research_agent,
    &quot;writer&quot;: writer_agent,
}

@tool
def task(agent_name: str, description: str) -&amp;gt; str:
    &quot;&quot;&quot;Launch an ephemeral subagent for a task.

    Available agents:
    - research: Research and fact-finding
    - writer: Content creation and editing
    &quot;&quot;&quot;
    agent = SUBAGENTS[agent_name]
    result = agent.invoke({&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: description}]})
    return result[&quot;messages&quot;][-1].content

# 主协调 Agent
main_agent = create_agent(
    model=&quot;gpt-5.5&quot;,
    tools=[task],
    system_prompt=(
        &quot;You coordinate specialized sub-agents. &quot;
        &quot;Available: research (fact-finding), writer (content creation). &quot;
        &quot;Use the task tool to delegate work.&quot;
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;原文如此&lt;/strong&gt;：&lt;code&gt;task&lt;/code&gt; 的 docstring 里硬编码了可用 Agent 列表，而系统提示里又重复了一遍——原文用这种方式演示&quot;系统提示枚举&quot;。真实项目里两处重复容易不同步，建议&lt;strong&gt;只留一处权威来源&lt;/strong&gt;（或改用下面的 Enum / 工具化发现）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;决策三：上下文工程——三处&quot;阀门&quot;&lt;/h2&gt;
&lt;p&gt;原文把上下文工程分三类，每一类影响的对象不同：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;目的&lt;/th&gt;
&lt;th&gt;影响谁&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;#%E5%AD%90%E6%99%BA%E8%83%BD%E4%BD%93%E8%A7%84%E6%A0%BC%E5%90%8D%E5%AD%97%E4%B8%8E%E6%8F%8F%E8%BF%B0%E5%B0%B1%E6%98%AF%E6%8F%90%E7%A4%BA%E8%AF%8D%E6%9D%A0%E6%9D%86&quot;&gt;&lt;strong&gt;子智能体规格（specs）&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;确保&quot;该调用的时候&quot;真能调起来&lt;/td&gt;
&lt;td&gt;主管的&lt;strong&gt;路由决策&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;#%E5%AD%90%E6%99%BA%E8%83%BD%E4%BD%93%E8%BE%93%E5%85%A5%E7%BB%99%E5%AE%83%E4%BB%80%E4%B9%88%E4%B8%8A%E4%B8%8B%E6%96%87&quot;&gt;&lt;strong&gt;子智能体输入（inputs）&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;确保子智能体拿到&quot;刚好够用&quot;的上下文&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;子智能体&lt;/strong&gt;的表现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;#%E5%AD%90%E6%99%BA%E8%83%BD%E4%BD%93%E8%BE%93%E5%87%BA%E8%AE%A9%E5%AE%83%E4%BA%A4%E5%9B%9E%E4%BB%80%E4%B9%88&quot;&gt;&lt;strong&gt;子智能体输出（outputs）&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;确保主管能基于结果继续行动&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;主管&lt;/strong&gt;的表现&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;（更完整的上下文工程方法见官方的&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/context-engineering&quot;&gt;上下文工程指南&lt;/a&gt;。）&lt;/p&gt;
&lt;h3&gt;子智能体规格：名字与描述就是提示词杠杆&lt;/h3&gt;
&lt;p&gt;原文说得很直白：&lt;strong&gt;子智能体的&quot;名字&quot;和&quot;描述&quot;是主 Agent 判断该调谁的主要依据&lt;/strong&gt;——它们就是提示词杠杆，得仔细起。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;名字（Name）&lt;/strong&gt;：主 Agent 怎么称呼这个子智能体。要&lt;strong&gt;清晰、动作导向&lt;/strong&gt;（例如 &lt;code&gt;research_agent&lt;/code&gt;、&lt;code&gt;code_reviewer&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;描述（Description）&lt;/strong&gt;：主 Agent 对这个子智能体能力的认知。要&lt;strong&gt;具体说明它处理什么任务、什么时候该用它&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你用的是&lt;a href=&quot;#%E5%8D%95%E4%B8%80%E6%B4%BE%E5%8F%91%E5%B7%A5%E5%85%B7task-%E6%A8%A1%E5%BC%8F&quot;&gt;单一派发工具&lt;/a&gt;，还得额外告诉主 Agent&quot;有哪些子智能体可用&quot;。按&lt;strong&gt;数量&lt;/strong&gt;和&lt;strong&gt;注册表是静态还是动态&lt;/strong&gt;，有三种给法：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方式&lt;/th&gt;
&lt;th&gt;最适合&lt;/th&gt;
&lt;th&gt;代价&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;系统提示枚举&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;小而静态的清单（&amp;lt; 10 个）&lt;/td&gt;
&lt;td&gt;简单，但 Agent 变更时要改提示词&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enum 约束&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;小而静态的清单（&amp;lt; 10 个）&lt;/td&gt;
&lt;td&gt;类型安全、显式，但变更时要改代码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;工具化发现&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;大或动态的注册表&lt;/td&gt;
&lt;td&gt;灵活可扩展，但增加复杂度&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;系统提示枚举&lt;/h4&gt;
&lt;p&gt;把可用的 Agent 直接列在主 Agent 的系统提示里——主 Agent 把它们当作指令的一部分读到。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;什么时候用（原文）&lt;/strong&gt;：Agent 少且固定（&amp;lt; 10 个）；注册表很少变；想要最简单的实现。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;main_agent = create_agent(
    model=&quot;...&quot;,
    tools=[task],
    system_prompt=(
        &quot;You coordinate specialized sub-agents. &quot;
        &quot;Available agents:\n&quot;
        &quot;- research: Research and fact-finding\n&quot;
        &quot;- writer: Content creation and editing\n&quot;
        &quot;- reviewer: Code and document review\n&quot;
        &quot;Use the task tool to delegate work.&quot;
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;在派发工具上加 Enum 约束&lt;/h4&gt;
&lt;p&gt;给 &lt;code&gt;task&lt;/code&gt; 工具的 &lt;code&gt;agent_name&lt;/code&gt; 参数加一个 &lt;strong&gt;Enum 约束&lt;/strong&gt;：带来类型安全，并让可用 Agent 在&lt;strong&gt;工具 schema&lt;/strong&gt; 里显式可见。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;什么时候用（原文）&lt;/strong&gt;：Agent 少且固定（&amp;lt; 10 个）；想要类型安全和明确的 Agent 名；比起&quot;靠提示词引导&quot;更偏好&quot;靠 schema 校验&quot;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from enum import Enum

class AgentName(str, Enum):
    RESEARCH = &quot;research&quot;
    WRITER = &quot;writer&quot;
    REVIEWER = &quot;reviewer&quot;

@tool
def task(agent_name: AgentName, description: str) -&amp;gt; str:
    &quot;&quot;&quot;Launch an ephemeral subagent for a task.&quot;&quot;&quot;
    # ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;工具化发现（tool-based discovery）&lt;/h4&gt;
&lt;p&gt;额外提供&lt;strong&gt;一个专门的工具&lt;/strong&gt;（比如 &lt;code&gt;list_agents&lt;/code&gt; 或 &lt;code&gt;search_agents&lt;/code&gt;），让主 Agent &lt;strong&gt;按需&lt;/strong&gt;发现有哪些 Agent。这实现了渐进式披露，也支持动态注册表。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;什么时候用（原文）&lt;/strong&gt;：Agent 很多（&amp;gt; 10）或注册表在持续增长；注册表经常变或是动态的；想减小提示词体积、省 token；不同团队各自管理不同 Agent。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@tool
def list_agents(query: str = &quot;&quot;) -&amp;gt; str:
    &quot;&quot;&quot;List available subagents, optionally filtered by query.&quot;&quot;&quot;
    agents = search_agent_registry(query)
    return format_agent_list(agents)

@tool
def task(agent_name: str, description: str) -&amp;gt; str:
    &quot;&quot;&quot;Launch an ephemeral subagent for a task.&quot;&quot;&quot;
    # ...

main_agent = create_agent(
    model=&quot;...&quot;,
    tools=[task, list_agents],
    system_prompt=&quot;Use list_agents to discover available subagents, then use task to invoke them.&quot;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;子智能体输入：给它什么上下文&lt;/h3&gt;
&lt;p&gt;默认只给任务描述。但有些上下文&quot;没法写进静态提示词&quot;——完整消息历史、之前的结果、任务元数据——这些可以从 Agent 的 &lt;strong&gt;state&lt;/strong&gt; 里取出来，传进子智能体：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.agents import AgentState
from langchain.tools import tool, ToolRuntime

class CustomState(AgentState):
    example_state_key: str

@tool(&quot;subagent1_name&quot;, description=&quot;subagent1_description&quot;)
def call_subagent1(query: str, runtime: ToolRuntime[None, CustomState]):
    # 按需把消息&quot;加工&quot;成适合子智能体的输入
    subagent_input = some_logic(query, runtime.state[&quot;messages&quot;])
    result = subagent1.invoke({
        &quot;messages&quot;: subagent_input,
        # 也可以顺便传其它 state 字段；
        # 但这些字段必须在&quot;主 Agent 和子智能体&quot;两边的 state schema 里都定义。
        &quot;example_state_key&quot;: runtime.state[&quot;example_state_key&quot;],
    })
    return result[&quot;messages&quot;][-1].content
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键在 &lt;code&gt;some_logic&lt;/code&gt;&lt;/strong&gt;（原文）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只传任务描述（&lt;code&gt;query&lt;/code&gt;）→ 子智能体&lt;strong&gt;保持隔离&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;传父级消息历史（&lt;code&gt;runtime.state[&quot;messages&quot;]&lt;/code&gt;）→ 给子智能体&lt;strong&gt;铺垫已有工作&lt;/strong&gt;，适合&quot;接手父级已经开工的事&quot;，比如继续一次 PR 审查、接着修一个 bug。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原文还提到 Deep Agents 把这两种做法命名为 &lt;code&gt;mode: &quot;isolated&quot;&lt;/code&gt;（默认）和 &lt;code&gt;mode: &quot;fork&quot;&lt;/code&gt;，并&lt;strong&gt;帮你实现了 fork&lt;/strong&gt;：被 fork 的子智能体会收到父级的完整消息历史（包括工具调用）和系统提示。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;子智能体收到什么&lt;/th&gt;
&lt;th&gt;最适合&lt;/th&gt;
&lt;th&gt;代价&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Isolated 隔离&lt;/strong&gt;（默认）&lt;/td&gt;
&lt;td&gt;只有任务描述&lt;/td&gt;
&lt;td&gt;需要很少前置上下文的聚焦工作&lt;/td&gt;
&lt;td&gt;上下文隔离好，但父级做过的事它要&lt;strong&gt;重新推导一遍&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Forked 分叉&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;父级的对话历史（在 Deep Agents 里还包括系统提示）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;接着&lt;/strong&gt;父级已经开工的任务继续做&lt;/td&gt;
&lt;td&gt;有前置上下文，代价是提示词更大、隔离性更弱&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;子智能体输出：让它交回什么&lt;/h3&gt;
&lt;p&gt;原文给了两种策略，目标是让主管&quot;能拿着结果继续做决策&quot;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;提示词约束子智能体&lt;/strong&gt;：明确要求它返回什么。&lt;strong&gt;常见故障&lt;/strong&gt;是——子智能体做了一堆工具调用和推理，但最终消息里&lt;strong&gt;没把结果写进去&lt;/strong&gt;。记得提醒它：&lt;strong&gt;主管只能看到你的最终输出&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用代码加工&lt;/strong&gt;：在返回前调整或补充响应。例如除了最终文本，还用 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/graph-api#command&quot;&gt;&lt;code&gt;Command&lt;/code&gt;&lt;/a&gt; 把特定 state 字段一起带回。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;from typing import Annotated
from langchain.tools import InjectedToolCallId
from langgraph.types import Command

@tool(&quot;subagent1_name&quot;, description=&quot;subagent1_description&quot;)
def call_subagent1(
    query: str,
    tool_call_id: Annotated[str, InjectedToolCallId],
) -&amp;gt; Command:
    result = subagent1.invoke({&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: query}]})
    return Command(update={
        # 把子智能体的额外状态带回来
        &quot;example_state_key&quot;: result[&quot;example_state_key&quot;],
        &quot;messages&quot;: [
            ToolMessage(
                content=result[&quot;messages&quot;][-1].content,
                tool_call_id=tool_call_id,
            )
        ],
    })
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这三处阀门可以用一句话记住——&lt;strong&gt;specs 决定&quot;派不派得出去&quot;，inputs 决定&quot;专家手上有没有料&quot;，outputs 决定&quot;主管能不能接得上&quot;&lt;/strong&gt;。多 Agent 系统调不动，八成是这三处之一没设计好。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;检查点与状态查看（Checkpointing and state inspection）&lt;/h2&gt;
&lt;p&gt;底层机制（原文）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;默认情况下子智能体使用 &lt;strong&gt;inherited checkpointer（继承检查点）模式&lt;/strong&gt;：每次调用都从&lt;strong&gt;全新状态&lt;/strong&gt;开始，支持 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/interrupts#pause-using-interrupt&quot;&gt;interrupt&lt;/a&gt;，并且可以&lt;strong&gt;安全地并行运行&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;如果你需要子智能体&lt;strong&gt;跨多次调用保留自己的持久对话历史&lt;/strong&gt;，就用 &lt;code&gt;checkpointer=True&lt;/code&gt; 编译它（continuations 模式）。各模式的完整对比见&lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/use-subgraphs#subgraph-persistence&quot;&gt;子图持久化&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个&lt;strong&gt;很实用的坑&lt;/strong&gt;（原文）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;因为子智能体是在&lt;strong&gt;工具函数内部&lt;/strong&gt;被调用的，LangGraph &lt;strong&gt;无法静态发现&lt;/strong&gt;它们。这意味着用 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/use-subgraphs#view-subgraph-state&quot;&gt;&lt;code&gt;get_state&lt;/code&gt; + &lt;code&gt;subgraphs&lt;/code&gt;&lt;/a&gt; &lt;strong&gt;拿不到子智能体的状态&lt;/strong&gt;。如果你需要读取嵌套图的状态（比如在 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/interrupts#pause-using-interrupt&quot;&gt;interrupt&lt;/a&gt; 期间），应该改成在&lt;strong&gt;自定义图的一个&lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/use-subgraphs#call-a-subgraph-inside-a-node&quot;&gt;节点函数&lt;/a&gt;里&lt;/strong&gt;调用子智能体。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这条对做&lt;strong&gt;生产级可观测性&lt;/strong&gt;的人非常关键——如果你打算把子智能体的执行放进 LangGraph 的 checkpoint / 人工审批链路里，&lt;strong&gt;&quot;包成工具&quot;这条路会让它隐身&lt;/strong&gt;。要在图里可见、可暂停、可恢复，就得把子智能体作为&lt;strong&gt;子图节点&lt;/strong&gt;接进主图（也就是 &lt;code&gt;08&lt;/code&gt; 篇 Custom workflow 的思路）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 &lt;strong&gt;从 langgraph-supervisor 迁移&lt;/strong&gt;：&lt;code&gt;langgraph-supervisor&lt;/code&gt; 包&lt;strong&gt;已不再积极维护&lt;/strong&gt;。官方提供了从 &lt;code&gt;create_supervisor&lt;/code&gt; 迁移到本文 subagents 模式的指南（含 interrupt/resume 与外部 API 回调的流程）：&lt;a href=&quot;https://docs.langchain.com/oss/python/migrate/langgraph-supervisor&quot;&gt;Migrate from langgraph-supervisor&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一句话实现&lt;/strong&gt;：子智能体 = &lt;code&gt;create_agent(...)&lt;/code&gt; &lt;strong&gt;包一层 &lt;code&gt;@tool&lt;/code&gt;&lt;/strong&gt;；主管 = 工具清单里挂着这些工具的那个 Agent。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;五个特征&lt;/strong&gt;：集中式控制、不与用户直连（可用 interrupt 变通）、子智能体即工具、可并行、&lt;strong&gt;默认无状态&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同步 vs 异步&lt;/strong&gt;：主管需要结果 → 同步（简单但卡对话）；用户不该等 → 异步 + 三工具模式（start / status / get result），用&quot;通知 → 用户点一下 → 再发消息&quot;完成闭环。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两种工具模式&lt;/strong&gt;：一智能体一工具（精细控制）vs 单一 &lt;code&gt;task&lt;/code&gt; 派发工具（团队分布、约定优于配置、&lt;strong&gt;上下文隔离&lt;/strong&gt;是主要收益）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;三处上下文阀门&lt;/strong&gt;：specs（名字/描述，三种枚举方式）、inputs（isolated vs forked）、outputs（提示词约束 + &lt;code&gt;Command&lt;/code&gt; 带回状态）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态可见性有坑&lt;/strong&gt;：包成工具的子智能体 LangGraph 静态发现不了，&lt;code&gt;get_state(subgraphs=...)&lt;/code&gt; 读不到；要读嵌套状态就放进自定义图的节点里；&lt;code&gt;langgraph-supervisor&lt;/code&gt; 已弃用，请迁移。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;选它&lt;/strong&gt;：多个独立领域（日历/邮件/CRM/数据库）、不需要子智能体直接面对用户、想要集中式管控和强上下文隔离。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别选它&lt;/strong&gt;：只有少量工具的简单场景（用单 Agent）；用户需要直接跟专家来回对话（用 &lt;code&gt;05&lt;/code&gt; Handoffs）；只是想做一次分类分发（用 &lt;code&gt;07&lt;/code&gt; Router）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;上一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03 性能对比&lt;/a&gt; ｜ &lt;strong&gt;下一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;05 Handoffs 交接&lt;/a&gt; ｜ &lt;strong&gt;实战&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-09-case-subagents/&quot;&gt;09 用 Subagents 搭个人助理&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>03 性能对比：五种模式在三种场景下各花多少次调用、多少 Token</title><link>https://aicooleo.cn/blog/posts/multiagent-03-cost/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-03-cost/</guid><description>多 Agent 模式没有&quot;最好&quot;，只有&quot;这个场景最划算&quot;。官方用三个场景把账算给你看：一次性请求（3 次调用的是 Handoffs/Skills/Router）、同会话重复请求（有状态模式能省 40–50%）、多领域并行（并行模式省 to…</description><pubDate>Wed, 26 Aug 2026 23:12:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：多 Agent 模式没有&quot;最好&quot;，只有&quot;这个场景最划算&quot;。官方用三个场景把账算给你看：&lt;strong&gt;一次性请求&lt;/strong&gt;（3 次调用的是 Handoffs/Skills/Router）、&lt;strong&gt;同会话重复请求&lt;/strong&gt;（有状态模式能省 40–50%）、&lt;strong&gt;多领域并行&lt;/strong&gt;（并行模式省 token、串行模式翻车）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;先声明&lt;/strong&gt;：下面是官方文档为&lt;strong&gt;教学对比&lt;/strong&gt;给出的示意数字（同一套任务、同一套假设），&lt;strong&gt;不是严格 benchmark&lt;/strong&gt;。请重点看&lt;strong&gt;相对大小与趋势&lt;/strong&gt;，不要把绝对值套到你的系统上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;先搞懂两个指标&lt;/h2&gt;
&lt;p&gt;原文的开场定义：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;原文定义&lt;/th&gt;
&lt;th&gt;说人话&lt;/th&gt;
&lt;th&gt;代价&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;模型调用次数&lt;/strong&gt;（Model calls）&lt;/td&gt;
&lt;td&gt;LLM 被调用的次数&lt;/td&gt;
&lt;td&gt;每调一次模型，用户就多等一段&lt;/td&gt;
&lt;td&gt;调用越多 → 延迟越高（&lt;strong&gt;串行&lt;/strong&gt;时尤其明显）+ 单请求 API 成本越高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;处理的 Token 总量&lt;/strong&gt;（Tokens processed）&lt;/td&gt;
&lt;td&gt;所有调用累计消耗的&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/context-engineering&quot;&gt;上下文窗口&lt;/a&gt;用量&lt;/td&gt;
&lt;td&gt;每调一次，提示词+历史都要重新&quot;读一遍&quot;&lt;/td&gt;
&lt;td&gt;token 越多 → 处理成本越高，还可能撞上下文上限&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这两个指标为什么分开看？因为&lt;strong&gt;它们的代价形状不一样&lt;/strong&gt;——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;调用次数 ≈ &lt;strong&gt;延迟&lt;/strong&gt;（用户体感）+ 固定开销。串行 7 次调用，用户就得等 7 段。&lt;/li&gt;
&lt;li&gt;token 总量 ≈ &lt;strong&gt;钱&lt;/strong&gt;（按 token 计费）+ 上下文压力。一个 6K token 的技能反复加载 5 次，就是白烧 30K。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;延迟感受 ≈ 调用次数 × 单次耗时（串行时累加；并行时取最长的那条）
账单     ≈ token 总量 × 单价（同一份内容被重复读的遍数越多，越贵）
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;场景一：一次性请求（&quot;买咖啡&quot;）&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;用户&lt;/strong&gt;：&quot;Buy coffee&quot;（帮我买杯咖啡）&lt;/p&gt;
&lt;p&gt;假设：有一个专门的咖啡 Agent / 技能，能调用 &lt;code&gt;buy_coffee&lt;/code&gt; 工具。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;模型调用次数&lt;/th&gt;
&lt;th&gt;最合适？&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;&lt;strong&gt;Subagents&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;&lt;strong&gt;Handoffs&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-06-skills/&quot;&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;&lt;strong&gt;Router&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Subagents：4 次调用&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/oneshot-subagents.png&quot; alt=&quot;Subagents 一次性请求：买咖啡共 4 次模型调用&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：官方图注为「Subagents 一次性请求：买咖啡请求共 4 次模型调用」。多出来的那一次，是因为&lt;strong&gt;结果要绕回主管&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文，按原文结论推演）：这 4 次大致是——① 主管决定派给咖啡子智能体；② 咖啡子智能体决定调 &lt;code&gt;buy_coffee&lt;/code&gt;；③ 工具结果回来后子智能体作答；④ 主管把结果汇总成人话回给用户。&lt;strong&gt;第 ④ 次就是&quot;集中管控&quot;的过路费。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;已用真实抓包验证&lt;/strong&gt;：我们用 DeepSeek 模型 + mitmproxy 跑了一次真实请求，4 次调用各自看到的上下文、原始请求/响应报文、以及&quot;第 4 次究竟买到了什么&quot;，全部记录在 &lt;a href=&quot;/posts/multiagent-13-appendix-4-calls/&quot;&gt;13 附录：Subagents 为什么是 4 次调用——抓包实录&lt;/a&gt;（复现脚本见 &lt;a href=&quot;/posts/multiagent-demo/&quot;&gt;&lt;code&gt;demo/&lt;/code&gt;&lt;/a&gt;）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Handoffs：3 次调用&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/oneshot-handoffs.png&quot; alt=&quot;Handoffs 一次性请求：买咖啡共 3 次模型调用&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：官方图注为「Handoffs 一次性请求：买咖啡请求共 3 次模型调用」。没有&quot;回收汇总&quot;这一步，交接完之后由咖啡 Agent 直接对用户说话。&lt;/p&gt;
&lt;h3&gt;Skills：3 次调用&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/oneshot-skills.png&quot; alt=&quot;Skills 一次性请求：买咖啡共 3 次模型调用&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：官方图注为「Skills 一次性请求：买咖啡请求共 3 次模型调用」。加载技能 → 调工具 → 回答，全程同一个 Agent。&lt;/p&gt;
&lt;h3&gt;Router：3 次调用&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/oneshot-router.png&quot; alt=&quot;Router 一次性请求：买咖啡共 3 次模型调用&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：官方图注为「Router 一次性请求：买咖啡请求共 3 次模型调用」。原文在重复请求场景里给了精确拆分：&lt;strong&gt;路由模型调用（1）→ 专家 Agent 调 &lt;code&gt;buy_coffee&lt;/code&gt;（2）→ 专家 Agent 回答（3）&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;关键结论（原文）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key insight&lt;/strong&gt;：Handoffs、Skills、Router 在单任务上最高效（各 3 次调用）。&lt;strong&gt;Subagents 多一次&lt;/strong&gt;，因为结果要流回主管——这次额外开销换来的是&lt;strong&gt;集中控制&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;场景二：同一会话里重复请求&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;第 1 轮&lt;/strong&gt;：&quot;Buy coffee&quot;
&lt;strong&gt;第 2 轮&lt;/strong&gt;：&quot;Buy coffee again&quot;（再买一次）&lt;/p&gt;
&lt;p&gt;关键前提：&lt;strong&gt;同一个会话&lt;/strong&gt;里，用户把同一件事又说了一遍。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;第 2 轮调用次数&lt;/th&gt;
&lt;th&gt;两轮合计&lt;/th&gt;
&lt;th&gt;最合适？&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;&lt;strong&gt;Subagents&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;&lt;strong&gt;Handoffs&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-06-skills/&quot;&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;&lt;strong&gt;Router&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;逐模式看第 2 轮发生了什么（原文结论 + 两轮时序图）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Subagents：还是 4 次 → 合计 8 次&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant U as 用户
    participant S as 主管 Agent
    participant C as 咖啡子智能体
    participant T as 工具 buy_coffee

    Note over U,T: 第 1 轮：买咖啡 —— 4 次模型调用
    U-&amp;gt;&amp;gt;S: 买咖啡
    S-&amp;gt;&amp;gt;S: ① 决定派给咖啡子智能体
    S-&amp;gt;&amp;gt;C: 调用子智能体工具 schedule_event（只给这一句任务描述）
    C-&amp;gt;&amp;gt;C: ② 决定调用 buy_coffee
    C-&amp;gt;&amp;gt;T: buy_coffee()
    T--&amp;gt;&amp;gt;C: 已下单
    C-&amp;gt;&amp;gt;C: ③ 读工具结果并作答
    C--&amp;gt;&amp;gt;S: 结论文本
    S-&amp;gt;&amp;gt;S: ④ 汇总成人话
    S--&amp;gt;&amp;gt;U: 咖啡买好了

    Note over U,T: 第 2 轮：再买一杯 —— 又是 4 次，一模一样
    U-&amp;gt;&amp;gt;S: 再买一杯
    S-&amp;gt;&amp;gt;S: ① 重新决定派谁
    S-&amp;gt;&amp;gt;C: 调用子智能体工具 schedule_event（只给这一句任务描述）
    Note over C: 无状态：上一轮做过什么，它完全不记得
    C-&amp;gt;&amp;gt;C: ② 重新决定调用 buy_coffee
    C-&amp;gt;&amp;gt;T: buy_coffee()
    T--&amp;gt;&amp;gt;C: 已下单
    C-&amp;gt;&amp;gt;C: ③ 读工具结果并作答
    C--&amp;gt;&amp;gt;S: 结论文本
    S-&amp;gt;&amp;gt;S: ④ 汇总成人话
    S--&amp;gt;&amp;gt;U: 咖啡买好了
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张时序图在说什么&lt;/strong&gt;：① 主管每轮固定&lt;strong&gt;两次&lt;/strong&gt;模型调用（派活 ① + 收尾 ④），躲不掉；② 子智能体那一侧的 ②③ 在&lt;strong&gt;第 2 轮被原样重放&lt;/strong&gt;——它每次都在全新上下文里跑，上一轮的事一点不记得；③ 所以 4 + 4 = 8 次，&lt;strong&gt;第 2 轮一次没省&lt;/strong&gt;，换来的是彻底的上下文隔离。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Handoffs：2 次 → 合计 5 次&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant U as 用户
    participant F as 前台 Agent（第 1 轮的负责人）
    participant C as 咖啡 Agent
    participant ST as 工作流状态
    participant T as 工具 buy_coffee

    Note over U,T: 第 1 轮：买咖啡 —— 3 次模型调用
    U-&amp;gt;&amp;gt;F: 买咖啡
    F-&amp;gt;&amp;gt;F: ① 决定交接
    F-&amp;gt;&amp;gt;ST: transfer_to_coffee() 把 current_step 写成 coffee
    F-&amp;gt;&amp;gt;C: 交接控制权（连带当前对话历史）
    Note over F,C: 从这一刻起，对用户负责的是咖啡 Agent
    C-&amp;gt;&amp;gt;C: ② 直接调用 buy_coffee
    C-&amp;gt;&amp;gt;T: buy_coffee()
    T--&amp;gt;&amp;gt;C: 已下单
    C-&amp;gt;&amp;gt;C: ③ 回复用户
    C--&amp;gt;&amp;gt;U: 咖啡买好了

    Note over U,T: 第 2 轮：再买一杯 —— 2 次（省掉交接）
    U-&amp;gt;&amp;gt;C: 再买一杯
    Note over ST: 状态还在：current_step 仍然是 coffee
    Note over F,C: 入口先读状态 current_step=coffee&amp;lt;br/&amp;gt;→ 直接按咖啡步骤处理，前台配置不加载
    Note over F,C: 所以消息看起来&quot;跳过&quot;了前台&amp;lt;br/&amp;gt;其实是状态把它路由到了咖啡 Agent
    C-&amp;gt;&amp;gt;C: ① 直接调用 buy_coffee
    C-&amp;gt;&amp;gt;T: buy_coffee()
    T--&amp;gt;&amp;gt;C: 已下单
    C-&amp;gt;&amp;gt;C: ② 回复用户
    C--&amp;gt;&amp;gt;U: 咖啡买好了
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张时序图在说什么&lt;/strong&gt;：① 第 1 轮多出的那一次是**&quot;决定交接&quot;&lt;strong&gt;——由&lt;/strong&gt;当前负责的 Agent（前台）&lt;strong&gt;发起，把控制权交给咖啡 Agent，并把 &lt;code&gt;current_step&lt;/code&gt; 写成 &lt;code&gt;coffee&lt;/code&gt; 存进状态；② 第 2 轮用户的消息&lt;/strong&gt;直接落到咖啡 Agent**（它就是当前负责人），状态还在，于是&lt;strong&gt;跳过交接直接干活&lt;/strong&gt;；③ 3 + 2 = 5 次，&lt;strong&gt;省下的正是&quot;交接&quot;这一步&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;注意&lt;/strong&gt;：图里画的是 Handoffs 的&lt;strong&gt;语义&lt;/strong&gt;（控制权从&quot;当前负责人&quot;转移到&quot;下一步骤的 Agent&quot;）。&lt;code&gt;05&lt;/code&gt; 篇讲过这套语义有两种实现——&lt;strong&gt;单 Agent + 中间件&lt;/strong&gt;（其实是同一个 Agent 换上了咖啡步骤的提示词与工具集）或&lt;strong&gt;多 Agent 子图&lt;/strong&gt;（真正的两个图节点 + &lt;code&gt;Command.PARENT&lt;/code&gt; 跳转）。两种实现下&quot;少一次交接调用&quot;的账是一样的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;那第 2 轮&quot;前台&quot;为什么不见了？&lt;/strong&gt;（这是 Handoffs 最容易误读的一点）
因为&lt;strong&gt;前台不是一道每次都要经过的关卡，而是一个&quot;状态&quot;&lt;/strong&gt;。用户的每条消息都进入&lt;strong&gt;同一个图 / 同一个 Agent&lt;/strong&gt;，只是&lt;strong&gt;入口会先读状态&lt;/strong&gt;，再决定这一轮用哪套配置来处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单 Agent + 中间件&lt;/strong&gt;：从头到尾就是&lt;strong&gt;同一个 Agent 对象&lt;/strong&gt;。中间件每次调模型前都会 &lt;code&gt;step = request.state.get(&quot;current_step&quot;)&lt;/code&gt;——第 1 轮读到 &lt;code&gt;triage&lt;/code&gt;，就穿上&quot;前台&quot;的提示词与工具；第 2 轮读到 &lt;code&gt;coffee&lt;/code&gt;，直接穿上&quot;咖啡&quot;的配置。&lt;strong&gt;没有第二个 Agent 被唤醒，也没有&quot;前台再来一次&quot;这个过程&lt;/strong&gt;，所以省掉一次模型调用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多 Agent 子图&lt;/strong&gt;：图入口有一条条件边 &lt;code&gt;add_conditional_edges(START, route_initial, […])&lt;/code&gt;，&lt;code&gt;route_initial&lt;/code&gt; 读 &lt;code&gt;state[&quot;active_agent&quot;]&lt;/code&gt;。第 2 轮它直接 &lt;code&gt;goto&lt;/code&gt; 咖啡节点——&lt;strong&gt;前台节点是被路由&quot;跳过&quot;的，不是被咨询后放行的&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;前提是状态要活着&lt;/strong&gt;：跨回合保留状态靠 &lt;strong&gt;checkpointer + 同一个 &lt;code&gt;thread_id&lt;/code&gt;&lt;/strong&gt;。如果没挂检查点，第 2 轮会从头开始（&lt;code&gt;current_step&lt;/code&gt; 回到 &lt;code&gt;triage&lt;/code&gt;），你就得&lt;strong&gt;再付一次交接调用&lt;/strong&gt;，这张表里&quot;省 1 次&quot;的收益直接归零。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;它是&quot;粘&quot;的，不是&quot;锁&quot;的&lt;/strong&gt;：咖啡 Agent 在第 2 轮若发现这活该转给别人，照样可以再调一次交接工具把 &lt;code&gt;current_step&lt;/code&gt; 改掉——只是&lt;strong&gt;默认不会每轮都重新判断&quot;我该是谁&quot;&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;机制细节与两种实现的完整代码见 &lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;&lt;code&gt;05&lt;/code&gt; 篇&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Skills：2 次 → 合计 5 次&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant U as 用户
    participant A as Agent（同一个）
    participant SK as 技能库 load_skill
    participant T as 工具 buy_coffee

    Note over U,T: 第 1 轮：买咖啡 —— 3 次模型调用
    U-&amp;gt;&amp;gt;A: 买咖啡
    A-&amp;gt;&amp;gt;A: ① 决定加载技能
    A-&amp;gt;&amp;gt;SK: load_skill(&quot;coffee&quot;)
    SK--&amp;gt;&amp;gt;A: 手册正文（进入上下文）
    A-&amp;gt;&amp;gt;A: ② 按手册调用 buy_coffee
    A-&amp;gt;&amp;gt;T: buy_coffee()
    T--&amp;gt;&amp;gt;A: 已下单
    A-&amp;gt;&amp;gt;A: ③ 回复用户
    A--&amp;gt;&amp;gt;U: 咖啡买好了

    Note over U,T: 第 2 轮：再买一杯 —— 2 次（省掉重新加载）
    U-&amp;gt;&amp;gt;A: 再买一杯
    Note over A: 手册还躺在上下文里，而且每轮都在为它付 token
    A-&amp;gt;&amp;gt;A: ① 直接调用 buy_coffee
    A-&amp;gt;&amp;gt;T: buy_coffee()
    T--&amp;gt;&amp;gt;A: 已下单
    A-&amp;gt;&amp;gt;A: ② 回复用户
    A--&amp;gt;&amp;gt;U: 咖啡买好了
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张时序图在说什么&lt;/strong&gt;：① 第 1 轮多出的那一次是**&quot;决定加载技能&quot;&lt;strong&gt;，代价是手册正文被塞进上下文；② 第 2 轮这份手册&lt;/strong&gt;还躺在对话历史里**，不用再加载，直接干活；③ 3 + 2 = 5 次。注意它的另一面：手册会&lt;strong&gt;一直占着上下文&lt;/strong&gt;，每一轮都在为它付 token（这正是场景三里 Skills 调用最少却最贵的原因）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Router：还是 3 次 → 合计 6 次&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant U as 用户
    participant R as 路由 Router
    participant C as 咖啡 Agent
    participant T as 工具 buy_coffee

    Note over U,T: 第 1 轮：买咖啡 —— 3 次模型调用
    U-&amp;gt;&amp;gt;R: 买咖啡
    R-&amp;gt;&amp;gt;R: ① 分类：判断该走哪个域
    R-&amp;gt;&amp;gt;C: 分发给被选中的 Agent（附上要办的事）
    C-&amp;gt;&amp;gt;C: ② 调用 buy_coffee
    C-&amp;gt;&amp;gt;T: buy_coffee()
    T--&amp;gt;&amp;gt;C: 已下单
    C-&amp;gt;&amp;gt;C: ③ 作答
    C--&amp;gt;&amp;gt;R: 结论
    R--&amp;gt;&amp;gt;U: 咖啡买好了（汇总后交给用户）

    Note over U,T: 第 2 轮：再买一杯 —— 又是 3 次
    U-&amp;gt;&amp;gt;R: 再买一杯
    Note over R: 路由器无状态：上一轮判过什么，它不记得
    R-&amp;gt;&amp;gt;R: ① 重新分类
    R-&amp;gt;&amp;gt;C: 分发给被选中的 Agent（附上要办的事）
    C-&amp;gt;&amp;gt;C: ② 调用 buy_coffee
    C-&amp;gt;&amp;gt;T: buy_coffee()
    T--&amp;gt;&amp;gt;C: 已下单
    C-&amp;gt;&amp;gt;C: ③ 作答
    C--&amp;gt;&amp;gt;R: 结论
    R--&amp;gt;&amp;gt;U: 咖啡买好了（汇总后交给用户）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张时序图在说什么&lt;/strong&gt;：① &lt;strong&gt;分类这一步每轮都要重做&lt;/strong&gt;——路由器不记上一轮判过什么；② 后面两步（干活 + 作答）当然也要重跑；③ 3 + 3 = 6 次（单领域时&quot;汇总&quot;只是把专家的结论转交给用户，不再额外占一次模型调用；多领域并行时才需要真正的汇总，见场景三）。原文给的优化方向：把路由器&lt;strong&gt;包成有状态 Agent 的工具&lt;/strong&gt;（见 &lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;&lt;code&gt;07&lt;/code&gt; 篇&lt;/a&gt;的&quot;有状态路由&quot;），让分类结果能延续。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一句话对照：谁被重复付钱？&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;                 第 1 轮                              第 2 轮
Subagents   派活① + 干活②③ + 汇总④ = 4        派活① + 干活②③ + 汇总④ = 4   ← 整条链路重跑
Handoffs    交接① + 干活② + 作答③ = 3          （交接已在状态里）干活①② = 2    ← 省掉「交接」
Skills      加载① + 干活② + 作答③ = 3          （手册已在上下文里）干活①② = 2   ← 省掉「加载」
Router      分诊① + 干活② + 作答③ = 3          分诊① + 干活② + 作答③ = 3      ← 整条链路重跑

规律：无状态模式（Subagents / Router）第 2 轮把整条链路重跑一遍；
      有状态模式（Handoffs / Skills）第 2 轮省掉「进入状态」的那一步（交接 / 加载）。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;关键结论（原文）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key insight&lt;/strong&gt;：有状态模式（Handoffs、Skills）在重复请求上能&lt;strong&gt;省 40–50% 的调用&lt;/strong&gt;。Subagents 则保持&quot;每次请求成本恒定&quot;——这种无状态设计换来强上下文隔离，代价是重复调用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这条结论的工程含义很实在——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果你的业务&lt;strong&gt;重复请求占比高&lt;/strong&gt;（比如客服来回追问同一件事），应该优先 Handoffs / Skills。&lt;/li&gt;
&lt;li&gt;如果每次请求都&lt;strong&gt;几乎不相关&lt;/strong&gt;（比如一次性的批处理任务），Subagents 的&quot;恒定成本 + 强隔离&quot;反而更省心。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;场景三：多领域任务（一次问三个领域）&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;用户&lt;/strong&gt;：&quot;Compare Python, JavaScript, and Rust for web development&quot;（比较三种语言做 Web 开发）&lt;/p&gt;
&lt;p&gt;假设：每个语言 Agent / 技能各带 &lt;strong&gt;约 2000 token&lt;/strong&gt; 的文档；&lt;strong&gt;所有模式都能并行调用工具&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;模型调用次数&lt;/th&gt;
&lt;th&gt;Token 总量&lt;/th&gt;
&lt;th&gt;最合适？&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;&lt;strong&gt;Subagents&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;~9K&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;&lt;strong&gt;Handoffs&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;7+&lt;/td&gt;
&lt;td&gt;~14K+&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-06-skills/&quot;&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;~15K&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;&lt;strong&gt;Router&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;~9K&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Subagents：5 次调用，约 9K token&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/multidomain-subagents.png&quot; alt=&quot;Subagents 多领域：5 次调用，并行执行&quot; /&gt;&lt;/p&gt;
&lt;p&gt;官方图注为「Subagents 多领域：5 次调用，含并行执行」。原文解读：&lt;strong&gt;每个子智能体在隔离的上下文里工作&lt;/strong&gt;，只带自己相关的资料。总计 &lt;strong&gt;9K token&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文，按原文结论推演）：5 次 ≈ 主管决策（1）+ 三个语言子智能体各 1 次（3）+ 主管汇总（1）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Handoffs：7+ 次调用，约 14K+ token&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/multidomain-handoffs.png&quot; alt=&quot;Handoffs 多领域：7 次以上串行调用&quot; /&gt;&lt;/p&gt;
&lt;p&gt;官方图注为「Handoffs 多领域：7 次以上串行调用」。原文解读：Handoffs &lt;strong&gt;只能串行&lt;/strong&gt;——没法同时研究三门语言；对话历史越来越长还额外加负担。总计 &lt;strong&gt;~14K+ token&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;Skills：3 次调用，约 15K token&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/multidomain-skills.png&quot; alt=&quot;Skills 多领域：3 次调用，上下文持续累积&quot; /&gt;&lt;/p&gt;
&lt;p&gt;官方图注为「Skills 多领域：3 次调用，上下文累积」。原文解读：加载之后，&lt;strong&gt;后续每一次调用都要重新处理全部 6K token 的技能文档&lt;/strong&gt;。因为上下文隔离，Subagents 整体处理的 token 比它&lt;strong&gt;少 67%&lt;/strong&gt;。总计 &lt;strong&gt;15K token&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这是本篇最有价值的一条反直觉结论——
&lt;strong&gt;调用次数少 ≠ 便宜&lt;/strong&gt;。Skills 只调 3 次，却因为&quot;每轮都重读全部技能文档&quot;而最贵。省调用和省 token 是两件事。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Router：5 次调用，约 9K token&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/multidomain-router.png&quot; alt=&quot;Router 多领域：5 次调用，并行执行&quot; /&gt;&lt;/p&gt;
&lt;p&gt;官方图注为「Router 多领域：5 次调用，含并行执行」。原文解读：Router 用&lt;strong&gt;一次 LLM 做路由&lt;/strong&gt;，然后&lt;strong&gt;并行&lt;/strong&gt;调起各专家。和 Subagents 类似，只是多了一个显式的路由步骤。总计 &lt;strong&gt;9K token&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;关键结论（原文）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key insight&lt;/strong&gt;：多领域任务上，&lt;strong&gt;能并行的模式（Subagents、Router）最高效&lt;/strong&gt;。Skills 调用少但 token 高（上下文累积）。Handoffs 在这里最吃亏——它必须串行，没法利用并行工具调用同时咨询多个领域。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;三个场景放一起看&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;一次性请求&lt;/th&gt;
&lt;th&gt;重复请求&lt;/th&gt;
&lt;th&gt;多领域&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;&lt;strong&gt;Subagents&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;4 calls&lt;/td&gt;
&lt;td&gt;8 calls (4+4)&lt;/td&gt;
&lt;td&gt;5 calls, 9K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;&lt;strong&gt;Handoffs&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3 calls&lt;/td&gt;
&lt;td&gt;5 calls (3+2)&lt;/td&gt;
&lt;td&gt;7+ calls, 14K+ tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-06-skills/&quot;&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3 calls&lt;/td&gt;
&lt;td&gt;5 calls (3+2)&lt;/td&gt;
&lt;td&gt;3 calls, 15K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;&lt;strong&gt;Router&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3 calls&lt;/td&gt;
&lt;td&gt;6 calls (3+3)&lt;/td&gt;
&lt;td&gt;5 calls, 9K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;按&quot;优化目标&quot;选（原文表）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;优化目标&lt;/th&gt;
&lt;th&gt;Subagents&lt;/th&gt;
&lt;th&gt;Handoffs&lt;/th&gt;
&lt;th&gt;Skills&lt;/th&gt;
&lt;th&gt;Router&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;单次请求&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;重复请求&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;并行执行&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;大上下文领域&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;简单、聚焦的任务&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：如果只能记三句话——&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;只做一件事&lt;/strong&gt;：Handoffs / Skills / Router，别用 Subagents（多一次汇总）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同一会话反复做同一件事&lt;/strong&gt;：Handoffs / Skills（有状态，第二轮就省了）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次涉及多个领域&lt;/strong&gt;：Subagents / Router（能并行，token 还省）；Handoffs 在这里是下策。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;怎么把这些数字换算成你系统里的直觉&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文，方法论）：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先测你自己的&quot;一次调用&quot;耗时&lt;/strong&gt;：假设一次带工具的模型调用 1.5–3 秒，那么&quot;3 次串行 ≈ 4.5–9 秒&quot;。用户能不能忍，直接决定你能不能选串行模式（Handoffs）或必须并行（Subagents / Router）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把调用次数换成钱&lt;/strong&gt;：调用次数 × 平均 token × 单价。特别留意 &lt;strong&gt;Handoffs 的&quot;历史越来越长&quot;&lt;/strong&gt;——它的成本是随轮次增长的，不是常数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意上下文上限&lt;/strong&gt;：Skills 在多领域场景下 15K token 只是三门语言；换成十个领域，就是 60K+，很容易撞上限（也可能触发压缩，进一步加成本）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;看你的流量结构&lt;/strong&gt;：重复请求多 → 有状态模式（Handoffs/Skills）收益最大；几乎都是独立请求 → Subagents 的恒定成本反而可预测、好做容量规划。
&lt;ul&gt;
&lt;li&gt;⚠️ &lt;strong&gt;这个数不好直接取&lt;/strong&gt;：一次调用耗时、平均 token 都能从 trace 里量出来，但&quot;重复请求占比&quot;&lt;strong&gt;不是一个现成字段&lt;/strong&gt;，得先定义&quot;什么叫重复&quot;（同一会话里这一轮能否沿用上一轮的域/步骤）。定义、三种取数口径（业务主键 / 域标签 / 离线分类）、可直接套用的 SQL 模板与六个坑，单独写了一篇：&lt;a href=&quot;/posts/multiagent-15-appendix-dup-rate/&quot;&gt;&lt;code&gt;15&lt;/code&gt; 附录：重复请求占比怎么统计&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别忘调试成本&lt;/strong&gt;：Subagents 出问题时你要看&quot;主管 → 子智能体 → 主管&quot;三段轨迹；Handoffs 是&quot;一个人一路走下来&quot;，链路短但状态在变。&lt;strong&gt;可观测性成本也要算进选型&lt;/strong&gt;（用 &lt;a href=&quot;https://smith.langchain.com&quot;&gt;LangSmith&lt;/a&gt; 这类 trace 工具能省不少力）。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;两个指标别混&lt;/strong&gt;：调用次数 ≈ 延迟与固定开销；token 总量 ≈ 账单与上下文压力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次性任务&lt;/strong&gt;：Handoffs / Skills / Router 各 3 次调用，Subagents 4 次（多的一次是&quot;汇总回主管&quot;的集中管控成本）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重复请求&lt;/strong&gt;：有状态模式（Handoffs / Skills）第 2 轮只要 2 次调用，&lt;strong&gt;省 40–50%&lt;/strong&gt;；Router 与 Subagents 每轮成本不变。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多领域任务&lt;/strong&gt;：能并行的 Subagents / Router 最省（5 次调用、~9K token）；Handoffs 串行最贵（7+ 次、14K+）；Skills 调用最少（3 次）却最贵（15K）——&lt;strong&gt;少调用不等于少花钱&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;这些是示意数字&lt;/strong&gt;：用来建立&quot;哪种模式在哪个场景吃亏&quot;的直觉，真实决策请用自己的流量与实测数据。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;看流量结构定大方向&lt;/strong&gt;：重复请求多 → Handoffs / Skills；多领域并行多 → Subagents / Router；单发任务多 → Handoffs / Skills / Router（3 次调用）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拿不准就先量三件事&lt;/strong&gt;：一次调用的延迟、平均 token、重复请求占比——这三个数字基本就替你做完选型了。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;上一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-02-patterns/&quot;&gt;02 五种模式总览与选型&lt;/a&gt; ｜ &lt;strong&gt;下一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;04 Subagents 子智能体&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>02 五种模式总览与选型：先认脸，再挑人</title><link>https://aicooleo.cn/blog/posts/multiagent-02-patterns/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-02-patterns/</guid><description>多 Agent 就五种基本打法——Subagents（主管派活）、Handoffs（转接电话）、Skills（随用随翻手册）、Router（前台分诊）、Custom workflow（自己画流程图）。先认全这五张脸，再按&quot;要不要并行 / …</description><pubDate>Wed, 26 Aug 2026 21:07:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：多 Agent 就五种基本打法——&lt;strong&gt;Subagents（主管派活）&lt;/strong&gt;、&lt;strong&gt;Handoffs（转接电话）&lt;/strong&gt;、&lt;strong&gt;Skills（随用随翻手册）&lt;/strong&gt;、&lt;strong&gt;Router（前台分诊）&lt;/strong&gt;、&lt;strong&gt;Custom workflow（自己画流程图）&lt;/strong&gt;。先认全这五张脸，再按&quot;要不要并行 / 要不要换人格 / 要不要省上下文&quot;来挑。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;一张表认全五种模式&lt;/h2&gt;
&lt;p&gt;原文的定义（下表第二列是原文意思的中文转述，&quot;控制权在谁手里&quot;一列是补充解读）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;原文怎么定义&lt;/th&gt;
&lt;th&gt;说人话&lt;/th&gt;
&lt;th&gt;控制权在谁手里&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;&lt;strong&gt;Subagents&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;主管 Agent 把子智能体&lt;strong&gt;当工具调用&lt;/strong&gt;；所有路由都要经过主管，由它决定何时调、传什么、怎么合并结果&lt;/td&gt;
&lt;td&gt;主管手里有份&quot;外包名单&quot;，派活给专家，收结果再对人说话&lt;/td&gt;
&lt;td&gt;主管（用户只跟主管对话）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;&lt;strong&gt;Handoffs&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;行为&lt;strong&gt;根据状态动态变化&lt;/strong&gt;：工具调用更新一个状态变量，从而触发路由或配置变化——切换 Agent，或改变当前 Agent 的工具与提示词&lt;/td&gt;
&lt;td&gt;像客服转接电话：上一环节把&quot;当前状态&quot;交给下一环节，下一环节用不同的身份/权限继续&lt;/td&gt;
&lt;td&gt;当前活跃的那个 Agent（用户可以直接跟它对话）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-06-skills/&quot;&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;专用提示词与知识&lt;strong&gt;按需加载&lt;/strong&gt;；单个 Agent 全程保持控制权，需要时才去加载&lt;/td&gt;
&lt;td&gt;一个老员工 + 一柜子说明书，遇到什么活翻哪本&lt;/td&gt;
&lt;td&gt;同一个 Agent 从头到尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;&lt;strong&gt;Router&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;一个&lt;strong&gt;路由步骤&lt;/strong&gt;先给输入分类，再定向到一个或多个专用 Agent，最后把结果&lt;strong&gt;汇总&lt;/strong&gt;成一个回答&lt;/td&gt;
&lt;td&gt;医院分诊台：先判断你该看哪个科，可能同时开几个检查，最后医生合并结论&lt;/td&gt;
&lt;td&gt;路由层（用户不直接跟专家对话）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-08-custom-workflow/&quot;&gt;&lt;strong&gt;Custom workflow&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;用 &lt;a href=&quot;https://docs.langchain.com/oss/python/langgraph/overview&quot;&gt;LangGraph&lt;/a&gt; 搭&lt;strong&gt;定制执行流&lt;/strong&gt;，把确定性逻辑和 Agent 行为混在一起；可以把其它模式当节点嵌进去&lt;/td&gt;
&lt;td&gt;流程图由你画：哪一步是固定代码、哪一步交给模型，你说了算&lt;/td&gt;
&lt;td&gt;你（图怎么连就怎么走）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;四种能力维度，官方给你打星&lt;/h2&gt;
&lt;p&gt;原文用四个维度给模式打分（⭐ 越多越强）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;分布式开发&lt;/th&gt;
&lt;th&gt;并行化&lt;/th&gt;
&lt;th&gt;多跳（连续调多个）&lt;/th&gt;
&lt;th&gt;直接跟用户交互&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-04-subagents/&quot;&gt;&lt;strong&gt;Subagents&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-05-handoffs/&quot;&gt;&lt;strong&gt;Handoffs&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-06-skills/&quot;&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/multiagent-07-router/&quot;&gt;&lt;strong&gt;Router&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;四个维度的含义（原文注释）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分布式开发（Distributed development）&lt;/strong&gt;：不同团队能不能各自独立维护组件？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;并行化（Parallelization）&lt;/strong&gt;：多个 Agent 能不能同时跑？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多跳（Multi-hop）&lt;/strong&gt;：支不支持&lt;strong&gt;串联&lt;/strong&gt;调用多个子智能体（A 的结果喂给 B）？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;直接跟用户交互（Direct user interaction）&lt;/strong&gt;：子智能体能不能&lt;strong&gt;直接&lt;/strong&gt;跟用户对话？&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这张表最好这么读——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handoffs 的&quot;并行&quot;是空的：它是&lt;strong&gt;串行&lt;/strong&gt;换人，前一棒交完才轮到下一棒。&lt;/li&gt;
&lt;li&gt;Router 的&quot;多跳&quot;是空的：它只做&lt;strong&gt;一层&lt;/strong&gt;分诊 + 汇总，不适合&quot;A 完了接着 B&quot;。&lt;/li&gt;
&lt;li&gt;Subagents 的&quot;用户直连&quot;只有 ⭐：用户永远只跟主管说话，子智能体是&quot;后台工人&quot;。&lt;/li&gt;
&lt;li&gt;Skills 四项都高，是因为它压根没拆 Agent——&lt;strong&gt;所有能力都在一个 Agent 身上，只是知识按需加载&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;怎么选：先问三句话&lt;/h2&gt;
&lt;h3&gt;决策树&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    START[&quot;需求：到底要不要多 Agent？&quot;] --&amp;gt; Q1{&quot;单 Agent + 好工具/好提示词&amp;lt;br/&amp;gt;能搞定吗？&quot;}
    Q1 -- &quot;能&quot; --&amp;gt; STOP[&quot;先别拆：优化工具与提示词&amp;lt;br/&amp;gt;（见 01 篇）&quot;]
    Q1 -- &quot;不能&quot; --&amp;gt; Q2{&quot;瓶颈主要是哪一种？&quot;}
    Q2 -- &quot;知识/提示词太长，要按需加载&quot; --&amp;gt; SK[&quot;Skills：单 Agent + 技能按需加载&amp;lt;br/&amp;gt;（见 06 篇）&quot;]
    Q2 -- &quot;要同时问多个领域，越并行越好&quot; --&amp;gt; RT[&quot;Router：先分诊，再并行，最后汇总&amp;lt;br/&amp;gt;（见 07 篇）&quot;]
    Q2 -- &quot;会话里要换身份/换阶段（像转接电话）&quot; --&amp;gt; HO[&quot;Handoffs：状态驱动，切换行为&amp;lt;br/&amp;gt;（见 05 篇）&quot;]
    Q2 -- &quot;要上下文隔离 + 集中管控 + 可能多跳&quot; --&amp;gt; SUB[&quot;Subagents：主管 + 子智能体&amp;lt;br/&amp;gt;（见 04 篇）&quot;]
    Q2 -- &quot;流程有硬性步骤/分支/重试，还要混固定代码&quot; --&amp;gt; CW[&quot;Custom workflow：自己画 LangGraph&amp;lt;br/&amp;gt;（见 08 篇）&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这张图在说什么&lt;/strong&gt;：① 第一步永远是&quot;要不要拆&quot;，能单 Agent 解决就别拆；② 第二步看瓶颈类型：&lt;strong&gt;上下文太长&lt;/strong&gt; → Skills；&lt;strong&gt;要并行&lt;/strong&gt; → Router；&lt;strong&gt;要换人格/换阶段&lt;/strong&gt; → Handoffs；&lt;strong&gt;要隔离 + 集中管控&lt;/strong&gt; → Subagents；&lt;strong&gt;有硬流程&lt;/strong&gt; → Custom workflow。③ 这只是&quot;首选&quot;，真实系统经常混用。&lt;/p&gt;
&lt;h3&gt;30 秒版选择器&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;你的首要目标是&lt;/th&gt;
&lt;th&gt;首选&lt;/th&gt;
&lt;th&gt;为什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;一次请求只干一件事，想最省调用&lt;/td&gt;
&lt;td&gt;Handoffs / Skills / Router&lt;/td&gt;
&lt;td&gt;三者都是 3 次调用（见 &lt;code&gt;03&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;同一会话反复问同一领域&lt;/td&gt;
&lt;td&gt;Handoffs 或 Skills&lt;/td&gt;
&lt;td&gt;有状态，第二轮能省掉一次交接/加载&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;一次涉及多个领域，还能并行&lt;/td&gt;
&lt;td&gt;Subagents 或 Router&lt;/td&gt;
&lt;td&gt;并行执行，token 也最省&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;长上下文领域，怕污染主对话&lt;/td&gt;
&lt;td&gt;Subagents 或 Router&lt;/td&gt;
&lt;td&gt;上下文隔离&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;任务简单、只想要&quot;按需加载说明书&quot;&lt;/td&gt;
&lt;td&gt;Skills&lt;/td&gt;
&lt;td&gt;不拆 Agent，最轻&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;用户要直接跟&quot;专家&quot;对话、来回追问&lt;/td&gt;
&lt;td&gt;Handoffs&lt;/td&gt;
&lt;td&gt;唯一支持子智能体直接面对用户的模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;流程是硬编码步骤 + 局部用模型&lt;/td&gt;
&lt;td&gt;Custom workflow&lt;/td&gt;
&lt;td&gt;只有它能混确定性逻辑&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;五种模式长什么样（官方示意图）&lt;/h2&gt;
&lt;h3&gt;Subagents：主管把子智能体当工具&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/pattern-subagents.png&quot; alt=&quot;Subagents 模式：主管 Agent 把子智能体当作工具来协调&quot; /&gt;&lt;/p&gt;
&lt;p&gt;官方图注意为「Subagents 模式：主 Agent 把子智能体当工具协调」。看图重点：&lt;strong&gt;所有箭头都要经过中间那个主管&lt;/strong&gt;——子智能体之间不直接说话，用户也只面对主管。&lt;/p&gt;
&lt;h3&gt;Handoffs：靠工具调用把控制权交给下一个 Agent&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/pattern-handoffs.png&quot; alt=&quot;Handoffs 模式：Agent 之间通过工具调用转移控制权&quot; /&gt;&lt;/p&gt;
&lt;p&gt;官方图注意为「Handoffs 模式：Agent 通过工具调用转移控制权」。看图重点：这是一条&lt;strong&gt;横向的接力链&lt;/strong&gt;（A → B → C），每个 Agent 都能&quot;直接回应用户&quot;或&quot;交给下一个&quot;。&lt;/p&gt;
&lt;h3&gt;Skills：单 Agent 按需加载专业上下文&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/pattern-skills.png&quot; alt=&quot;Skills 模式：单个 Agent 按需加载专业上下文&quot; /&gt;&lt;/p&gt;
&lt;p&gt;官方图注意为「Skills 模式：单个 Agent 按需加载专用上下文」。看图重点：&lt;strong&gt;只有一个 Agent&lt;/strong&gt;，旁边挂着一排&quot;技能&quot;，用到哪个加载哪个。&lt;/p&gt;
&lt;h3&gt;Router：先分类，再分发&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/multiagent/pattern-router.png&quot; alt=&quot;Router 模式：路由步骤先把输入分类，再分发给专用 Agent&quot; /&gt;&lt;/p&gt;
&lt;p&gt;官方图注意为「Router 模式：路由步骤把输入分类后分发给专用 Agent」。看图重点：左边一次&lt;strong&gt;分类&lt;/strong&gt;，中间&lt;strong&gt;并行分发&lt;/strong&gt;给多个专家，右边&lt;strong&gt;汇总&lt;/strong&gt;成一个回答。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：五种模式长什么样，光看图容易&quot;看着都懂、选时懵&quot;。建议把 &lt;code&gt;03&lt;/code&gt; 篇的性能数字和本篇的决策树对着看——&lt;strong&gt;图决定结构，数字决定代价&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;别把这五种当单选题：可以混着用&lt;/h2&gt;
&lt;p&gt;原文的原话很直接：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can mix patterns! For example, a &lt;strong&gt;subagents&lt;/strong&gt; architecture can invoke tools that invoke custom workflows or router agents. Subagents can even use the &lt;strong&gt;skills&lt;/strong&gt; pattern to load context on-demand.&lt;/p&gt;
&lt;p&gt;（你可以混着用！比如 Subagents 架构里的工具可以去调 Custom workflow 或 Router；子智能体自己也可以用 Skills 模式按需加载上下文。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：常见的三种混法——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Router 套 Subagents&lt;/strong&gt;：先分诊到&quot;业务域&quot;，域内再由主管管一群子智能体。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subagents + Skills&lt;/strong&gt;：主管不把所有领域知识塞给子智能体，而是让子智能体自己去 &lt;code&gt;load_skill&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom workflow 打底 + 其它模式当节点&lt;/strong&gt;：固定步骤用代码写，需要&quot;动态决策&quot;的节点才放进 Agent（这也是最贴近真实生产系统的做法）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;五种模式 = 五种控制权安排&lt;/strong&gt;：主管集中管（Subagents）／状态驱动换人（Handoffs）／一人多技能（Skills）／先分诊再汇总（Router）／你自己画图（Custom workflow）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;四个维度看能力边界&lt;/strong&gt;：分布式开发、并行、多跳、用户直连——哪一项是硬需求，就直接排掉不支持的模式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选型先看瓶颈&lt;/strong&gt;：上下文长 → Skills；要并行 → Router；要换人格 → Handoffs；要隔离+管控 → Subagents；有硬流程 → Custom workflow。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模式可组合&lt;/strong&gt;：真实系统几乎不会只用一种，Subagents / Skills / Router / Custom workflow 可以互相嵌套。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代价见 &lt;code&gt;03&lt;/code&gt;&lt;/strong&gt;：一次性请求 Handoffs/Skills/Router 最省（3 次调用），重复请求有状态模式最省，多领域并行模式最省 token。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单选题就按决策树走&lt;/strong&gt;：单一领域 + 省调用 → Handoffs 或 Skills；多领域并行 → Subagents 或 Router；用户要跟专家直接聊 → Handoffs。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拿不定主意就选 Subagents&lt;/strong&gt;：它最通用（四项能力里三项满星），代价是每次多一次&quot;汇总&quot;调用。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;上一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-01-why/&quot;&gt;01 为什么需要多 Agent&lt;/a&gt; ｜ &lt;strong&gt;下一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-03-cost/&quot;&gt;03 性能对比：调用次数与 Token&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>01 为什么需要多 Agent：先把「我要多 Agent」翻译成人话</title><link>https://aicooleo.cn/blog/posts/multiagent-01-why/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/multiagent-01-why/</guid><description>多 Agent 不是&quot;更高级的架构&quot;，而是&quot;分工&quot;——当一件事一个人扛不住（上下文太长、工具太多、要同时干几件事）时，才拆成几个人干。拆之前先证明一个人真的扛不住。</description><pubDate>Wed, 26 Aug 2026 11:24:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话讲透&lt;/strong&gt;：多 Agent 不是&quot;更高级的架构&quot;，而是&quot;分工&quot;——当一件事一个人扛不住（上下文太长、工具太多、要同时干几件事）时，才拆成几个人干。&lt;strong&gt;拆之前先证明一个人真的扛不住。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;官方先泼的一盆冷水&lt;/h2&gt;
&lt;p&gt;原文开头就提醒：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Multi-agent systems coordinate specialized components to tackle complex workflows. However, not every complex task requires this approach—a single agent with the right (sometimes dynamic) tools and prompt can often achieve similar results.&lt;/p&gt;
&lt;p&gt;（多 Agent 系统协调&quot;专业化组件&quot;来对付复杂工作流。但并不是每个复杂任务都需要它——&lt;strong&gt;一个 Agent 配上合适的（有时是动态的）工具和提示词，往往能达到差不多的效果&lt;/strong&gt;。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;说人话：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多 Agent = 多几次模型调用 = &lt;strong&gt;更贵、更慢、更容易出故障&lt;/strong&gt;（多一个环节就多一个出错点）。&lt;/li&gt;
&lt;li&gt;所以它换来的必须是&lt;strong&gt;单 Agent 真做不到的东西&lt;/strong&gt;：上下文隔离、并行、团队分工。&lt;/li&gt;
&lt;li&gt;如果你只是因为&quot;一个 Agent 的提示词写得太乱了&quot;，那先重构提示词和工具，别急着拆 Agent。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：这和工程里的常识一致——微服务不是&quot;比单体高级&quot;，而是&quot;当团队规模、部署边界、性能隔离真的需要时&quot;才拆。拆早了，你得到的是分布式系统的全部麻烦，却没有它的收益。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;你说&quot;我要多 Agent&quot;，其实是想要这三件事之一&lt;/h2&gt;
&lt;p&gt;原文说，开发者说需要 multi-agent 时，通常想要的是下面三种能力之一（或几个）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;诉求&lt;/th&gt;
&lt;th&gt;原文叫法&lt;/th&gt;
&lt;th&gt;说人话&lt;/th&gt;
&lt;th&gt;典型症状&lt;/th&gt;
&lt;th&gt;常见对应模式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;上下文管理&lt;/td&gt;
&lt;td&gt;Context management&lt;/td&gt;
&lt;td&gt;给 Agent 专业知识，但&lt;strong&gt;别把上下文窗口撑爆&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;提示词越写越长，塞了一堆用不上的资料，模型开始&quot;看漏&quot;&lt;/td&gt;
&lt;td&gt;Skills、Subagents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分布式开发&lt;/td&gt;
&lt;td&gt;Distributed development&lt;/td&gt;
&lt;td&gt;不同团队各管一块能力，边界清晰，再组装成大系统&lt;/td&gt;
&lt;td&gt;一个巨大的提示词/工具集，谁都不敢改&lt;/td&gt;
&lt;td&gt;Subagents、Skills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;并行化&lt;/td&gt;
&lt;td&gt;Parallelization&lt;/td&gt;
&lt;td&gt;给子任务开专门 worker，&lt;strong&gt;并发&lt;/strong&gt;跑完更快&lt;/td&gt;
&lt;td&gt;串行问三个领域，用户等到怀疑人生&lt;/td&gt;
&lt;td&gt;Subagents、Router&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;原文对第一点有一句很关键的话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If context were infinite and latency zero, you could dump all knowledge into a single prompt—but since it&apos;s not, you need patterns to selectively surface relevant information.&lt;/p&gt;
&lt;p&gt;（如果上下文无限大、延迟为零，你完全可以把所有知识一股脑塞进一个提示词——但现实不是这样，所以你需要一些模式来&lt;strong&gt;有选择地&lt;/strong&gt;把相关信息端上来。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;什么时候真的该上多 Agent&lt;/h2&gt;
&lt;p&gt;原文给了三类&quot;确实值得&quot;的场景：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;症状&lt;/th&gt;
&lt;th&gt;为什么单 Agent 会崩&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;工具太多，选不准&lt;/td&gt;
&lt;td&gt;一个 Agent 挂了 30+ 个工具，经常调错&lt;/td&gt;
&lt;td&gt;工具描述全挤在一个上下文里，模型分不清该用哪个&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;需要&quot;专业 + 大量上下文&quot;&lt;/td&gt;
&lt;td&gt;每个领域都有长提示词 + 专用工具&lt;/td&gt;
&lt;td&gt;全量加载 → 上下文爆掉、成本飙升、注意力被稀释&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;有&lt;strong&gt;顺序约束&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;必须&quot;先登记保修，才能分类问题，再决定维修/换货&quot;&lt;/td&gt;
&lt;td&gt;单 Agent 很难可靠地&quot;锁住&quot;流程，容易跳步&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;：官方文档特别强调，多 Agent 设计的中心是&lt;strong&gt;上下文工程（context engineering）&lt;/strong&gt;——决定每个 Agent 到底看到哪些信息。你的系统好不好，基本取决于&quot;每个 Agent 手里那份材料是不是刚刚好&quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;一张图看懂：为什么&quot;分工&quot;能省上下文&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;【单 Agent 硬扛】所有知识 + 所有工具都塞进同一个提示词
┌─────────────────────────────────────────┐
│ 系统提示：产品文档 + 工单库 + 论坛问答…全量 │ ← 上下文越塞越长
│ 工具清单：40 个                          │ ← 工具越多，越容易选错
└─────────────────────────────────────────┘
              │
              ▼
     一次模型调用要扛下所有事：慢、贵、还容易幻觉

【多 Agent 分工】每个 Agent 只带自己那份上下文
┌───────────┐   ┌───────────┐   ┌───────────┐
│ 产品文档   │   │ 工单库     │   │ 论坛问答   │  ← 上下文互相隔离
│ ~2K token │   │ ~2K token │   │ ~2K token │
└───────────┘   └───────────┘   └───────────┘
        └───────────────┬───────────────┘
                        ▼
            主管 / 路由：只负责&quot;派活 + 汇总&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;那&quot;少即是多&quot;的边界在哪：先试试这些更简单的做法&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;🧭 &lt;strong&gt;补充解读&lt;/strong&gt;（非原文）：原文只说了&quot;单个 Agent + 合适的工具和提示词常常就够&quot;。结合实际工程，下面这些更便宜的手段应该&lt;strong&gt;先用完&lt;/strong&gt;，再考虑多 Agent：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;你真正的问题&lt;/th&gt;
&lt;th&gt;先试这些（成本低得多）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;提示词太长太乱&lt;/td&gt;
&lt;td&gt;精简提示词、把稳定知识放系统提示、把易变信息放检索&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;工具太多选不准&lt;/td&gt;
&lt;td&gt;给工具改好名字/描述；按场景动态挂载工具；工具分组&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;需要固定几步流程&lt;/td&gt;
&lt;td&gt;普通代码 &lt;code&gt;if/match&lt;/code&gt; + 函数编排（确定性逻辑根本不需要 LLM 决策）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;需要专业知识&lt;/td&gt;
&lt;td&gt;检索（RAG）或&quot;技能&quot;按需加载（见 &lt;code&gt;06&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;只是嫌慢&lt;/td&gt;
&lt;td&gt;并行工具调用、流式输出，而不是拆 Agent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;只有当&quot;必须让模型&lt;strong&gt;动态决定&lt;/strong&gt;派谁、并且&lt;strong&gt;上下文必须隔离&lt;/strong&gt;&quot;时，多 Agent 才是正确答案。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;官方已经内置了一套：Deep Agents&lt;/h2&gt;
&lt;p&gt;原文的提示框里写了：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;想要开箱即用的多 Agent 支持，用 &lt;a href=&quot;https://docs.langchain.com/oss/python/deepagents/overview&quot;&gt;Deep Agents&lt;/a&gt;：它是构建在 LangChain 之上的更高层封装，自带 &lt;strong&gt;subagents（子智能体）、skills（技能）、planning（规划）、虚拟文件系统、上下文管理&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;说人话：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本系列文档（&lt;code&gt;01&lt;/code&gt;–&lt;code&gt;12&lt;/code&gt;）教你的是&lt;strong&gt;怎么自己搭&lt;/strong&gt;这些模式，理解原理、能按需改造。&lt;/li&gt;
&lt;li&gt;Deep Agents 是&quot;已经搭好的一套&quot;，适合你不想自己写编排的时候。&lt;/li&gt;
&lt;li&gt;两者不冲突：理解了本文档的模式，你用 Deep Agents 时才知道它内部在干什么、什么时候该绕开它。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;多 Agent 的本质是分工&lt;/strong&gt;：把&quot;上下文 / 团队边界 / 并行&quot;三件事里的至少一件，从&quot;一个人扛&quot;变成&quot;几个人各扛一块&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认答案是单 Agent&lt;/strong&gt;：只有当工具多到选不准、单域上下文长到爆、或流程有硬顺序约束时，才升级。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;它是有代价的&lt;/strong&gt;：调用次数变多（见 &lt;code&gt;03&lt;/code&gt; 的实测对比），链路变长，出错面和调试难度都上升。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设计重心是上下文工程&lt;/strong&gt;：不是&quot;我分了几个 Agent&quot;，而是&quot;每个 Agent 看到的信息是否刚好&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deep Agents 提供内置实现&lt;/strong&gt;：想少写编排代码，可以直接用它。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一句话选型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;该上多 Agent&lt;/strong&gt;：工具有几十个且选不准；单域知识上下文太长；需要并行处理多个领域；需要&quot;先 A 后 B&quot;的强顺序约束。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别上多 Agent&lt;/strong&gt;：只是提示词乱、只是想更快、只是流程固定几步——这些用精简提示词、动态工具、检索或普通代码编排就能解决。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;下一篇&lt;/strong&gt;：&lt;a href=&quot;/posts/multiagent-02-patterns/&quot;&gt;02 五种模式总览与选型&lt;/a&gt; — 认全 Subagents / Handoffs / Skills / Router / Custom workflow 五张脸，再学会挑。&lt;/p&gt;
</content:encoded></item><item><title>Python 异步编程</title><link>https://aicooleo.cn/blog/posts/python-async-basics/</link><guid isPermaLink="true">https://aicooleo.cn/blog/posts/python-async-basics/</guid><description>async + await + future 的用法、原理与案例</description><pubDate>Tue, 30 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在现代 Python 开发中，随着应用对高并发、高性能的需求不断增加，传统同步编程方式在处理大量 I/O 操作时逐渐显得力不从心。异步编程通过极大提升程序的并发能力和资源利用率，成为了 Web 开发、微服务、实时通讯、数据抓取等领域的核心技术。&lt;/p&gt;
&lt;h2&gt;1. 问题场景&lt;/h2&gt;
&lt;p&gt;在开始学习 Python 的异步编程之前，先来看看&lt;strong&gt;传统同步编程&lt;/strong&gt;在处理&lt;strong&gt;I/O 密集型任务&lt;/strong&gt;时，存在哪些问题。I/O 密集型任务的特点是：程序在执行过程中，需要频繁等待&lt;strong&gt;输入/输出操作完成&lt;/strong&gt;。例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;等待网络请求返回数据&lt;/li&gt;
&lt;li&gt;等待磁盘读写完成&lt;/li&gt;
&lt;li&gt;等待数据库查询结果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在这些等待期间，&lt;strong&gt;程序会阻塞&lt;/strong&gt;，&lt;strong&gt;CPU 处于空闲&lt;/strong&gt;，无法继续做其他事情，从而大大降低了整体效率。下面通过一段代码，来模拟这个问题场景：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import time

def task1():
    time.sleep(5)  # 模拟一个耗时 5 秒的 I/O 操作
    return 10

def task2():
    time.sleep(3)  # 模拟一个耗时 3 秒的 I/O 操作
    return 20

def main():
    result = task1()
    print(&apos;任务1执行结果:&apos;, result)
    result = task2()
    print(&apos;任务2执行结果:&apos;, result)

if __name__ == &apos;__main__&apos;:
    start = time.time()
    main()
    print(&apos;总耗时:&apos;, time.time() - start)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;任务1执行结果: 10
任务2执行结果: 20
8.023201704025269
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，&lt;code&gt;task1()&lt;/code&gt; 执行时，程序进入 &lt;code&gt;sleep(5)&lt;/code&gt;，必须&lt;strong&gt;等待 5 秒&lt;/strong&gt;才能继续。&lt;code&gt;task2()&lt;/code&gt; 只有在 &lt;code&gt;task1()&lt;/code&gt; 完成后才能开始，又&lt;strong&gt;等待 3 秒&lt;/strong&gt;。整体耗时大约 &lt;strong&gt;8 秒&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在同步编程中，一旦遇到 I/O 阻塞，&lt;strong&gt;整个程序会停下来等待&lt;/strong&gt;。这导致：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU 资源被浪费&lt;/li&gt;
&lt;li&gt;整体执行效率低下&lt;/li&gt;
&lt;li&gt;单位时间内完成的任务量少&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;理想情况应该是：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当 task1 处于等待时，CPU 可以切换去执行其他任务（比如 task2），而不是傻等着。这样，程序可以同时推进多个任务，从而显著提升效率。这就是异步编程要解决的核心问题。&lt;/p&gt;
&lt;h2&gt;2. 解决方法&lt;/h2&gt;
&lt;p&gt;我们希望当程序遇到 I/O 等待时，能够挂起当前任务，并切换执行其他任务。并且在合适的时机，恢复挂起的任务，继续完成后续操作。&lt;/p&gt;
&lt;p&gt;虽然这听起来很简单，但要正确地管理任务的挂起和恢复时机，需要一套专门的调度机制。幸运的是，Python 内置的 &lt;code&gt;asyncio&lt;/code&gt; 模块，正好为我们提供了这样的能力 —— &lt;strong&gt;事件循环（Event Loop）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它的工作流程大致是这样的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先，创建一个事件循环&lt;/li&gt;
&lt;li&gt;然后，将需要执行的任务注册到事件循环中&lt;/li&gt;
&lt;li&gt;最后，启动事件循环，开始调度和执行各个任务&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;需要注意的是，为了让事件循环正确地识别和调度任务，我们需要遵循以下两个规则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任务函数定义时，需要使用 &lt;code&gt;async def&lt;/code&gt; 定义，表示这是一个异步函数，是需要交给事件循环管理的&lt;/li&gt;
&lt;li&gt;在任务函数内部，当遇到需要等待其他异步操作的地方，需要使用 &lt;code&gt;await&lt;/code&gt;。这就告诉事件循环：此处可以挂起当前任务，等异步操作完成后再恢复执行。这样可以避免程序在等待期间白白占用资源，从而提升整体效率。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过这套机制，我们可以让程序在遇到 I/O 等待时不会空耗资源，从而大幅提升整体的执行效率。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio
import time


async def task1():
    print(&apos;task1 开始执行&apos;)
    # await 后面的对象必须是 async def 定义的对象，而 sleep 则不是，所以会报错
    # await time.sleep(5)
    # 需要切换为 async def 版本的 sleep 函数
    await asyncio.sleep(5)
    print(&apos;task1 结束执行&apos;)
    return 10

async def task2():
    print(&apos;task2 开始执行&apos;)
    # await time.sleep(5)
    await asyncio.sleep(3)
    print(&apos;task2 结束执行&apos;)
    return 20


# 任务执行入口点
async def main():
    print(&apos;main 开始执行&apos;)
    # 获得事件循环
    event_loop = asyncio.get_running_loop()
    # 手动注册任务
    t1 = event_loop.create_task(task1())
    t2 = event_loop.create_task(task2())

    # 等待事件循环调度执行、获得 t1 任务的结果
    result = await t1
    print(&apos;任务1执行结果:&apos;, result)

    # 等待事件循环调度执行、获得 t2 任务的结果
    result = await t2
    print(&apos;任务2执行结果:&apos;, result)

    print(&apos;main 结束执行&apos;)


if __name__ == &apos;__main__&apos;:
    start = time.time()
    # 创建事件循环
    event_loop = asyncio.get_event_loop()
    # 启动事件循环
    event_loop.run_until_complete(main())
    print(time.time() - start)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;main 开始执行
task1 开始执行
task2 开始执行
task2 结束执行
task1 结束执行
任务1执行结果: 10
任务2执行结果: 20
main 结束执行
5.013811826705933
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过使用异步方式改造代码，我们发现两个任务的执行时间由原来的 8 秒缩减到 5 秒，提高了任务的执行效率。另外，在事件循环初始化这部分代码稍显复杂，我们可以使用更简洁的代码，如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 任务执行入口点
async def main():
    # 1. 获得事件循环
    # 2. 注册任务函数
    # 3. 等待所有任务执行结束
    results = await asyncio.gather(task1(), task2())
    print(results)


if __name__ == &apos;__main__&apos;:
    start = time.time()
    # 创建事件循环，并启动任务执行
    asyncio.run(main())
    # 大概需要 5 秒
    print(time.time() - start)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 细节探讨&lt;/h2&gt;
&lt;p&gt;上个例子中，我们提到 async 和 await 关键字，下面我们再详细了解下这两个关键字相关内容。&lt;/p&gt;
&lt;h3&gt;3.1 async&lt;/h3&gt;
&lt;p&gt;在 Python 中，&lt;code&gt;async&lt;/code&gt; 关键字主要用于定义异步函数/协程函数，这种函数可以执行非阻塞操作（通过 &lt;code&gt;await&lt;/code&gt; 实现），并允许在等待某些任务完成时，程序继续执行其他任务。使用 async 定义异步函数/协程函数如下代码所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio

async def task():
    print(&apos;task&apos;)

def demo():
    coro = task()
    # &amp;lt;class &apos;coroutine&apos;&amp;gt;
    print(type(coro))

if __name__ == &apos;__main__&apos;:
    demo()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关于这段代码，有以下几个重要点需要理解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;协程函数的返回值不是直接的结果，而是一个 &lt;strong&gt;协程对象&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协程对象&lt;/strong&gt;是一个未开始执行的任务。要执行这个任务，必须通过事件循环来调度。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import asyncio

async def task():
    print(&apos;task&apos;)

def demo():
    coro = task()
    # 创建事件循环，并将 coro 协程对象注册到事件循环中，由事件循环调度运行
    asyncio.run(coro)

if __name__ == &apos;__main__&apos;:
    demo()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python 中的异步编程之所以需要使用协程而不是普通函数，主要是因为协程能够支持暂停和恢复执行，而普通函数则不能。&lt;/p&gt;
&lt;h3&gt;3.2 await&lt;/h3&gt;
&lt;p&gt;在 Python 中， &lt;code&gt;await&lt;/code&gt; 关键字主要用在 &lt;code&gt;async def&lt;/code&gt; 定义的协程函数中，用于暂停协程的执行，直到异步操作完成，然后继续执行后续代码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio
import time

async def sub_task():
    print(&apos;sub task&apos;)
    return 100

async def task():
    # 暂停当前的 task 执行
    # 等待 sub_task 执行完毕
    result = await sub_task()
    # 再执行后续代码
    print(&apos;result:&apos;, result)

def demo():
    coro = task()
    asyncio.run(coro)

if __name__ == &apos;__main__&apos;:
    demo()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;await&lt;/code&gt; 关键字后面可以跟 &lt;code&gt;coroutine&lt;/code&gt;、&lt;code&gt;future&lt;/code&gt; 对象，如果 &lt;code&gt;await&lt;/code&gt; 的是其他类型的对象可能会出现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TypeError: xxx can&apos;t be used in &apos;await&apos; expression
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当 await 后面是协程对象时，事件循环暂停当前协程执行，然后继续执行 await 后面的协程对象中的代码， 此时是不会从 task1 切换到 task2。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio

async def task02():
    print(&apos;task02&apos;)
    return 200

async def sub_task():
    print(&apos;sub_task&apos;)
    return 100

async def task01():
    print(&apos;task01&apos;)
    result = await sub_task()
    return result

async def start():
    # 在事件循环中注册两个任务 task01 和 task02
    # 并等待两个任务的执行结果
    result = await asyncio.gather(task01(), task02())
    print(result)

def demo():
    asyncio.run(start())


if __name__ == &apos;__main__&apos;:
    demo()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;task01
sub_task
task02
[100, 200]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，当 await 后面的对象换成了 future 对象，则事件循环会挂起当前任务，并转到其他的任务去执行。我们也可以理解为，await future 时，就是事件循环切换任务的一个时机。请看下面的示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio

async def task02():
    print(&apos;task02&apos;)
    return 200

async def sub_task():
    print(&apos;sub_task&apos;)
    return 100

async def task01():
    print(&apos;task01&apos;)
    # 注意下面的代码：sleep 内部会执行 await future，此时任务会切换到任务2去执行
    await asyncio.sleep(3)
    result = await sub_task()
    return result

async def start():
    result = await asyncio.gather(task01(), task02())
    print(result)

def demo():
    asyncio.run(start())


if __name__ == &apos;__main__&apos;:
    demo()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;task01
task02
sub_task
[100, 200]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从执行结果来看，await future 时，事件循环确实从 task01 这个任务线切换到了 task02 这个任务线上。&lt;/p&gt;
&lt;h3&gt;3.3 future&lt;/h3&gt;
&lt;p&gt;Future 对象是异步编程中的一个重要概念，它的具体作用如下图所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    A[事件循环] --&amp;gt; B[task1 → sub_task]
    B --&amp;gt; C[需要等待网络接收数据]
    C --&amp;gt; D[把等待工作交给新线程执行]
    D --&amp;gt; E[创建 Future 对象&amp;lt;br/&amp;gt;事件循环与线程共同持有]
    E --&amp;gt; F[事件循环挂起 task1&amp;lt;br/&amp;gt;切换去执行 task2]
    D --&amp;gt; G[线程拿到数据&amp;lt;br/&amp;gt;future.set_result 回填]
    G --&amp;gt; H[事件循环检测到结果&amp;lt;br/&amp;gt;恢复 task1 继续执行]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假设：我们需要 Python 事件循环来管理两个任务。执行过程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当事件循环执行到 &lt;strong&gt;task1 -&amp;gt; sub_task -&amp;gt; 网络接收数据&lt;/strong&gt; 时，需要等待，为了不阻塞事件循环，会创建一个新的线程，并把该任务扔到该线程中去执行。&lt;/li&gt;
&lt;li&gt;创建一个 future 对象，让事件循环和新创建的线程对象持有。当新线程接收到数据后，通过 future 对象将数据传递到 task1 任务对象中。当事件循环检测到 future 对象有返回结果，继续调度 task1 任务执行。&lt;/li&gt;
&lt;li&gt;由于 task1 任务已经被挂起，事件循环就可以切换到任务2去执行，避免在任务1上等待。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从这里我们可以看到 future 对象是负责异步执行等待任务的对象和事件循环之间的桥梁。我们再回头看下 future 这个单词，它表示未来的意思。也就是说，它的作用就是帮助我们的程序，获得未来某个时间的操作结果，这才是真正需要切换任务的时机点。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio
import time
from concurrent.futures import ThreadPoolExecutor

def thread_task(futrue):
    time.sleep(5)
    futrue.set_result(100)


async def sub_task():
    print(&apos;sub task 开始&apos;)

    # 创建 future 对象
    event_loop = asyncio.get_running_loop()
    future = event_loop.create_future()
    # 创建线程池对象
    executor = ThreadPoolExecutor()
    # 在其他线程执行任务
    event_loop.run_in_executor(executor, thread_task, future)
    # 挂起当前任务，事件循环调度其他任务执行
    result = await future

    print(&apos;sub task 结束&apos;)
    return result


async def task1():
    print(&apos;task1 开始&apos;)
    result = await sub_task()
    print(&apos;task1 结束&apos;)
    return result


async def task2():
    print(&apos;task2 开始&apos;)
    await asyncio.sleep(1)
    print(&apos;task2 结束&apos;)
    return 200


async def main():
    result = await asyncio.gather(task1(), task2())
    print(result)


if __name__ == &apos;__main__&apos;:
    asyncio.run(main())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;程序执行结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;task1 开始
sub task 开始
task2 开始
task2 结束
sub task 结束
task1 结束
[100, 200]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 最后小结&lt;/h2&gt;
&lt;p&gt;异步编程是一种在&lt;strong&gt;单线程中实现并发执行&lt;/strong&gt;的技术，它通过“事件循环”来调度任务，遇到会等待的操作时（比如网络请求或文件读写），就把控制权交给其他任务，自己等结果再回来继续处理。&lt;/p&gt;
&lt;p&gt;这样就避免了“等着发呆”的情况，大大提升了运行效率。与多线程不同，异步不靠开启多个线程，而是通过任务切换来充分利用时间资源。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;通俗来说：异步编程就是让单线程也能干多件事，不等白等，高效非阻塞。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当程序主要瓶颈是文件读写、网络请求、数据库操作等 IO 操作时，异步编程可以在等待 IO 响应期间处理其他任务，最大化资源利用率。相比开多线程，异步在性能和资源占用上更轻量。简言之，Python 异步编程在 IO 密集型任务上最优，避免线程阻塞等待。&lt;/p&gt;
&lt;p&gt;写代码时需要具备“异步思维”——凡是会等待的操作，都应该考虑是否使用 &lt;code&gt;await&lt;/code&gt;、是否用异步库实现。每写一个任务，都应自问：&lt;strong&gt;这个地方是不是可以不阻塞？&lt;strong&gt;简言之，关注&lt;/strong&gt;是否等待&lt;/strong&gt;，主动识别可异步处理的环节。&lt;/p&gt;
</content:encoded></item></channel></rss>