Agent 工程体系 · 第 87/98 篇。内容以 2026 年 9 月可验证的公开规范和稳定接口为基线;框架版本敏感能力会明确标注,不把实验行为写成通用保证。
Agent 延迟与成本:TTFT、步骤、Token、工具耗时、预算和降级
Agent 的延迟和成本,不是一次模型调用的两个附属指标,而是一个由模型轮次、步骤数量、Token 数量、工具执行、并发关系、预算策略和失败回退共同决定的系统问题。
一次“回答用户问题”的请求,可能经历如下路径:
flowchart LR
A[用户请求] --> B[请求接入与路由]
B --> C[模型第 1 轮]
C --> D{是否调用工具}
D -- 否 --> E[最终回答]
D -- 是 --> F[工具执行]
F --> G[模型第 2 轮]
G --> H{是否继续调用工具}
H -- 是 --> F
H -- 否 --> E
E --> I[记录 Trace、Token、成本和质量]
如果只记录最终请求的总耗时和账单金额,就无法回答几个关键问题:
- 用户为什么等了 8 秒?
- 是模型首 Token 慢,还是工具慢?
- 是模型调用次数增加,还是上下文反复增长?
- 某次降级究竟节省了成本,还是因为质量下降导致了重试?
- 一个更快、更便宜的模型,是否因为多走了几步而变得更慢、更贵?
因此,Agent 的成本治理必须从“单次模型调用”提升到“完整工作流运行”的层级。OpenAI 的评测文档将一次 Agent trace 视为包含模型调用、工具调用、guardrail 和 handoff 的端到端记录;这正是分析延迟和成本所需要的观测边界。(developers.openai.com)
一、先定义测量对象:请求、步骤、轮次和 Trace
1. 一次请求不等于一次模型调用
设一次用户请求为一个 request,它从网关接收请求开始,到返回最终结果或明确失败结束。
一个 Agent 请求可能包含:
request
├── model_turn_1
│ ├── prompt 构造
│ ├── 模型生成工具调用
│ └── 工具参数解析
├── tool_call_1
├── model_turn_2
│ ├── 工具结果加入上下文
│ └── 模型生成最终回答
└── response serialization
这里至少有四个不同的概念。
2. Step:一次工作流步骤
**步骤(step)**是工作流状态向前推进的一次可观测动作。它可以是:
- 一次模型决策;
- 一次工具调用;
- 一次 handoff;
- 一次 guardrail 检查;
- 一次检索或缓存读取;
- 一次人工审批等待。
工程上必须先规定 step 的口径,否则“平均步骤数”没有可比性。
一种实用口径是:
model_step = 一次模型请求及其响应
tool_step = 一次工具请求及其响应
handoff_step = 一次 Agent 间转交
另一种口径可能把“模型选择工具 + 工具执行 + 模型消费结果”算作一个业务步骤。两种口径都可以,但不能混用。
本文以下使用:
步骤数 S = 模型步骤数 + 工具步骤数 + handoff 等显式工作流步骤
模型轮次 M = 发生模型请求的次数
工具次数 K = 发生工具执行的次数
3. Turn:一次模型轮次
**轮次(turn)**通常指 Agent 与模型之间的一次交互。一个典型工具循环是:
Turn 1:用户问题 → 模型决定调用 search
Tool 1:执行 search
Turn 2:用户问题 + search 结果 → 模型生成最终回答
这次请求有:
M = 2
K = 1
S = 3
如果模型一次返回多个并行工具调用,则仍然可以把模型决策算作一个 turn;工具调用是多个独立的 tool step。
OpenAI Agents SDK 的默认 tracing 层级也区分了 workflow、task、turn、agent、generation、function tool call、guardrail 和 handoff 等对象。SDK 文档明确说明,Trace 表示一个端到端 workflow,Span 表示带有开始和结束时间的操作。(openai.github.io)
4. Trace:一次端到端运行记录
Trace不是日志行的集合,而是一次运行的因果结构:
trace_id = req_20260901_001
├── request_span
│ ├── route_span
│ ├── model_turn_span
│ │ └── generation_span
│ ├── tool_span
│ └── model_turn_span
│ └── generation_span
└── response_span
每个 Span 至少应记录:
span_id
parent_span_id
started_at
ended_at
status
model / tool name
input_tokens
output_tokens
cached_tokens
reasoning_tokens(如果供应商提供)
error_type
retry_count
parent_span_id 很重要。没有父子关系,只能知道某个工具耗时 2 秒,却不知道它属于哪一次模型决策,也无法判断它是否阻塞了最终回答。
二、延迟不是一个数字:TTFT、流式输出和完整响应
1. TTFT 的定义
**TTFT(Time To First Token)**是从请求进入模型服务,到客户端收到第一个可显示输出 Token 之间的时间。
可以形式化为:
注意它不是:
- HTTP 请求建立时间;
- 完整响应时间;
- 用户请求到最终答案时间;
- 模型开始生成内部 Token 的时间。
如果系统在模型前还有路由、鉴权、Prompt 构造,则用户感知的首字节时间可能是:
因此,模型 TTFT 正常,不代表用户一定能快速看到内容。
2. TTFT 与工具调用的特殊关系
假设模型第一轮的输出不是自然语言,而是工具调用:
{
"name": "get_weather",
"arguments": {
"city": "杭州"
}
}
那么第一个 Token 可能很快到达,但用户仍然看不到有意义的回答,因为系统必须等待:
模型首 Token
→ 完整工具参数
→ 工具执行
→ 第二轮模型调用
→ 最终自然语言输出
因此,Agent 至少要区分三种时间:
model_ttft = 某次模型调用的首 Token 时间
answer_ttft = 用户首次收到可展示自然语言的时间
e2e_latency = 请求开始到最终回答完成的时间
例如:
| 阶段 | 耗时 |
|---|---|
| 网关和路由 | 80 ms |
| 第一轮模型 TTFT | 300 ms |
| 第一轮模型完整输出 | 500 ms |
| 工具执行 | 2,000 ms |
| 第二轮模型 TTFT | 350 ms |
| 第二轮模型完成 | 1,100 ms |
| 最终答案首 Token | 2,930 ms |
| 完整响应 | 4,030 ms |
如果只看第一轮模型 TTFT,会误判系统“300 ms 很快”;但用户真正等待了近 3 秒才看到答案。
3. 完整延迟的分解
对一次串行 Agent 运行,可以写成:
其中:
- :排队等待;
- :模型路由与配置加载;
- :第 次模型请求耗时;
- :解析结构化输出、校验参数等耗时;
- :第 次工具调用耗时;
- :最终结果格式化和传输耗时。
如果多个工具并发执行,则工具部分不是简单求和,而是关键路径上的最大值:
例如同时查询库存、价格和优惠:
库存:120 ms
价格:180 ms
优惠:900 ms
并发执行的工具阶段约为:
串行执行则为:
并发节省了约 300 ms,但同时增加了:
- 下游并发压力;
- 超时和取消处理复杂度;
- 多个结果合并的错误路径;
- 工具副作用控制难度。
因此,并发不是“无条件优化”,而是以工具独立、无顺序依赖、资源允许为前提。
三、模型生成为什么影响 TTFT 和完整延迟
1. 输入 Token 影响首 Token,输出 Token 影响尾延迟
将一次模型请求的耗时近似拆成:
其中:
- :输入 Token 数量;
- :输出 Token 数量;
prefill:模型读取和处理输入上下文;decode:逐步生成输出。
通常:
- 输入 Token 增多,主要影响 TTFT;
- 输出 Token 增多,主要影响从首 Token 到完成的时间;
- 长上下文可能同时增加服务端排队和上下文处理成本;
- 推理型模型还可能有额外的 reasoning Token。
这里的“通常”不是供应商 SLA。真实曲线取决于模型架构、服务负载、上下文长度、缓存命中和部署方式,不能把线性公式当成性能保证。
2. Token 的四种计数不能混为一谈
一个 Agent 运行中常见的 Token 维度包括:
input_tokens
output_tokens
cached_input_tokens
reasoning_tokens
其中:
- 输入 Token:发送给模型的 Token;
- 输出 Token:模型返回给应用的可见或结构化输出 Token;
- 缓存输入 Token:命中 Prompt Cache 的输入部分;
- 推理 Token:模型内部用于推理的 Token,是否单独统计取决于模型和 API。
不能用最终回答长度估算总 Token。工具循环会重复发送上下文:
Turn 1 输入:系统提示 + 用户问题
Turn 2 输入:系统提示 + 用户问题 + 工具调用 + 工具结果
Turn 3 输入:系统提示 + 用户问题 + 工具调用 + 工具结果 + 新工具结果
因此总输入 Token 近似为:
如果每一轮都携带历史上下文, 可能不断增长。
3. 一个完整 Token 算例
假设某请求经历两轮模型调用:
| 轮次 | 输入 Token | 输出 Token | 其中缓存输入 |
|---|---|---|---|
| Turn 1 | 2,000 | 180 | 0 |
| Turn 2 | 5,000 | 420 | 1,600 |
| 合计 | 7,000 | 600 | 1,600 |
则:
总输入 Token = 2,000 + 5,000 = 7,000
总输出 Token = 180 + 420 = 600
总模型 Token = 7,600
但账单不应简单按 7,600 个普通 Token 计算,因为第二轮有一部分输入命中缓存。若价格为:
普通输入:$p_in / 1M tokens
缓存输入:$p_cache / 1M tokens
输出:$p_out / 1M tokens
则成本为:
关键点是:缓存命中减少的是计费输入成本和部分上下文处理成本,但不必然消除模型轮次、工具耗时或输出成本。
这也是为什么 Prompt Cache、语义缓存和工具缓存必须分开治理:
- Prompt Cache:减少重复输入上下文的处理和计费;
- 语义缓存:直接复用相似问题的完整结果,可能绕过模型和工具;
- 工具缓存:复用工具结果,但通常仍需一次模型调用来组织回答。
四、步骤数是延迟和成本的乘数
1. 成本模型
设第 次模型调用的输入、输出和缓存 Token 分别为:
- :输入 Token;
- :输出 Token;
- :命中缓存的输入 Token。
设模型价格为:
- :普通输入 Token 单价;
- :缓存输入 Token 单价;
- :输出 Token 单价。
则模型成本为:
工具和基础设施成本为:
其中 不能漏算。一次失败重试可能同时增加:
- 模型 Token;
- 工具调用次数;
- 下游 API 费用;
- 排队和并发占用;
- 失败后的人工或补偿处理。
2. 步骤的放大效应
假设每次模型轮次平均产生:
输入:3,000 Token
输出:500 Token
单轮请求大约处理:
3,500 Token
如果 Agent 平均需要 4 个模型轮次,则不是“回答长度增加 4 倍”这么简单,因为后续轮次往往携带更长的上下文:
| 轮次 | 输入 | 输出 |
|---|---|---|
| 1 | 2,000 | 300 |
| 2 | 4,500 | 250 |
| 3 | 7,000 | 500 |
| 4 | 9,000 | 600 |
总输入为:
总输出为:
虽然最终回答只有 600 个输出 Token,但输入处理已经达到 22,500 Token。Agent 成本常常由上下文重复携带决定,而不是由最终答案长度决定。
3. 反例:减少单轮 Token,却增加总成本
方案 A:
一次强模型调用
输入:8,000
输出:1,000
总 Token:9,000
方案 B:
四次轻模型调用
每次输入:3,000
每次输出:300
总 Token:13,200
方案 B 的单次调用更小,但总 Token 更多;如果工具步骤和失败重试也更多,端到端成本可能反而更高。
所以“短 Prompt”“小模型”“短回答”都不是完整的成本指标,必须计算:
五、工具耗时:模型快,不代表 Agent 快
1. 工具耗时应拆成完整路径
一个工具的耗时至少包括:
例如一个库存工具返回慢,原因可能是:
- Agent 服务等待本地连接池;
- OAuth Token 刷新;
- 下游数据库查询慢;
- 第三方 API 的网络延迟;
- 返回 JSON 过大;
- Schema 校验或结果裁剪耗时。
如果只在工具函数外层记录一个总耗时,就无法确定优化方向。
2. 工具结果会同时影响后续延迟和成本
工具返回 100 条商品记录,可能造成三种放大:
工具响应体变大
→ 第二轮输入 Token 增加
→ 第二轮 TTFT 增加
→ 第二轮输入成本增加
因此,工具接口不应把“原始数据库结果”直接交给模型,而应根据模型任务返回最小充分信息。
例如,不要返回:
{
"items": [
{
"id": "P001",
"name": "...",
"description": "...",
"audit_log": "...",
"warehouse_events": [],
"internal_score": 0.83
}
]
}
如果模型只需要判断是否有货,更适合返回:
{
"available": true,
"quantity": 12,
"warehouse": "HZ-01"
}
这不是单纯的序列化优化。它改变了下一轮模型的输入 ,从而同时影响:
- 下一轮 TTFT;
- 下一轮输入 Token 成本;
- 上下文超限概率;
- 模型从噪声中选错信息的概率。
3. 工具并发的边界
只有满足以下条件时,工具才适合并发:
- 工具之间没有数据依赖;
- 工具没有不可重复的副作用;
- 下游允许并发;
- 任意一个工具失败时,系统定义了部分结果策略;
- 能够对每个工具独立设置超时和取消。
例如:
查询天气、查询空气质量、查询日历
通常可以并发。
但以下流程不应简单并发:
创建订单 → 扣库存 → 扣款
它存在明确的事务顺序和补偿关系。并发会缩短理论耗时,却可能制造重复扣款、库存不一致或无法回滚的问题。
六、预算:不是一个 max_tokens 参数
1. Token 预算
Token 预算限制模型处理规模,常见维度包括:
max_input_tokens
max_output_tokens
max_reasoning_tokens
max_total_tokens
不同 API 和模型对这些参数的支持并不统一,不能假设所有模型都有相同名称和语义。生产系统应以具体模型和 API 文档为准。
在 Agent 层面,更重要的是维护运行时预算:
from dataclasses import dataclass
@dataclass
class Budget:
max_model_turns: int = 4
max_tool_calls: int = 6
max_total_input_tokens: int = 20_000
max_total_output_tokens: int = 4_000
max_wall_time_ms: int = 8_000
max_cost_usd: float = 0.05
这些字段分别限制不同资源:
max_model_turns防止模型反复思考或循环调用;max_tool_calls防止工具风暴;max_total_input_tokens控制上下文重复;max_total_output_tokens控制答案和结构化输出;max_wall_time_ms控制用户等待;max_cost_usd控制直接费用。
只设置输出 Token 上限,无法阻止 Agent 在 10 轮中每轮发送巨大上下文。
2. 时间预算和费用预算要同时存在
设当前运行状态为:
@dataclass
class RunUsage:
model_turns: int = 0
tool_calls: int = 0
input_tokens: int = 0
output_tokens: int = 0
elapsed_ms: int = 0
estimated_cost_usd: float = 0.0
每次准备执行动作前,检查:
def can_execute(usage: RunUsage, budget: Budget, action: str) -> bool:
if usage.elapsed_ms >= budget.max_wall_time_ms:
return False
if action == "model" and usage.model_turns >= budget.max_model_turns:
return False
if action == "tool" and usage.tool_calls >= budget.max_tool_calls:
return False
if usage.input_tokens >= budget.max_total_input_tokens:
return False
if usage.output_tokens >= budget.max_total_output_tokens:
return False
if usage.estimated_cost_usd >= budget.max_cost_usd:
return False
return True
这段逻辑的关键不是代码本身,而是预算检查必须发生在动作执行前。如果模型已经发出请求后才发现超预算,费用和延迟已经发生。
3. 预算需要预留最终回答空间
假设总输出预算为 2,000 Token,前两轮工具决策已经消耗 1,800 Token,那么系统不能再允许模型自由生成最终答案。
应保留一个回答保底预算:
例如:
总输出预算:2,000
最终回答预留:500
中间步骤可用:1,500
否则 Agent 可能在中间生成大量分析后,最终只能截断回答,造成“Token 预算没有超,但业务结果不可用”。
七、降级:在预算耗尽前改变策略
1. 降级不是简单换小模型
**降级(degradation)**是当延迟、成本、下游可用性或资源预算不再允许继续执行原策略时,切换到一个明确的次优路径。
可能的降级动作包括:
强模型 → 快模型
多工具 → 核心工具
实时查询 → 最近一次缓存
完整答案 → 摘要答案
自动执行 → 请求用户确认
多 Agent 协作 → 单 Agent
工具失败后继续推理 → 返回部分结果
降级必须定义“保留什么”和“牺牲什么”。
例如:
| 级别 | 保留 | 牺牲 |
|---|---|---|
| L0 | 完整回答、实时工具、强模型 | 成本和延迟较高 |
| L1 | 完整回答、核心工具 | 非关键工具 |
| L2 | 基于缓存或已有上下文回答 | 实时性 |
| L3 | 简短说明和人工转接 | 自动化程度 |
2. 降级触发条件
设:
- :剩余时间;
- :剩余成本;
- :剩余步骤;
- :下一步预期耗时;
- :下一步预期成本。
只有在以下条件成立时才继续:
并且:
同时:
如果任一条件不满足,就应在下一步前降级,而不是继续执行后再解释超时。
3. 一个具体降级算例
假设:
总时间预算:5,000 ms
当前已用:3,700 ms
剩余时间:1,300 ms
候选动作:
| 动作 | 预期耗时 | 预期成本 | 质量 |
|---|---|---|---|
| 再调用实时搜索 + 强模型 | 2,100 ms | 0.018 美元 | 高 |
| 读取工具缓存 + 快模型 | 650 ms | 0.004 美元 | 中 |
| 直接基于已有上下文回答 | 250 ms | 0.001 美元 | 较低 |
第一种动作满足质量目标,但不满足时间预算:
第二种动作满足预算:
因此选择“工具缓存 + 快模型”是可解释的降级,而不是随机失败。
4. 不应降级的场景
以下操作不应因为预算紧张而静默降级:
- 付款;
- 删除数据;
- 权限变更;
- 医疗、法律或安全关键判断;
- 需要实时一致性的库存扣减;
- 任何不可逆副作用。
这些场景可以:
延迟处理
→ 请求用户确认
→ 转人工
→ 返回明确失败
但不能把“实时校验”悄悄换成过期缓存后继续执行副作用。
八、模型路由与回退:成本优化必须保持语义约束
1. 路由的本质
模型路由不是“贵模型处理难问题,便宜模型处理简单问题”这么简单。路由器实际要判断:
任务类型
输入长度
是否需要工具
是否需要结构化输出
风险等级
延迟预算
成本预算
当前模型健康度
可以把候选模型表示为:
其中:
- :模型在该任务上的预期延迟;
- :预期成本;
- :质量或成功率;
- :业务权重。
但这个优化必须受硬约束限制:
如果某模型无法稳定输出工具所需的 Schema,即使它便宜、快速,也不应被路由到该步骤。
2. 回退必须区分“重试”和“切换”
以下是不同操作:
同一模型、相同参数再次请求 = 重试
同一模型、减少上下文后请求 = 修复后重试
切换到另一个模型 = 回退
跳过工具、返回部分结果 = 降级
它们的成本和故障含义不同。
例如:
429 限流 → 延迟重试或切换健康模型
结构化输出解析失败 → 修复 Schema 提示后重试
工具超时 → 工具级回退或使用缓存
预算不足 → 降级,不应盲目重试
“预算不足”不是瞬态错误。对它进行无条件重试,只会增加成本。
3. 回退的语义风险
假设强模型会调用:
get_current_price(product_id)
快模型则直接回答价格。
如果回退后仍然允许它输出价格,就可能把“无法实时查询”误写成“已查询到价格”。因此模型回退必须传递能力边界:
当前模式:degraded
实时价格工具:不可用
允许输出:缓存价格,并标记时间
禁止输出:声称已完成实时查询
降级状态应成为 Agent 上下文的一部分,而不是只存在于网关日志中。
九、缓存与路由:三种缓存解决三个不同问题
1. Prompt Cache
Prompt Cache 复用重复的输入前缀。它适合:
稳定系统提示
固定工具 Schema
固定领域规则
重复的长上下文前缀
它主要减少:
- 重复输入的处理;
- 部分输入计费;
- 长 Prompt 对 TTFT 的影响。
它不能直接减少:
- 模型轮次;
- 工具调用次数;
- 动态用户输入;
- 输出 Token;
- 工具执行时间。
2. 语义缓存
语义缓存根据问题语义相似度复用完整答案。
例如:
“杭州今天下雨吗?”
“今天杭州会下雨吗?”
可能命中同一缓存结果。
但语义缓存必须绑定:
查询意图
用户权限
数据时间
工具版本
模型版本
Prompt 版本
答案有效期
否则会出现:
语义相似
≠
业务结果可复用
“查询订单状态”和“查询订单详情”语义相近,但返回内容、权限和新鲜度要求不同,不能只依赖向量相似度决定复用。
3. 工具缓存
工具缓存只缓存工具结果:
天气 API 结果
商品库存查询
汇率查询
知识库检索结果
它保留了模型重新组织答案的机会,因此比完整语义缓存更安全,但仍要定义 TTL 和失效条件。
对于工具缓存,缓存键通常应包含:
tool_name
tool_version
normalized_arguments
tenant_id
permission_scope
freshness_policy
不能只用:
get_weather("杭州")
作为键,因为不同租户、不同区域、不同权限或不同数据新鲜度策略可能要求不同结果。
十、用 Trace 找出真正的慢点和贵点
1. 最小观测字段
每次 Trace 建议形成如下摘要:
{
"trace_id": "trace_20260901_0001",
"workflow": "order_assistant",
"status": "degraded",
"e2e_latency_ms": 4820,
"answer_ttft_ms": 2710,
"model_turns": 2,
"tool_calls": 2,
"input_tokens": 8600,
"output_tokens": 720,
"cached_input_tokens": 4200,
"estimated_cost_usd": 0.031,
"degrade_reason": "remaining_time_budget_low",
"spans": [
{"type": "model", "latency_ms": 640},
{"type": "tool", "name": "get_order", "latency_ms": 980},
{"type": "tool", "name": "get_coupon", "latency_ms": 1500},
{"type": "model", "latency_ms": 1700}
]
}
这样可以区分:
- 首轮模型慢;
- 工具慢;
- 第二轮输入太大;
- 多次轮次造成总成本;
- 已经发生了降级;
- 降级是否发生得太晚。
2. Agents SDK Tracing 的使用方式
下面示例展示一个显式 Trace 和最终 flush。它不是业务预算实现,而是把一次工作流包裹在统一观测边界内:
import asyncio
from agents import Agent, Runner, RunConfig, flush_traces, trace
agent = Agent(
name="Order assistant",
instructions=(
"根据订单工具返回的信息回答用户。"
"如果工具不可用,不要声称已经查询到实时结果。"
),
)
async def main() -> None:
try:
with trace("order_assistant"):
result = await Runner.run(
agent,
"查询订单 ORD-1001 的状态",
run_config=RunConfig(
tracing={"include_task_and_turn_spans": True}
),
)
print(result.final_output)
finally:
# 适合短生命周期任务或需要在任务结束时立即看到 Trace 的场景
flush_traces()
if __name__ == "__main__":
asyncio.run(main())
代码的生命周期是:
trace("order_assistant")创建工作流级 Trace;Runner.run执行 Agent;- SDK 为模型轮次、生成、工具、guardrail 等操作创建 Span;
- Trace 上下文退出后,结构完整;
flush_traces()将缓冲中的 Trace 导出。
Agents SDK 默认启用 tracing,并自动记录 Runner、task、turn、agent、generation、function、guardrail 和 handoff 等 Span;默认导出通常是后台批量进行的,长生命周期任务如果要求任务结束后立即可见,可以显式调用 flush_traces()。(openai.github.io)
需要注意,Trace 不等于账单真相。成本仍应使用模型响应中的实际 usage、供应商账单或内部计价表计算;Trace 负责提供因果结构和时序,不能凭 Span 时长推导 Token 费用。
3. 如何从 Trace 定位问题
观察以下两个请求:
请求 A:
model_turns=1
tool_calls=0
input_tokens=4,000
e2e=1,200 ms
cost=$0.018
请求 B:
model_turns=3
tool_calls=2
input_tokens=18,000
e2e=4,900 ms
cost=$0.041
如果请求 B 的最终回答质量只比 A 高一点,优先检查:
- 是否能把两个独立工具并发;
- 是否把工具结果裁剪到最小字段;
- 是否存在重复搜索;
- 是否可以在第一轮直接完成;
- 是否误把一次解析失败当成完整重试;
- 是否应该在第二轮切换到便宜模型;
- 是否有语义缓存或工具缓存可命中。
OpenAI 的评测建议也是先从 Trace 调试工作流行为,再将代表性 Trace 固化为数据集和可重复的评测运行;这使延迟、步骤、工具选择和质量可以一起比较,而不是只看单个性能数字。(developers.openai.com)
十一、把延迟和成本纳入评测,而不是只做线上监控
1. 质量不能脱离资源约束
一个 Agent 版本可能在离线准确率上提升,但同时:
步骤数:2.1 → 4.8
输入 Token:6,000 → 19,000
P95 延迟:3.2 s → 8.7 s
平均成本:0.018 → 0.063 美元
如果业务 SLA 是 5 秒,这不是单纯的质量升级,而是不可上线的策略变化。
评测结果至少应同时包含:
quality_success_rate
tool_selection_accuracy
model_turns
tool_calls
input_tokens
output_tokens
p50 / p95 / p99 latency
answer_ttft
cost_per_request
fallback_rate
degradation_rate
2. 质量与成本的 Pareto 边界
设一个版本的结果为:
其中:
- :质量;
- :延迟;
- :成本。
如果版本 B 满足:
且至少一个严格更优,则 B 支配 A。
但真实系统中经常没有支配关系:
版本 A:质量高、慢、贵
版本 B:质量中、快、便宜
版本 C:质量略低、延迟中等、成本最低
这时不能用“平均分”掩盖取舍,应按请求类型分层:
高风险订单:A
普通问答:B
低价值批量任务:C
3. 反例:平均值掩盖尾部风险
两个版本的平均延迟都是 2 秒:
| 版本 | P50 | P95 | P99 |
|---|---|---|---|
| A | 1.8 s | 2.5 s | 3.2 s |
| B | 0.8 s | 5.9 s | 18 s |
如果只看平均值,会认为两者相同;但 B 可能存在:
- 少量工具超时;
- 特定输入触发长上下文;
- 某个模型回退循环;
- 并发下游连接池耗尽。
Agent 生产运营必须优先分析尾延迟,因为用户感知和 SLA 违约通常由 P95、P99 决定。
十二、常见误解与失败表现
误解一:TTFT 低,所以 Agent 延迟低
失败表现:
首轮模型 200 ms 返回工具调用
用户 3 秒后才看到最终答案
原因是把模型 TTFT 当成了 answer TTFT。
修复方式是同时记录:
model_ttft
answer_ttft
e2e_latency
误解二:输出短,所以成本低
失败表现:
最终回答只有 100 Token
但每次请求发送了 30,000 Token 的重复历史
原因是忽略了输入 Token 和多轮上下文。
误解三:工具调用不计模型 Token
失败表现:
工具响应 2 MB
第二轮模型输入暴涨
工具本身可能没有模型 Token 费用,但工具结果被送回模型后会形成新的输入 Token。
误解四:超时就重试
失败表现:
模型超时
→ 重试
→ 工具超时
→ 再次调用模型
→ 最终仍超时
如果剩余时间已经不足,重试只会增加成本和拥塞。应先判断:
是否为瞬态错误?
剩余时间是否足够?
重试是否幂等?
重试后的成功概率是否值得成本?
误解五:换小模型就是降级完成
失败表现:
小模型无法正确调用工具
或者输出不符合 Schema
系统却继续执行
真正的降级必须同时改变能力契约:
允许的工具集合
输出格式
实时性承诺
可执行副作用
错误提示
十三、一个可落地的运行时决策顺序
一次 Agent 运行可以按照以下顺序推进:
1. 接收请求
2. 创建 Trace 和预算
3. 进行任务分类与模型路由
4. 检查缓存
5. 执行模型轮次
6. 解析工具调用
7. 在调用工具前检查时间、次数和成本预算
8. 并发执行无依赖工具
9. 裁剪和校验工具结果
10. 再次检查预算
11. 继续模型轮次或触发降级
12. 返回最终结果
13. 记录实际 Token、延迟、成本、回退和质量标签
每一步都应更新状态:
state = {
"mode": "normal", # normal / degraded / failed
"model_turns": 1,
"tool_calls": 2,
"input_tokens": 8600,
"output_tokens": 420,
"elapsed_ms": 3200,
"estimated_cost_usd": 0.021,
"available_tools": ["get_order"],
"freshness": "realtime"
}
状态变化示例:
normal
→ remaining_time < expected_next_step
→ degraded
→ disable noncritical tools
→ use cached tool result
→ fast model
→ final answer
其中 mode、available_tools 和 freshness 必须影响后续模型提示或编排逻辑。否则系统虽然在内部进入了降级状态,模型却仍会按照正常能力继续规划,最终造成“承诺超过实际能力”。
十四、生产基线应回答的核心问题
一个可以运营的 Agent,不应只回答“成功率是多少”,还应能回答:
延迟
用户看到首个有效答案用了多久?
模型首 Token 慢,还是工具慢?
P95 延迟主要由哪类工具或哪种模型造成?
步骤
平均和 P95 模型轮次是多少?
哪些任务出现异常循环?
一次额外轮次带来了多少质量收益?
Token
输入、输出、缓存输入和推理 Token 各是多少?
上下文是否在轮次间不必要地增长?
工具结果是否成为主要输入来源?
成本
每请求成本分布是什么?
重试成本占比多少?
便宜模型是否因为更多步骤而失去优势?
缓存命中是否真的减少了总成本?
工具
哪个工具位于关键路径?
工具失败后是否触发模型重复调用?
工具结果是否过大?
哪些工具可以安全并发?
预算与降级
预算耗尽前是否提前降级?
降级后是否遵守能力边界?
哪些请求不允许使用过期缓存?
回退是否保持了 Schema、权限和副作用语义?
当这些问题都能从 Trace、usage、账单和评测数据中得到答案时,Agent 的延迟与成本才真正进入工程治理范围。单独优化 TTFT、单独压缩 Prompt 或单独切换小模型,都只能改善局部指标;真正决定生产表现的是完整工作流的关键路径、资源预算和失败后的下一步选择。
系列导航与关联阅读
- 系列入口:Agent 工程完整路线:从运行循环、记忆与协议到安全、评测和生产交付
- 上一篇:Agent 测试与重放:模型替身、工具 Stub、确定性、录制和回归
- 下一篇:Agent 缓存与路由:Prompt Cache、语义缓存、工具缓存和失效
- 延伸:Agent 模型选择与路由:能力、延迟、成本、回退和稳定性
官方资料
本文依据 Agent、模型、协议与框架官方资料重新梳理;正文、示例与生产清单由 WR BLOG 编写。

评论
0 条讨论