多 Agent 与选型策略
上一章看完了 MCP 的全景,工具箱里已经有了本地工具、Skill、MCP 三类能力。能力多了,新问题随之而来:怎么组织它们?本章分两层回答:前半是工具层的组织——Skill(第 10 章)和 MCP(第 11 章)经常…
上一章看完了 MCP 的全景,工具箱里已经有了本地工具、Skill、MCP 三类能力。能力多了,新问题随之而来:怎么组织它们?本章分两层回答:前半是工具层的组织——Skill(第 10 章)和 MCP(第 11 章)经常被放在一起比较,但它们其实不在同一个维度上竞争,一个是"知识怎么进上下文",一个是"工具怎么跨进程",这一半给出清晰的分工边界和组合原则;后半是执行层的组织——当任务大到单个 Agent 吃不消,用子代理分而治之。这是第四章「怎么扩展它」的收官一章:扩展的终点不是把能力堆多,而是把能力组织好。
1. 先说结论:一句话分工
一句话分工:
Skill 分发的是"知道怎么做"(文档),MCP 分发的是"能动手做"(服务)。
它们解决的是两个不相交的问题:
| Skill | MCP | |
|---|---|---|
| 分发物 | 文档(怎么做事的方法论) | 工具(可执行的能力) |
| 进入系统的方式 | 作为文本注入上下文 | 作为进程/服务被连接 |
| 消耗的资源 | 使用时的 token(正文进入窗口) | 连接的进程 + schema 常驻 token |
| 生效机制 | 改变模型的行事方式(统计约束) | 提供模型原本没有的动作 |
| 失败模式 | 模型没遵循文档 | 连接断、调用超时、结果异常 |
| 类比 | 给新人的操作手册 | 外包公司的服务接口 |
回想第 10 章的结论——"工具给模型手,技能给模型方法论"——这就是两个机制的本质差异,选型的一切讨论都从这个差异出发。
2. 同一个需求的两条实现路径
拿一个具体需求做思想实验:"让 Agent 能做前端设计"。
路径 A(Skill):写一份设计规范文档,模型读了照做。
优点:零基础设施;改文档立即生效;灵活(模型可酌情变通)
缺点:遵守是概率性的;效果依赖模型能力;无法保证一致性
路径 B(MCP):做一个 design_server,暴露 render_component、check_contrast 等工具。
优点:确定性执行(对比度检查就是真的跑代码);不依赖模型悟性
缺点:开发成本高;只能覆盖可形式化的部分("美感"没法做工具)
观察关键差异:这个需求里,有些子能力适合 Skill(设计理念),有些适合 MCP(对比度校验)。真实答案通常是组合:
Skill(设计规范文档)
└── 其中一条规定:"每次交付前必须调用 check_contrast 工具(MCP)验证"
方法论指导工具的使用——这是最常见的生产形态。
3. 选型决策表
按需求特征逐条对照:
| 需求特征 | 推荐 | 理由 |
|---|---|---|
| 领域知识/规范/流程(评审标准、写作风格) | Skill | 本质是"给模型看的话" |
| 访问外部系统(数据库、API、第三方服务) | MCP | 需要真实执行,文档给不了 |
| 团队共享的 SOP | Skill | 文档版本管理即可,无需部署 |
| 严格确定性要求(合规校验、精确计算) | MCP | 第 6、7 章原则:硬约束靠代码 |
| 高频小动作(读文件、查时间) | MCP | 常驻 schema 成本低,调用可靠 |
| 低频大手册(特定行业的完整规范) | Skill | 渐进式披露省窗口(第 10 章的核心价值) |
| 需要给全生态复用(任何 Agent 都能用) | MCP | 协议即生态,一次实现处处接入 |
| 快速迭代试错 | Skill | 改 markdown 就行 |
两条经验法则:
- 拿不准先写 Skill:成本是几行 markdown,验证想法后再决定要不要硬化成工具
- 组合优于二选一:问"这个能力怎么拆"而不是"这个能力归谁"
4. 三个维度看清差异
决策表之外,更立体的对比可以从三个维度展开:
维度一:信任模型还是信任代码。Skill 把正确性交给模型的"理解力";MCP 把正确性交给代码的确定性。你的场景容错高(创意写作)倾向前者,容错低(支付、合规)倾向后者——这呼应第 8 章"能力来自模型,可靠性来自工程"的总纲。
维度二:变更成本。Skill 的迭代是改文档(秒级、无风险);MCP 的迭代是改服务、发版本、管兼容(小时级、有协调成本)。探索期用 Skill 固化认知,稳定期把验证过的流程下沉为工具——Skill 是 MCP 的孵化器。
维度三:分发半径。Skill 天然是"给自己人用的"(团队内共享文档);MCP 的协议属性让它适合"给全世界用"(发布到生态)。开源一个 Skill 只是分享一个 markdown;开源一个 MCP server 是贡献一个服务组件——后者才有生态位的概念。
5. 组合架构的三条原则
真实系统里两者共存,按这三条原则组织:
原则一:按层分工。MCP 工具是"动词层"(能做什么),Skill 是"副词层"(怎么做更好):
Skill: 代码评审规范
├─ 第1步 用 list_changes 工具(MCP)看变更
├─ 第2步 逐文件用 read_file(MCP)细读
├─ 第3步 按本文档第3节的检查清单人工评估
└─ 第4步 用 format_report 工具(MCP)产出报告
Skill 编排 MCP 工具——这不就是第 8 章 ReAct 的文档化:把验证过的行动序列固化成方法论,模型照着走减少试错。
原则二:索引归索引。第 10 章的渐进式披露和第 7 章的工具控制可以叠加:skill 索引一行常驻,工具 schema 按职责装配。两者对窗口的占用要统一预算(第 13 章 Context Engineering 的账本里,这两项都记在"系统级常驻"科目下)。
原则三:升级路径单向。Skill 验证成熟 → 硬化为工具(MCP)。反方向(把工具拆回文档)通常意味着维护两份逻辑,是坏味道。例外是工具的"使用说明"(什么时候调、参数怎么选的领域知识)——那本来就是 Skill 的地盘。
6. 一个完整的例子:企业知识库助手
"企业知识库助手"的完整选型,综合运用全部原则:
| 能力 | 机制 | 理由 |
|---|---|---|
| 检索知识库 | MCP(search_kb 工具) | 访问外部系统,必须真实执行 |
| 回答格式规范 | Skill(企业公文写作手册) | 文档型知识,渐进披露 |
| 数据脱敏规则 | MCP(sanitize 工具)+ Skill(规则说明) | 硬约束靠代码,例外情况靠文档补充 |
| 新员工 FAQ | Skill | 低频、纯知识 |
| 权限边界 | 系统提示词 + MCP 工具的权限层 | 第 15 章主题,此处不展开 |
注意"数据脱敏"那行——同一个能力可以同时需要两种机制:规则本体文档化(Skill 教模型识别敏感场景),执行靠工具(MCP 保证真的被处理了)。这是"组合优于二选一"的最典型形态。
7. 为什么需要子代理:三堵墙
前六节讲的都是工具层的组织——一个能力该交给文档(Skill)还是服务(MCP)。工具生态至此讲完了(本地工具、Skill、MCP),还剩最后一块拼图:当任务大到单个 Agent 吃不消,需要分身——子代理(Sub-agent)。
单个 Agent 有两个硬上限:上下文窗口装不下无限信息,串行循环跑不快复杂任务。子代理用"分而治之"同时突破这两个——主代理拆任务,子代理各自带着干净的上下文去执行,只把结果交回来。
具体来说,第 8 章的 Agent 跑长任务时会撞上三堵墙:
墙一:上下文污染。任务越长,messages 里堆的历史越多——中间步骤的工具结果(多半已经没用了)持续占用预算(第 6 章的裁剪只能缓解,因为"哪些还有用"很难判断)。到后期模型注意力被稀释,质量肉眼可见地下降(第 4 章的窗口预算问题在长任务上的总爆发)。
墙二:串行低速。主循环一步接一步,即使两个子任务毫无依赖(查两个城市天气、读十个文件)也只能排队。
墙三:职责混杂。一个 Agent 又写代码又查资料又排版,系统提示词塞满互相打架的指令——第 5 章说过,指令越多越容易被稀释。
子代理的解法,对三堵墙各有一剂药:
主代理(orchestrator)
├─ 拆解任务:"调研A、调研B、汇总" ← 职责上移到规划层
├─ spawn 子代理1(干净上下文,只做A) ← 墙一:每个子代理窗口都是新的
├─ spawn 子代理2(干净上下文,只做B) ← 墙二:可并行
└─ 收集结果,汇总输出 ← 墙三:每个子代理提示词专一
关键洞察:子代理和第 8 章的 Agent 是同一个东西。区别只在调用方式——人调用的叫 Agent,Agent 调用的叫子代理。就像函数调用函数没有新语义,只是栈深了一层。
8. 心智模型:子代理是一种"高级工具"
从第 7 章的视角看:子代理就是一个返回字符串的大工具——输入是任务描述(prompt),输出是执行结果(text)。对主代理而言:
工具调用: calculate("(12+8)*3") → "60"
子代理调用: run_sub_agent("调研XX") → "调研结果:..."
接口完全同构!所以实现上可以把子代理包装成第 7 章的 Tool 注册进 Registry——主代理不需要知道"这个工具背后是函数还是一整个 Agent"。这个视角让架构保持干净:分而治之不需要发明新机制,复用现有的一切(这也是第 11 章"分层"哲学的延续)。
和普通工具的差异在两点(都源于它是"活的"):
- 执行时长:子代理内部要跑若干轮循环,秒到分钟级
- 内部状态:子代理有自己的 messages、自己的工具集、自己的系统提示词
9. 实现:三层结构
9.1 子代理工厂
子代理就是第 8 章的 Agent,但加两个配置:专用工具集、精简的轮次预算:
from dataclasses import dataclass
@dataclass
class SubAgentSpec:
"""子代理的岗位说明书"""
name: str
system: str # 专一职责的系统提示词
tools: list # 只装配职责内的工具(第 7 章:工具是负债)
max_rounds: int = 4 # 小任务不需要大预算
9.2 把子代理包装成工具
from pydantic import BaseModel, Field
class RunSubAgentArgs(BaseModel):
task: str = Field(description="要子代理完成的任务描述,要具体、自包含、"
"包含所有必要背景信息")
def make_subagent_tool(spec: SubAgentSpec, registry_factory):
"""把一个子代理岗位包装成主代理可调用的工具"""
def run_sub_agent(task: str) -> str:
# 每次调用创建全新实例 → 干净上下文(墙一的解药)
sub_registry = registry_factory(spec)
sub_agent = Agent(
system=spec.system,
registry=sub_registry,
max_rounds=spec.max_rounds,
)
# 事件可以转发到主代理的监听器(级联观测)
return sub_agent.run(task)
return Tool(
run_sub_agent, RunSubAgentArgs,
description=f"委派「{spec.name}」子代理执行任务。"
f"适用:{spec.system[:60]}。任务描述必须自包含。",
)
两个设计决策:
- 每次 spawn 新实例:不复用,保证上下文绝对干净——脏上下文是本章第 7 节墙一的病根,复用等于带病上岗
- description 强调"任务必须自包含":子代理看不到主代理的任何历史,任务描述里缺背景就会瞎做。这是子代理系统的第一大翻车点,值得在说明书里反复强调
9.3 主代理组装
# 主代理自己的工具(汇总用的基础能力)
main_registry = Registry()
main_registry.register(Tool(calculate, CalculateArgs, "..."))
# 注册子代理岗位
researcher = SubAgentSpec(
name="调研员",
system="你是一个网络调研助手。用搜索工具查证事实,"
"返回结构化调研结果(要点+来源)。",
tools=[web_search, read_webpage],
)
main_registry.register(make_subagent_tool(researcher, build_research_registry))
agent = Agent(
system="你是项目经理,负责拆解任务、委派子代理、汇总结果。"
"委派时给子代理的任务必须包含全部背景信息。",
registry=main_registry,
max_rounds=6,
)
print(agent.run("调研一下 Rust 和 Go 在 Web 后端的生态对比"))
运行轨迹(示意):
主代理: ⚙ run_sub_agent({"task": "调研 Rust 的 Web 后端生态:框架、"
"成熟度、大厂案例,返回要点和来源"})
↳ (子代理内部 3 轮:搜索→阅读→整理,主代理视角只看到结果)
⚙ run_sub_agent({"task": "调研 Go 的 Web 后端生态:……"})
↳ ……
⚙ calculate(...)
✔ 汇总输出对比报告
注意主代理的 messages 里只有子代理的最终结果(几百 token),而不是子代理内部的完整轮转(可能上万 token)——上下文隔离的直接收益。
10. 并行执行
子代理是最适合并行的结构:主代理一次 tool_calls 里发出多个 run_sub_agent(第 7 章的并行调用),执行层并发跑:
import concurrent.futures as cf
def dispatch_parallel(self, calls: list[tuple[str, str]]) -> list[str]:
"""并行执行多个(子代理)调用"""
with cf.ThreadPoolExecutor(max_workers=4) as pool:
futures = [pool.submit(self.dispatch, name, args) for name, args in calls]
return [f.result() for f in futures]
第 8 章的 Agent 循环里,把"逐个执行 tool_calls"换成"收集后并行执行",就是完整的并行版本。收益直观:两个各需 30 秒的调研,串行 60 秒、并行约 30 秒。
注意边界:只有无依赖的调用才能并行。如果后一个调用需要前一个的结果(先查 ID 再查详情),模型通常会自己分开两轮请求——并行与否其实由模型在每一轮内怎么打包调用决定,执行层只需要提供并行能力。
11. 工程要点
① 结果契约。子代理返回自由文本,主代理要再消费它——所以子代理的系统提示词里应规定输出结构("返回:结论/要点/来源 三段")。本质是第 6 章结构化输出在代理间的应用:代理间通信也该有 schema。
② 预算控制。子代理是 token 黑洞:每个 spawn 都是完整的 LLM 会话。主代理 max_rounds × 子代理 max_rounds 是最坏情况的乘法关系。工程上必须给子代理更小的预算(示例里的 4 轮),并计入总账(第 8 章的 token 闸门要扩展成"含子代理的总账")。
③ 观测级联。子代理的事件应向上转发(9.2 的注释),否则调试时主代理显示"调用了子代理,10 分钟后返回",中间一片漆黑——第 15 章可观测性的伏笔:调用树(trace)必须跨代理层级连续。
④ 失败传播。子代理达到轮次上限返回"(未完成)"时,主代理需要决策:重试、换方案还是如实上报。把"子代理失败"设计成可被主代理理解的结果(而不是异常),失败处理就成了模型的自然决策而非代码的崩溃路径——第 7 章原则在代理间的延伸。
⑤ 深度限制。子代理可以再生子代理(无限套娃),工程上要限制层数(常见 2 层)。没有限制的深层级联会导致:预算指数爆炸 + 观测困难 + 定位问题如地狱。
12. 何时不用子代理
最后是克制清单——子代理不是银弹:
- 简单任务:一次 tool call 能解决的事,包装成子代理纯属浪费(多一次 LLM 调用的开销和延迟)
- 强依赖顺序的流程:步骤间数据强耦合时,拆开反而要在任务描述里搬运大量上下文,得不偿失——这种该用第 14 章的 Graph 结构
- 对延迟敏感:子代理至少多一跳 LLM 调用。实时场景直接主代理自己做
- 上下文本来就不脏:短会话硬拆,只会让任务描述变复杂。分而治之的成本在"任务描述必须自包含"
判断口诀:上下文脏了、可并行了、职责真多了——三者至少占一,才值得拆。
本章小结
- 一句话分工:Skill 分发"知道怎么做"(文档),MCP 分发"能动手做"(服务)
- 三个维度:信任模型 vs 信任代码;迭代成本高低;分发半径大小
- 经验法则:拿不准先写 Skill(它是 MCP 的孵化器);组合优于二选一
- 三条架构原则:动词层/副词层分工、索引统一预算、升级路径单向(Skill→工具)
- 同一能力可拆可叠:硬约束给 MCP,领域知识给 Skill
- 三堵墙(上下文污染、串行低速、职责混杂)各有解药:干净上下文、并行、专一提示词
- 心智模型:子代理 = 返回字符串的高级工具;主代理不需要知道背后是不是 Agent
- 三层实现:岗位说明书(Spec)→ 工具包装(每次 spawn 新实例)→ 主代理组装
- 工程五要点:结果契约、预算乘法控制、观测级联、失败传播、深度限制
- 克制清单:简单任务、强依赖流程、延迟敏感、短会话都不要拆
至此,Agent 的全部构造技术都齐了:循环(第 8 章)、工具(第 7 章)、技能(第 10 章)、协议(第 11 章)、分身(本章)。第四章「怎么扩展它」到此收官。剩下的问题是工程级的:上下文怎么经营(第 13 章)、流程怎么编排(第 14 章)、怎么稳妥上生产(第 15 章)。最后一部分,我们从上下文开始。
下一篇:13 · 提示词工程与上下文工程