「什么时候该多开一个 Agent,什么时候那是自找麻烦」——这是 Agent 架构里最容易答错的一道题。正确答案不是「复杂任务就该多 Agent」,而是三个具体条件:上下文隔离、并行提速、权限隔离。三个都不满足,多开 Agent 只会让系统更难调试、更贵、更慢。
编辑部注 · 本章的核心判断题(为什么用 Subagent 而不是长上下文单 Agent)在真实面经中出现过,请重点看第二节。
先说一个反直觉的结论:大多数「感觉该上多 Agent」的场景,正确的解法是把单 Agent 的上下文管理做好。多 Agent 引入了进程间通信、上下文传递损失、结果汇总冲突、调试难度指数上升这四项成本,只有当它换来的收益明确大于这些成本时才值得。
一、三种收益,对应三种拆分方式
| 收益 | 什么时候成立 | 拆分方式 | 典型场景 |
|---|---|---|---|
| 上下文隔离 | 子任务会产生大量只要用一次、但会严重污染主上下文的中间输出 | 子 Agent 只把结论返回给父 Agent | 深度检索:读了 30 篇文档,主 Agent 只需要 5 条结论 |
| 并行提速 | 子任务之间彼此独立、无数据依赖 | 同时派生多个子 Agent,最后汇总 | 对 8 个候选方案各做一次可行性评估 |
| 权限隔离 | 某些操作需要更高权限或更强的沙箱约束 | 用独立的 Agent 持有不同工具集 | 读数据的 Agent 与能写生产库的 Agent 分开 |
注意:只有第一种收益是「结构性」的——它解决了单 Agent 无法解决的问题(上下文容量)。第二种收益可以用并发工具调用实现,第三种收益可以用工具级权限控制实现。所以真正必须上多 Agent 的场景,比想象中少。
二、为什么用 Subagent,而不是长上下文的单 Agent
这道题在真实面经里出现过,它是本章最值得背下来的答案框架。
三、编排形态:三种模式与各自的坑
| 形态 | 结构 | 适合 | 主要风险 |
|---|---|---|---|
| 主管制(Supervisor) | 父 Agent 分解任务、派生、汇总 | 任务分解清晰、子任务同质 | 父 Agent 成为瓶颈与单点;汇总时信息压缩损失 |
| 流水线(Pipeline) | A 的输出交给 B,B 交给 C | 阶段界限清晰的加工流程 | 上游错误被逐级放大且难以定位;某一环卡住整体阻塞 |
| 评审制(Critic) | 生成者 + 评审者交替循环 | 质量要求高、可验证的产出 | 容易陷入「改了又改」的无进展循环,必须设轮数上限 |
生成者与评审者互相不满意,来回修改十几轮——每次修改都「看起来有道理」,但整体质量并不提升。这在日志里表现为一轮正常的对话,实际上已经烧掉大量预算。
修法三件套:① 评审必须有明确的通过标准(rubric),而不是「你觉得好不好」;② 设最大轮数上限;③ 每轮必须产生实质变化(改动点可枚举),否则判定为无进展、直接退出。这与第 2 章的无进展检测是同一个思路。
四、结果汇总:比想象中难
多 Agent 的成果最终要合到一起,这里有三个具体的坑:
第一,格式不一致。 三个子 Agent 返回三种结构,父 Agent 得先做格式归一。解法:子 Agent 的输出必须强制结构化(固定字段的 JSON),而不是自由文本。
第二,结论冲突。 两个子 Agent 对同一问题给出相反结论。解法:不要静默选一个,要让父 Agent 显式处理——要么用可判定的依据(时间、来源权威性)裁决,要么在最终产物里同时呈现两种观点及其依据。
第三,信息损失。 子 Agent 的结论太简略,父 Agent 无法判断其可信度。解法:结论里必须带最小必要证据——一句话的依据、来源标识、以及该子任务的置信度。
from dataclasses import dataclass, asdict
from typing import Literal
import json
@dataclass
class SubResult:
"""
子 Agent 返回给父 Agent 的唯一格式。
设计要点:带证据、带置信度、带来源——否则父 Agent 无法判断该不该采信。
"""
sub_task: str
status: Literal["ok", "partial", "failed"]
conclusion: str # 结论,尽量短
evidence: list[str] # 支撑结论的最小证据(每条一句)
sources: list[str] # 来源标识,可点开核对
confidence: float # 0-1,子 Agent 自评
tokens_used: int = 0
caveats: list[str] = None # 已知局限 / 未覆盖范围
def render(self) -> str:
"""父 Agent 看到的形态:结构化、紧凑、可核验"""
return json.dumps(asdict(self), ensure_ascii=False, indent=1)
def dispatch(sub_tasks: list[str], run_subagent) -> list[SubResult]:
"""
并行派发。注意三点:
1. 每个子任务必须有明确的产出要求(否则子 Agent 会返回无法使用的东西)
2. 失败不能中断整体(partial 也要收)
3. 汇总前先做冲突检测
"""
results: list[SubResult] = []
for t in sub_tasks:
spec = (f"{t}\n\n"
f"要求:只返回结论 + 最少必要证据 + 来源 + 你的置信度(0-1)。"
f"不要返回过程、不要返回原始文档全文。")
try:
results.append(run_subagent(spec))
except Exception as e:
results.append(SubResult(
sub_task=t, status="failed",
conclusion=f"子任务执行失败:{type(e).__name__}",
evidence=[], sources=[], confidence=0.0))
return results
def merge(results: list[SubResult]) -> dict:
"""汇总:先归并,再显式暴露冲突,绝不静默选边"""
ok = [r for r in results if r.status == "ok"]
partial = [r for r in results if r.status == "partial"]
failed = [r for r in results if r.status == "failed"]
# 低置信度结论不进主结论,只作为参考项
strong = [r for r in ok if r.confidence >= 0.7]
weak = [r for r in ok if r.confidence < 0.7]
conflicts = detect_conflicts(strong)
return {
"primary": [{"conclusion": r.conclusion, "sources": r.sources} for r in strong],
"reference": [{"conclusion": r.conclusion, "confidence": r.confidence} for r in weak],
"unresolved": conflicts, # 交给父 Agent 或用户裁决,不自己选
"partial": [r.sub_task for r in partial],
"failed": [{"task": r.sub_task, "reason": r.conclusion} for r in failed],
"coverage": f"{len(ok)}/{len(results)} 个子任务完成",
}
def detect_conflicts(results: list[SubResult]) -> list[dict]:
"""
冲突检测:真实系统里可以用语义比对或让父 Agent 判断;
这里用一个可插拔的占位实现,重点是**把冲突显式暴露出来**。
"""
found = []
for i in range(len(results)):
for j in range(i + 1, len(results)):
a, b = results[i], results[j]
if contradicts(a.conclusion, b.conclusion): # 占位
found.append({"a": a.conclusion, "b": b.conclusion,
"a_sources": a.sources, "b_sources": b.sources})
return found
def contradicts(a: str, b: str) -> bool:
return False # 占位:真实实现用语义模型或规则五、什么时候不要用多 Agent
这份清单比「什么时候用」更值钱:
| 情形 | 为什么不该拆 | 更好的做法 |
|---|---|---|
| 子任务之间有强数据依赖 | 串行依赖下并行收益为零,只增加通信开销 | 单 Agent,按顺序执行 |
| 子任务产出很小 | 拆分的收益(隔离噪音)小于成本(通信 + 汇总) | 直接在主 Agent 里调用工具 |
| 需要全局一致性判断 | 每个子 Agent 都只看到局部,无法做全局判断 | 单 Agent 持有全局视图 |
| 调试期、问题还没有定位 | 多一层派生就多一层不确定性,排查成本剧增 | 先让单 Agent 跑通,再考虑拆分 |
| 延迟敏感且并行度低 | 进程启动与上下文传递带来的固定延迟可能超过收益 | 单 Agent + 并发工具调用 |
六、自测
七、小结
| 议题 | 结论 |
|---|---|
| 该不该拆 | 只有满足上下文隔离 / 并行提速 / 权限隔离之一才拆 |
| 最主要收益 | 上下文隔离(父 Agent 只装结论,不装材料) |
| 三种形态 | 主管制 / 流水线 / 评审制,各有对应的失控方式 |
| 汇总三坑 | 格式不一致、结论冲突、信息损失 |
| 冲突处理 | 绝不静默选边;能裁决就裁决并说明依据 |
| 上线顺序 | 先让单 Agent 跑通,再按需拆分 |
多 Agent 不是能力升级,是上下文管理的一种手段。把它当成能力升级来用,就会在不需要的地方引入三个新的失败面。本刊编辑部
下一章处理把能力交出去之后必须面对的问题:怎么守住边界。
◇ 面试官会怎么问
共 3 条 · 其中 1 条高频 · 先自己答一遍,再展开对照
为什么要用 Subagent,而不是一个长上下文 Agent 一直做下去? 高频
① 上下文隔离——如果所有子任务都塞进同一个上下文,会互相污染(无关信息干扰判断),而且上下文会迅速膨胀到需要频繁压缩,压缩就会丢信息。Subagent 各自持有干净上下文,只装载与自己任务相关的信息。
② 并行提速——互相独立的子任务可以并发执行,端到端时间从求和变成取最大值。
③ 权限与提示隔离——不同子任务需要的工具权限不同(读数据的和写数据库的不该有同样的能力),分 Agent 天然形成权限边界;同时每个 Subagent 可以有专门化的提示词,比一个"什么都会"的通用提示更稳。
但要多说一句:跨 Agent 传递只走结构化产物(文件、JSON 结果),不要用自然语言转述,否则会引入"传话失真",这是多 Agent 系统最常见的翻车方式。
追问链
- 部分子任务失败了怎么办?
- 结果汇总阶段会不会又变成一个长上下文问题?
多 Agent 怎么协作?有哪些组件?
① Orchestrator(调度与规划,负责拆任务、下发、汇总);
② Subagent Pool(执行单元,各自独立上下文与能力集);
③ 通信层(结构化消息:任务目标 + 约束 + 输入产物指针 + 期望输出 schema);
④ 结果汇总层(按 schema 校验、去重、冲突标注);
⑤ 失败隔离层(单个子任务失败不阻塞其他,可重试或降级,明确"部分成功"语义);
⑥ 可观测层(每个 Subagent 的 trace 挂到同一个任务链路下)。
模式上主要三种:Supervisor(集中调度,适合任务可明确分解)、Pipeline(固定阶段流水线,适合稳定流程)、去中心化(适合探索型、无明确分解方案)。
选型标准是任务可分解性 + 失败隔离需求——不是 Agent 越多越好。
多 Agent 系统的常见踩坑有哪些?
① 传话失真——用自然语言在 Agent 之间转述需求,每转一手丢一点信息。解法是只传结构化产物,且用 schema 强制约束。
② 上下文重复膨胀——为了"让子 Agent 有足够信息",把主上下文整个复制下去,结果每个子 Agent 都背着几十 K token,成本和延迟全部失控。解法是只下发该子任务的最小必要上下文,其余通过工具按需拉取。
③ 汇总变成新的长上下文——Oracle 把所有子结果原文拼起来再决策,又回到长上下文问题。解法是要求子 Agent 输出结构化摘要 + 产物指针,汇总层只读摘要,需要细节时回读产物。
④ 死锁与循环等待——A 等 B 的结论、B 等 A 的输入。解法是显式依赖图 + 超时 + 单向数据流。
结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。