Agent 推理加速 · 生产实践手册

0 阅读24分钟

目标读者:负责将工具调用类 Agent(ReAct / Claude Code 类多轮 tool-use 循环)落地到生产环境的工程师、SRE、平台架构师。 覆盖后端:Claude API、OpenAI / 兼容 API、自部署开源模型(vLLM / SGLang / TRT-LLM)、本地与边缘(llama.cpp / Ollama)。 关注维度:延迟、吞吐、成本、稳定性。


0. 为什么 Agent 推理特别难加速

通用 LLM 推理优化的经验在 Agent 场景下会失效,原因来自 Agent 自身的负载特征:

特征通用 Chat工具调用 Agent
单次请求轮数110–100+
上下文增长平缓单调累积(每轮追加工具结果)
输出长度中长多数轮短输出(只为决定下一个工具)
前缀重复率极高(system + history 几乎不变)
失败放大1 次重试1 次失败 → 整条 trace 重跑
阻塞点模型本身经常是 工具 I/O,不是模型

工程上要先认清一个事实:Agent 端到端延迟里,模型推理常常只占 40–60%,剩下是工具 I/O、序列化、调度、网络。盲目压模型 latency 而不优化外围,常常做的是负功。先 profile,再动手。


1. 端到端延迟拆解(先 profile,再优化)

任何加速工作第一步都是把一次完整 Agent run 拆成下面六段,分别打点:

[1] 请求进入 → 第一次模型调用开始        // 入口排队、鉴权、限流
[2] 模型 prefill(TTFT)                  // 长上下文的主要成本
[3] 模型 decode(输出 token)             // 工具调用决策通常很短
[4] 工具执行 (HTTP / DB / shell)          // 经常是真正的瓶颈
[5] 结果回填 + 下一轮 prefill              // **prefix cache 命中与否决定一切**
[6] 终止判定与最终响应组装                // 流式 flush

最小可用埋点(OpenTelemetry 语义):

with tracer.start_as_current_span("agent.turn", attributes={
    "agent.turn_index": i,
    "agent.input_tokens": usage.input_tokens,
    "agent.cached_input_tokens": usage.cache_read_input_tokens,  # 关键
    "agent.output_tokens": usage.output_tokens,
    "agent.ttft_ms": ttft_ms,
    "agent.tool_name": tool_name,
    "agent.tool_latency_ms": tool_ms,
}):
    ...

必须采集的四个指标,缺一不可:

  • TTFT(每轮):体感延迟来源。
  • 缓存命中率 = cache_read_input_tokens / input_tokens。生产 Agent 应稳定在 >85%,低于 70% 说明缓存策略有问题。
  • 每轮工具耗时分布:找出 P99 工具,常常是某个慢 SQL 或外部 API。
  • 轮数分布:P50 / P95 / P99 轮数,超长 trace 是成本和超时的主要来源。

2. 前缀缓存(Prefix Cache)—— 单点收益最大的优化

这是 Agent 加速里 ROI 最高 的一项,没有之一。原理:每一轮 Agent 调用,前 N-1 轮的内容是完全一样的,只在尾部追加新工具结果。复用前缀的 KV,能把第 N 轮的 prefill 成本从 O(全部上下文) 降到 O(新增部分)。

2.1 Claude API 上的 prompt caching

Anthropic 的 prompt caching 通过 cache_control 显式标记缓存断点(最多 4 个),命中后 cached 部分按 10% 价格计费,写入按 125%(5 分钟 TTL)或 200%(1 小时 TTL)计费。

Agent 场景的正确放置顺序(从前到后,必须保持稳定):

messages = [
    {"role": "system", "content": [
        {"type": "text", "text": STABLE_SYSTEM_PROMPT,
         "cache_control": {"type": "ephemeral"}},        # 断点 1: system
    ]},
    # tools 定义紧随 system —— tools schema 变了会击穿所有缓存
]

response = client.messages.create(
    model="claude-sonnet-4-6",
    system=[{"type": "text", "text": SYSTEM, "cache_control": {"type": "ephemeral"}}],
    tools=[
        *TOOLS_STABLE,
        {**TOOLS_STABLE[-1], "cache_control": {"type": "ephemeral"}},  # 断点 2: tools 末尾
    ],
    messages=[
        *history[:-1],
        {**history[-1], "cache_control": {"type": "ephemeral"}},       # 断点 3: 历史末尾
    ],
)

Agent 缓存的五条铁律

  1. 稳定前缀绝不动态拼接时间戳、随机 ID、用户名。把这些放到 system 末尾或单独的 user turn 里。
  2. 工具 schema 一旦上线就冻结。新增工具追加到列表末尾,不要插入中间,不要重排序。
  3. 历史只追加,不修改。任何对历史的"清理""压缩"都会击穿缓存——要做也只在 trace 结束后做。
  4. 断点放在长且稳定的内容末尾,不要放在易变内容前。
  5. TTL 选择:交互式 Agent(用户在线)用 5 分钟 ephemeral;长尾批处理或人工审核 Agent 用 1 小时。

健康指标:cache_read_input_tokens / input_tokens > 0.9,成本下降通常 70–85%,TTFT 下降 50–80%

2.2 vLLM / SGLang 上的自动前缀缓存

自部署后端通常默认开启 automatic prefix caching (APC),按 block(默认 16 token)做哈希匹配,无需手动标记。

vLLM 启动参数(生产推荐):

vllm serve <model> \
  --enable-prefix-caching \
  --block-size 16 \
  --max-num-seqs 256 \
  --gpu-memory-utilization 0.90 \
  --max-model-len 131072 \
  --enable-chunked-prefill        # 长上下文必开

SGLang 的优势在 RadixAttention——多分支 Agent(并行子任务)共享前缀的能力比 vLLM 强。多 Agent 编排选 SGLang,单分支线性 Agent 选 vLLM,性能差距通常在 10% 以内。

自部署场景独有的坑

  • APC 与 LoRA 多租户混用:不同 LoRA 适配器的前缀不能共享,按租户分池。
  • Tokenizer 漂移:升级模型版本时如果 tokenizer 变了,缓存全部失效,要在变更窗口预热。
  • --enforce-eager 会关掉 CUDA Graph:调试可用,生产必须关掉这个开关。

2.3 OpenAI 兼容 API

OpenAI 自 2024 年起对 gpt-4o 系列做 自动 prompt caching(>1024 token 自动生效,无需 API 改动),命中部分 50% 价格。Agent 工程上要做的只有一件事:保持前缀字节级稳定。常见无意击穿:

  • tools 数组顺序在不同进程间不一致(用 OrderedDict 或显式排序)。
  • system 里嵌入了 datetime.now() 或 trace_id。
  • JSON 序列化 key 顺序不稳定(用 sort_keys=True 或固定 schema)。

3. 上下文管理:让前缀稳定的工程手段

前缀缓存的前提是历史"只增不改",但 Agent trace 会无限增长。生产上要在 不破坏前缀的前提下 控制上下文长度,否则会同时撞上模型上限、成本和延迟三堵墙。

3.1 滑窗 + 摘要混合策略(推荐)

[system + tools]  ← 永久缓存层,绝不动
[历史摘要 block]  ← 缓存层,超过阈值时整体替换(接受一次性击穿)
[最近 K 轮原文]   ← 滚动窗口

触发条件:当 input_tokens > 阈值(如 80K / 模型上限的 60%)时,把最早的 M 轮压缩成一段摘要,整体替换前缀的"历史摘要 block"。这一次会击穿缓存,但之后又能稳定命中,比每轮微调划算得多。

反模式:每轮都让 LLM 决定"要不要压缩历史" —— 这会让前缀永远不稳定,缓存命中率会跌到个位数。

3.2 工具结果裁剪

工具输出是上下文膨胀的主要来源。在结果回填给模型前做一次裁剪:

工具类型裁剪策略
文件读取只保留请求的行范围;大文件用 offset/limit 强制分页
SQL 查询默认 LIMIT 100;超过时返回行数 + 前 50 行,让模型显式翻页
HTTP / 网页抓取服务端 HTML→Markdown 后再返回;剥广告、导航、脚本
Shellstdout 截断到 N KB,超过部分写文件让模型按需读

裁剪在 工具侧 做,不要在 prompt 里让模型"忽略 X"——前者节省 token,后者只增加噪声。

3.3 工具集合精简

一个常见错误是把 50 个工具一次性塞给模型。后果:

  • tools schema 占用 5–15K token,每轮都付钱。
  • 工具越多,模型选择正确工具的准确率越低(实测掉 5–15%)。
  • schema 任何改动都会击穿缓存。

生产做法:分阶段暴露工具。第一层用"meta-tool"(如 search_tools(intent))让模型动态发现工具;或按 Agent 角色固定一个 5–10 个工具的子集。


4. 模型层加速

在前缀缓存和上下文管理之后,模型本身的加速才有边际收益。

4.1 模型分级路由

并非每一轮都需要最强模型。典型 Agent 的轮分布是:

  • 意图理解 + 规划(开头 1–2 轮):需要最强模型。
  • 机械工具调用(中间 N 轮):弱模型够用,甚至更稳定。
  • 最终总结输出(结尾 1 轮):中等模型。

落地:

def pick_model(state):
    if state.turn_index == 0 or state.needs_planning:
        return "claude-opus-4-7"
    if state.last_tool_was_deterministic and state.expects_tool_call:
        return "claude-haiku-4-5-20251001"
    return "claude-sonnet-4-6"

注意:切换模型会击穿前缀缓存,因为不同模型缓存命名空间隔离。所以要么整条 trace 用同一个模型,要么把切换点做在 trace 自然分段处(如完成一个子任务后)。一种妥协方案:主循环固定一个模型,把"简单分类"型子任务外包给小模型异步调用,结果作为工具返回。

4.2 Claude 特有特性

  • Streaming + 早停:开启 stream=True,当解析到完整 tool_use block 且后续不可能再有 block 时,可以提前发起工具调用(流式 partial tool call)。在工具 I/O 大于 500ms 时收益明显。
  • Extended thinking:只对规划轮开启。每轮都开 thinking 是浪费——工具调用决策的 reasoning budget 设到 1024–2048 token 已经足够。
  • Batch API:离线评测、回放、数据生成场景必用,50% 折扣,24h SLA。不要用于在线 Agent
  • 并行 tool use:在 system 中明确允许 parallel_tool_use,模型会在单轮内返回多个独立工具调用,客户端并发执行后一次性回填。Agent 总轮数能降 20–40%。

4.3 自部署:投机解码与量化

技术收益代价Agent 场景适配
Speculative decoding (EAGLE / Medusa)decode 提速 1.5–3×显存 +10%,draft 模型训练输出长时收益高;工具调用决策很短,收益有限
INT8 / FP8 量化显存减半,吞吐 +30–50%工具调用准确率掉 1–3%必须用工具调用基准重新评测,不要只看 MMLU
INT4 (AWQ/GPTQ)显存 ¼,吞吐 +50%工具调用准确率掉 5–10%一般 不推荐 用于 Agent 生产
Chunked prefill长上下文 TTFT 降 30%+强烈推荐,Agent 必开
CUDA Graphdecode 延迟 -10–20%启动慢默认开

量化的关键警告:Agent 对工具调用 schema 遵守率极敏感,量化造成的小数点误差会让模型偶尔吐出非法 JSON,重试代价远超量化收益。上线前必须跑 ToolBench / 自有工具调用集回归。

4.4 llama.cpp / Ollama 边缘场景

  • n_keep + n_batch 配合 KV cache shift,让前缀真正复用,否则每轮都重算。
  • Q4_K_M 是质量/速度甜点,Q5_K_M 用于工具调用更稳。
  • Ollama 的 OLLAMA_KEEP_ALIVE 必须设到分钟级,否则模型反复 load。
  • 边缘 Agent 把"工具选择"留给本地,把"复杂推理"通过远程 API 兜底,是常见混合架构。

5. 并发与编排

5.1 工具并行执行

模型一轮返回多个 tool_use 时,客户端必须并发执行:

async def execute_tools(tool_calls):
    results = await asyncio.gather(
        *[run_tool(tc) for tc in tool_calls],
        return_exceptions=True,
    )
    return [normalize(r, tc) for r, tc in zip(results, tool_calls)]

并发时要注意:

  • 每个工具单独超时,不要用整体超时(一个慢工具会拖死所有快工具的结果回填)。
  • 失败的工具返回 error tool_result 给模型,不要 raise 整条 trace。
  • 共享资源(DB 连接池、外部 API 配额)要做限流,否则并发反而打挂下游。

5.2 推测性工具预取

对高确定性场景,可以在模型 streaming 出 tool_use 名称的瞬间就乐观启动工具,等参数补全后判断是否复用结果。仅适用于幂等只读工具(search、read_file),不适用副作用工具。

5.3 多 Agent 拓扑选择

拓扑适用加速点
单 Agent + 多工具任务边界不清晰prefix cache 最大化
Orchestrator + Workers子任务独立、可并行worker 并发 + 子 trace 独立缓存
Pipeline (DAG)任务可静态分解各阶段独立模型分级

多 Agent 系统每个子 Agent 用独立的稳定 system,子 Agent 之间 不要共享对话历史——会互相击穿缓存,并污染上下文。父子之间只传结构化结果。


6. 稳定性:生产级护栏

加速不能以牺牲稳定为代价。下面这些护栏在生产 Agent 是 必须项

6.1 超时分层

工具级超时   < 单轮模型超时   < 单轮总超时   < 整条 trace 超时
   (530s)         (60s)          (90s)         (510min)
  • 工具级超时必须比模型超时小,否则模型已经等不及。
  • 整条 trace 必须有硬上限和最大轮数(典型 20–50),防止死循环。

6.2 限流与排队

  • 客户端 token bucket 限流,避免触发服务端 429。
  • 不同优先级用 不同的密钥/项目,互相隔离配额。
  • 失败重试用指数退避 + jitter,永远不要无脑 while True retry

6.3 熔断与降级

故障检测降级动作
主模型 P99 飙升30 秒窗口 P99 > 阈值切到备用模型(同家族同尺寸)
单个工具连续失败滑窗失败率 > 50%给模型返回"工具暂不可用",让其换路径
缓存命中率骤降5 分钟均值 < 50%告警 + 强制检查 system/tools 是否被无意改动
上下文超限入参 token > 上限 95%强制摘要压缩或主动结束 trace

6.4 幂等性

带副作用的工具(写 DB、发邮件、转账)必须设计幂等键,由 Agent 框架在工具调用层传入。Agent 重试是常态,幂等是底线。

6.5 可观测性必备字段

每条 trace 至少落库:

  • trace_id、turn_index、model、input/output/cached token、TTFT、总耗时
  • 每个工具调用:name、参数 hash、耗时、状态、错误
  • 终止原因:stop(自然结束)/ max_turns / timeout / error / human_intervention

终止原因分布是 Agent 健康度的最直观指标。max_turns 占比 > 5% 说明任务边界没设计好;timeout 占比 > 1% 说明工具或模型有性能问题。


7. 成本控制

成本和延迟在 Agent 场景下高度相关,因为两者主因都是 token 量。下面给出按优先级排序的成本下降清单:

  1. prompt caching 命中率拉到 90%+:单项节省 70%+,几乎零代码改动。
  2. 模型分级:简单轮换 Haiku/小模型,整体成本再降 30–50%。
  3. 工具结果裁剪:单条 trace token 量降 30–60%。
  4. 轮数压缩:开启并行 tool use + 良好 system prompt,平均轮数降 20–40%。
  5. batch API(离线场景):再降 50%。
  6. 流式 + 早停:模型自我重复时及时停(设 stop sequence 或检测 tool_use block 完成)。
  7. 失败 trace 不计费 retry 策略:4xx 立即停,不要把错误内容回填给模型继续推。

成本看板必须按 trace 维度 而非 请求维度 看:一条 trace 平均成本、P99 成本、超过 X 元的 trace 占比,这才能反映真实业务成本。


8. 上线 Checklist

正式接入生产前,过一遍:

前缀稳定性

  • system / tools schema 已冻结版本,变更走灰度
  • system 内无 datetime.now() 等动态注入
  • tools 数组顺序在所有进程稳定(显式排序)
  • JSON 序列化使用 sort_keys=True 或固定 schema
  • 历史只追加,不做 in-place 修改

缓存

  • 缓存命中率监控告警阈值(< 80% 告警)
  • Claude:4 个 cache_control 断点放置正确
  • 自部署:--enable-prefix-caching 已开

上下文

  • 滑窗 + 摘要混合策略已实现
  • 工具结果服务端裁剪
  • 单 trace 最大轮数硬上限

模型

  • 模型分级路由策略(如适用)
  • streaming 已开
  • 并行 tool use 已启用且客户端并发执行
  • 量化模型已用工具调用基准回归

稳定性

  • 工具/模型/trace 三层超时
  • 限流 + 指数退避
  • 熔断降级策略覆盖主模型/工具/缓存
  • 副作用工具幂等键

可观测

  • 六段延迟分别打点
  • cache hit rate、轮数分布、终止原因看板
  • trace 维度成本看板

演练

  • 主模型不可用切备演练
  • 工具雪崩演练
  • 长 trace(接近上下文上限)回放

9. 常见反模式速查

反模式后果正解
system 里嵌入时间戳缓存 0 命中时间放第一个 user turn
每轮让模型决定是否压缩历史前缀永不稳定阈值触发 + 整体替换
50 个工具一次塞给模型schema 占满 + 准确率下降分阶段暴露 / meta-tool
并发但整体超时慢工具拖死所有每个工具独立超时
工具失败直接 raisetrace 全部重跑error tool_result 回填给模型
量化后只跑 MMLU 上线工具 schema 偶发违例用工具调用基准回归
while True retry把下游打挂指数退避 + 最大次数 + jitter
多 Agent 共享对话历史互相击穿缓存 + 上下文污染只传结构化结果
主模型 / 备模型缓存共用切换瞬间命中率归零缓存按模型隔离,切换在自然分段
端到端只看模型延迟优化方向错六段拆解

10. 各后端速查表

维度Claude APIOpenAI 兼容vLLMSGLangllama.cpp
Prefix cache显式 cache_control自动(gpt-4o+)自动 (APC)自动 (RadixAttn)n_keep 手动
缓存价格优惠10% read / 125% write50% read免费(自托管)免费免费
并行 tool use✅ 原生✅ 原生依赖模型依赖模型一般不支持
Streaming tool use✅ partial有限
Batch API✅ 50% off✅ 50% offN/AN/AN/A
投机解码平台内置平台内置✅ 可配✅ 可配
多分支共享前缀单分支单分支一般
推荐场景在线工具 Agent / 长上下文通用单 Agent 高吞吐多 Agent / 树搜索边缘 / 隐私

附:一次性优化决策树

TTFT 高?
 ├─ 缓存命中率 < 80%? → 修前缀稳定性(第 23 节)
 ├─ 输入 token 巨大?  → 上下文裁剪 + 摘要(第 3 节)
 └─ 自部署?           → 开 chunked prefill(第 4.3 节)

总延迟高但 TTFT 正常?
 ├─ 工具 P99 高?      → 单工具优化 / 并发 / 推测预取(第 5 节)
 ├─ 轮数 P99 高?      → 并行 tool use + system 改进(第 4.25.1 节)
 └─ decode 长?        → streaming 早停 / 投机解码(第 4.24.3 节)

成本高?
 ├─ 缓存命中率?        → 第 2 节
 ├─ 简单轮太多?        → 模型分级(第 4.1 节)
 └─ 工具结果太长?      → 服务端裁剪(第 3.2 节)

不稳定?
 ├─ 偶发非法 JSON?     → 检查量化 / 改用更强模型
 ├─ 长尾超时?          → 三层超时 + 熔断(第 6 节)
 └─ 重试雪崩?          → 退避 + 限流 + 幂等(第 6 节)

最后一句:Agent 加速 80% 的收益来自三件事 —— 前缀稳定 + 缓存命中、并行工具调用、合理超时与熔断。把这三项做透,再去碰投机解码和量化。


11. 多 Agent 架构深入

单 Agent 的优化做到极致后,下一个量级的收益来自架构层面:把一个长 trace 拆成多个可并行、可独立缓存、可独立失败的子 Agent。但多 Agent 引入的复杂度也是非线性的——下面把生产中真正用得到的拓扑、缓存策略、通信协议、失败模型一次说清。

11.1 何时该上多 Agent

不是所有任务都该多 Agent。先用单 Agent 跑通,出现以下任一信号才考虑拆分:

  • 平均轮数 > 30,且明显能切成阶段(调研 → 规划 → 执行 → 总结)。
  • 上下文逼近模型上限,且无法靠摘要稳定压缩。
  • 存在可并行的独立子任务(同时查 N 个数据源、对 N 个文件做相同操作)。
  • 不同子任务需要不同工具集 / 不同模型尺寸,单 Agent 时 tools schema 太胖。
  • 某些子任务对失败容忍度不同(探索性 vs 副作用性),需要独立的重试边界。

反过来,下面情况 不要 拆:

  • 任务边界依赖中间结果才能确定(拆分后还要回流,得不偿失)。
  • 子任务之间需要频繁共享大量上下文(通信成本会吃掉并行收益)。
  • 团队还没把单 Agent 的可观测性做好(多 Agent 调试难度 5–10 倍)。

11.2 三种主流拓扑

A. Orchestrator-Worker(最常用)

         ┌──────────────┐
         │ Orchestrator │  ← 强模型,规划 & 汇总
         └──────┬───────┘
        ┌──────┼──────┐
        ▼      ▼      ▼
     Worker  Worker  Worker   ← 中/弱模型,并行执行
  • Orchestrator 持有任务全貌,负责拆分子任务、分发、汇总,不直接调工具
  • Worker 是无状态的,输入 = 子任务描述 + 必要上下文片段,输出 = 结构化结果。
  • 适用于:调研类(多源检索)、批量处理(对 N 个对象执行相同操作)、规划-执行分离场景。

关键工程点

  1. Orchestrator 派发的子任务必须 完全自包含——Worker 不应需要"问回去"。这要求 Orchestrator 的规划 prompt 设计得足够细。
  2. Worker 的输出 schema 必须由 Orchestrator 预先声明,否则汇总阶段会反复重试解析。
  3. Worker 失败时 不要默认重试到底——把失败结果(含原因)返回给 Orchestrator,让强模型决定是重试、换路径还是放弃。

B. Pipeline / DAG(静态可分解任务)

[Stage 1: 抽取][Stage 2: 校验][Stage 3: 入库][Stage 4: 通知]
  • 阶段静态,每个阶段是一个专用 Agent,可独立部署、独立扩容、独立升级模型。
  • 适用于:数据处理流水线、文档处理、结构化抽取等可枚举步骤的场景。
  • 加速点:每个阶段独立的 prefix cache 命名空间,能各自做到 95%+ 命中;阶段间用消息队列解耦,慢阶段不阻塞快阶段。

C. Debate / Critic(质量优先,非加速优先)

Proposer → Critic → Proposer → ... → Finalizer
  • 用第二个 Agent 审查第一个 Agent 的输出。
  • 不要把它当作加速手段——它的目的是质量,会让延迟和成本翻倍。仅在 high-stakes 决策(代码改动、SQL 执行、对外发言)使用,且 Critic 用同等或更强模型。

11.3 子 Agent 的缓存隔离与共享

这是多 Agent 最容易被做错的地方。核心原则:缓存按 (model, role, tools schema) 三元组隔离

共享方式是否推荐说明
Orchestrator 与 Worker 共用 system工具集不同,schema 拼接后没人能命中
所有 Worker 共用 system同角色 Worker 必须共用,prefix 命中率才能上去
Worker 之间共享对话历史互相污染,前缀永不稳定
Worker 之间共享只读上下文片段用结构化 reference(如文档 ID + 行号),让每个 Worker 自己按需取
父子之间传递大段上下文通信开销会击穿并行收益
父子之间传递结构化 handoffJSON schema 化的任务规约

实际部署的命名空间设计

cache_key_prefix = f"{model}:{agent_role}:{tools_version}"
  • model:每个 Agent 固定一个模型,切换走灰度。
  • agent_role:orchestrator / worker.search / worker.code / critic ...
  • tools_version:工具 schema 哈希,schema 变了就升版本,老版本自然过期。

11.4 消息总线与通信协议

子 Agent 间通信不要用"自由对话"——会变成不可调试、不可缓存的黑盒。生产上用 结构化消息

class AgentTask(BaseModel):
    task_id: str
    parent_trace_id: str
    role: Literal["search", "code", "summarize", ...]
    input: dict           # 任务专用 schema
    context_refs: list[str]   # 只读上下文的引用(不内联)
    constraints: dict     # 超时、最大轮数、允许工具集
    deadline_ms: int

class AgentResult(BaseModel):
    task_id: str
    status: Literal["success", "partial", "failed", "timeout"]
    output: dict | None
    error: str | None
    usage: TokenUsage     # 子 trace 的 token 计费,必须回报给 orchestrator
    turns: int

通信层的工程要点

  • 消息队列(NATS / Redis Streams / Kafka) 而不是同步 HTTP——子 Agent 可独立伸缩,失败有重投。
  • Worker 数量超过 10 时,用 work stealing 模式而不是静态分配,避免 straggler。
  • Orchestrator 等待 Workers 时用 asyncio.as_completed 流式处理,第一个返回的就开始汇总,不要全部等齐。
  • 给每个子 task 单独的 deadline_ms,到点强制返回 partial,由 Orchestrator 决定补救。

11.5 上下文传递的三种范式

范式通信成本加速效果适用
完整传递差(每个 Worker 都付完整 prefill)不推荐
摘要传递中(信息损失,可能要回问)探索性任务
引用传递最佳(Worker 按需取,独立缓存)推荐默认

引用传递的实现:把原始上下文存到一个共享 K/V(Redis / S3),Orchestrator 传给 Worker 的是 ID + 取数范围。Worker 用专门的 fetch_context(ref) 工具按需取。这样:

  • Orchestrator 的 prefix 不被 Worker 的子任务上下文污染。
  • Worker 之间互相独立,可并行可重试。
  • 只读上下文可以被多个 Worker 同时缓存命中(同一 ref 内容字节相同)。

11.6 失败模型与重试边界

多 Agent 系统的稳定性核心是明确每一级的失败边界

工具失败    → Worker 内部处理(返回 error tool_result 让模型重选)
Worker 失败 → Orchestrator 处理(换路径 / 换 Worker / 放弃此子任务)
Orchestrator 失败 → 整 trace 失败(不再上面套一层)

反模式:层层重试。Orchestrator 重试 Worker,Worker 重试工具,工具内部又重试 HTTP——一次故障被放大成 N×M×K 次请求,下游被打挂。正确做法:

  • 每一级 只重试自己抽象层的失败,下层失败如实上抛。
  • 整条 trace 设全局重试预算(如 5 次),由 Orchestrator 统一调度。
  • 区分 可重试失败(超时、429、5xx)和 不可重试失败(鉴权、参数非法、业务拒绝),后者立即上抛。

11.7 多 Agent 特有的可观测性

单 Agent 的 trace 是线性的;多 Agent 是树/DAG。监控需要扩展:

必须采集的额外字段

  • parent_trace_id:建立父子关系。
  • agent_role:聚合时按角色看分布。
  • dispatch_latency:从 Orchestrator 发出到 Worker 开始的时间(队列健康度)。
  • fan_out:每次扇出的子任务数(异常扇出是 bug 信号)。
  • critical_path_ms:所有子任务中最长那条的耗时(这才是端到端延迟下限)。

看板必看的几个指标

指标健康范围异常意味
Worker 平均利用率60–80%太低 = 扇出粒度太大;太高 = 容量不足
子任务 P99 / P50 比< 5×过大说明有 straggler,要查具体子任务
Orchestrator 等待时间占比< 30%过高说明 Worker 慢或扇出失衡
跨 Agent 缓存命中率各角色独立 > 85%任一角色低 → 该角色 system/tools 在抖
trace 级失败率< 1%多 Agent 的失败率天然比单 Agent 高,需重点关注

11.8 多 Agent 的成本陷阱

多 Agent 不是天然更便宜——以下场景反而更贵:

  • 小任务硬拆:拆分本身的 orchestration 开销 > 加速收益。经验阈值:单子任务预期 < 3 轮模型调用就不该拆。
  • 重复上下文:Worker 间没用引用传递,每个 Worker 都付一遍 prefill 钱。
  • Critic 滥用:每个子任务都过 Critic,成本直接 ×2。
  • Orchestrator 用最强模型 + 每轮都长上下文:是单 Agent 成本的高倍数。

省钱的多 Agent 配方:

Orchestrator: 强模型 + 极致 prefix cache + 仅做规划/汇总(短输出)
Workers:      中/弱模型 + 独立小 tools schema + 高并发
Critic:       仅在 high-stakes 路径,且用相同档位模型

11.9 子 Agent 的灰度与版本

多 Agent 系统的发布是分布式问题:

  • 不要同时升级 Orchestrator 和 Worker——总有一边先发,新旧消息 schema 必须兼容。
  • 通信消息 schema 走 向后兼容演进(只加可选字段,不删不改语义),破坏性变更走双写双读窗口。
  • 每个 Agent 独立灰度比例,并发现回滚要按 Agent 独立回滚,而不是整条链。
  • prompt / tools 变更视同代码变更走 review,每次变更打 tag,缓存命名空间含 tag。

11.10 多 Agent 上线追加 Checklist

在第 8 节单 Agent 清单基础上,多 Agent 还需要:

  • 每个 Agent 角色有独立的 system / tools 版本与缓存命名空间
  • 子任务 schema 已声明,Orchestrator 与 Workers 双向兼容
  • 上下文用引用传递,非整段内联
  • 每个子任务有独立 deadline_ms 与最大轮数
  • 三层失败边界明确,无跨层重复重试
  • 全局重试预算
  • parent_trace_id / agent_role / critical_path_ms 已上报
  • 跨 Agent 缓存命中率看板按角色拆分
  • Orchestrator / Worker 独立灰度与回滚通道
  • 演练过:Worker 雪崩、单 Worker 慢、消息队列堆积、Orchestrator 重启 mid-trace

多 Agent 的一句话总结:它换的是 并行度和模块化,付的是 复杂度和通信开销。在单 Agent 把缓存、并行工具、护栏都做到位之前,不要急着上多 Agent;上了之后,把每个子 Agent 当独立的生产服务对待——独立的 system、独立的缓存、独立的失败边界、独立的灰度。