跳到主要内容

端到端延迟并非你的 LLM 调用 P99:代理系统中无人衡量的隐藏乘数

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的 LLM API 调用在 P99 分位下于 500 毫秒内完成。但你的用户却在等待 12 秒。这两个数字都是准确的,谁都没有撒谎——它们只是在测量完全不同的东西。两者之间的差距正是大多数 Agent 系统性能无声流失的地方,而且大多数团队从未对其进行过监控(instrumentation)。

问题是结构性的:P99 LLM 延迟是一个应用于多步执行模型的单次调用指标。一个 ReAct Agent 进行五次连续的工具调用、重试一个幻觉化的函数、组装不断增长的上下文并生成 300 个 token 的推理链,这并不是一次 LLM 调用。这是一个分布式工作流,其中 LLM 只是一个节点,而其他每个节点都有其自身的延迟开销。

Agent 请求的解剖

考虑一个简单的 Agent 任务:“寻找与主题 X 最相关的三篇文档,总结每一篇,并返回一个排序列表。”用户提交这个任务时期望在几秒钟内得到结果。实际发生的情况是:

  1. 提示词构建:系统组装工具 schema、对话历史和系统提示词(根据上下文大小,耗时 100–500 毫秒)
  2. LLM 调用 1:模型生成 search 工具调用(300–800 毫秒)
  3. 工具往返:搜索在网络上执行(100–300 毫秒)
  4. LLM 调用 2:模型接收结果,决定 fetch 文档 A(300–800 毫秒)
  5. 工具往返:执行获取(200–500 毫秒)
  6. 对文档 B 和 C 重复上述步骤
  7. LLM 最终调用:生成 400 个 token 的总结(300 毫秒推理 + 约 15 秒解码时间)

即使在乐观的情况下,这个工作流也会超过 10 秒。你 500 毫秒的 P99 LLM 延迟大约仅占总耗时的 15%。在这种情况下报告“我们的模型延迟是 500 毫秒”,就像报告数据库查询时间而忽略 ORM、网络和 HTTP 栈一样。

无人衡量的四个乘数效应

1. 顺序工具往返

每次工具调用都是一个阻塞的同步操作:生成调用 → 网络 → 外部 API → 解析响应 → 下一个模型步骤。对于一个速度中等的外部 API,每次往返耗时 200 毫秒,五次连续的工具调用就会增加整整一秒的纯等待时间——这还没算上每次调用之间的模型推理时间。

这种计算方式的扩展性很差。在生产环境中,连续进行十次工具调用的 Agent 仅网络开销就会累积 2–4 秒。大多数监控仪表盘从未将其拆解出来。延迟表现为 LLM 调用之间的延迟,由于单次调用本身都不慢,因此对于任何观察单次调用 P99 的人来说,这个问题是不可见的。

解决方案是存在的:现代 LLM API 支持并行工具调用,即模型在单个响应中返回多个工具调用以进行并发执行。Salesforce 对深度研究 Agent 的研究发现,随着并行工具调用数量的增加,性能会持续提升,对于多步任务,实际耗时可减少 40–60%。权衡之处在于,并行执行要求模型能够判断哪些工具是独立的——并非所有的工具调用序列都能安全地并行化。

2. 幻觉工具调用引发的重试级联

这是最致命的乘数效应,因为它既庞大又伪装成可靠性问题。当 Agent 幻觉出一个工具名称或构建了一个无效的参数 schema 时,系统的重试处理器会将其视为临时的网络故障,并应用相同的指数退避(exponential backoff)逻辑。

对生产环境中 ReAct Agent 日志的分析发现,在某些工作负载中,幻觉工具调用消耗了超过 90% 的重试配额。每个重试周期都会增加一轮完整的 LLM 推理(300–1500 毫秒)以及退避延迟。对单次幻觉调用进行三个重试周期,会在 Agent 进入真正的工具执行之前增加 3–8 秒。

更深层次的问题在于监控方式:团队将“重试次数”衡量为可靠性指标,而不是延迟贡献因素。重试处理器被触发,错误被捕获,指标增加——但实际的时间成本从未出现在延迟仪表盘上。它被归类为故障恢复事件,而不是性能事件。

区分错误分类有助于解决这个问题。幻觉工具调用的错误签名与临时故障不同(例如 TOOL_NOT_FOUND 与超时、连接拒绝)。如果断路器(circuit breaker)对幻觉类错误的计数方式不同于基础设施错误,就可以停止应用那些专为错误故障类别设计的重试延迟。

3. 提示词构建开销与上下文膨胀

在 Agent 循环中的每次 LLM 调用之前,必须有人组装提示词:提取对话历史、注入工具 schema、格式化工具结果、应用系统提示词。在具有完整历史记录和五个工具 schema 的十轮对话中,上下文很容易达到 8,000–10,000 个 token。

延迟成本是双重的。首先,提示词构建本身在每次迭代中会消耗 100–500 毫秒的 CPU 时间,用于解析、模板渲染和序列化。这很容易被忽略,因为它发生在应用程序代码中,而不是 LLM 调用跨度(spans)中。其次,更长的提示词意味着更长的预填充(prefill)阶段。预填充阶段——在生成任何输出之前处理整个输入上下文——随着 token 数量的增加而增加。10,000 个 token 的预填充比 2,000 个 token 的预填充耗时明显更长,这一时间在 LLM 侧表现为“首字延迟”(TTFT),而其根源则是应用程序侧的上下文增长。

多 Agent 系统进一步放大这一问题。对编排者-子 Agent 架构的研究发现,与没有经过优化的同等单次调用实现相比,token 数量会增加 4–15 倍。在某些配置中,检查子 Agent 输出的验证步骤消耗了多达 72% 的总 token。这些都不会表现为单次调用延迟异常——单次调用看起来很正常,但它们的数量和输入大小会产生复利效应。

4. 随着输出长度变化的 Decode 时间扩展

Prefill(预填充)和 Decode(解码)是本质上不同的计算方式,而大多数延迟测量会将两者混为一谈。Prefill —— 读取输入提示词 —— 是高度可并行的。Decode —— 一次生成一个输出 token —— 是顺序进行的,且受内存带宽限制。

在每个输出 token 耗时 30–50 ms 的情况下,一个 500 token 的推理链会增加 15–25 秒的纯 Decode 时间。TTFT(首字延迟)指标看起来可能不错(模型响应很快),但响应的尾部在 20 多秒后才会到达。这对于使用思维链(Chain-of-Thought)推理的智能体来说至关重要,因为模型在生成最终工具调用或答案之前会进行“大声思考”。

实际意义在于:一个产生较短中间推理(每步 token 较少)的智能体配置,其端到端延迟通常优于推理更详尽的配置,即使每步的准确率略低。输出 token 数量与响应质量之间的权衡是真实存在的,但通常未被纳入观测。

如何正确衡量端到端智能体延迟

行业已达成共识,将 OpenTelemetry 作为合适的底层框架,并采用了 OpenTelemetry 生成式 AI 特别兴趣小组(SIG)在 2024 年标准化的 gen_ai.* 语义规范。Span 模型清晰地映射了上述倍数关系:

Trace: "User Query" (用户查询)
├─ Span: prompt_construction (提示词构建) (100-500ms)
├─ Span: agent_loop_iteration[1] (智能体循环迭代[1])
│ ├─ Span: llm_call (LLM 调用,分别记录 prefill 和 decode)
│ └─ Span: tool_call[search] (工具调用[搜索],网络 + 执行)
├─ Span: agent_loop_iteration[2] (智能体循环迭代[2])
│ ├─ Span: llm_call
│ └─ Span: tool_call[fetch] (工具调用[抓取])
└─ Span: agent_loop_iteration[3] (智能体循环迭代[3])
└─ Span: llm_call (最终生成)

每个 Span 都会捕捉模型名称和版本、输入和输出 token 计数、分别记录的 TTFT 和 TPOT、工具调用签名以及重试元数据。核心纪律是独立衡量这四个层级:

  • 智能体循环层 (Agent loop layer):总实际墙钟时间、迭代次数、消耗的重试预算
  • LLM 层:TTFT(Prefill 代理)、TPOT(Decode 速率)、输出 token 计数
  • 工具层 (Tool layer):调用延迟(包括网络往返)、执行时间、错误类型
  • 上下文层 (Context layer):单次迭代的提示词大小、组装时间、token 增长率

像 Langfuse、OpenLLMetry 和 Arize Phoenix 这样的平台拥有原生的 OpenTelemetry 后端,可以针对常用框架自动生成这种结构,无需手动埋点。观测缺口通常出现在工具层:大多数现成的追踪工具可以处理 LLM 调用,但当执行进入自定义工具实现时,往往会丢失 Span。

数据背后的真实情况

一个拥有五个工具、未经过延迟优化且运行在中级 LLM 上的研究型智能体:

组件耗时
提示词构建(×5 次迭代)500 ms
LLM 推理(×5 次调用,P50)2,500 ms
工具往返(×5 次顺序调用)1,000 ms
重试周期(1 次幻觉)1,500 ms
400 token 最终输出的 Decode 时间16,000 ms
总计~21 秒

P50 LLM 推理单次调用为 500 ms,总计 2,500 ms。这仅占端到端时间的 12%。如果将 P50 LLM 延迟作为用户体验的代理指标进行报告,在功能上是具有误导性的。

应用高 ROI(投资回报率)的优化后,情况会发生实质性变化:

  • 并行工具调用:将工具往返时间缩短 60%,节省约 600 ms。
  • 针对静态上下文的提示词缓存:减少 30–50% 的 Prefill 时间,并消除大部分提示词组装开销。
  • 更短的推理链(200 token 而非 400):节省约 7 秒的 Decode 时间。
  • 重试分类(区分幻觉与基础设施故障):消除大部分重试延迟。

仅仅通过设计能够引导出更短、更直接推理的提示词来减少 Decode 时间,通常是你能使用的最大杠杆。而大多数团队从未尝试过这一点,因为他们没有将 Decode 时间与 TTFT 分开衡量。

组织层面的问题

将 P99 LLM 延迟作为主要性能指标的团队正在优化错误的东西。该指标虽然精确、可复现,但对于用户在多步智能体工作流中的体验几乎毫无意义。

组织的激励结构使情况变得更糟。LLM 延迟易于观测,也易于在仪表盘中解释。而端到端智能体延迟需要跨服务边界进行追踪,将 LLM 调用 Span 与工具执行 Span 相关联,并区分 Prefill 和 Decode 时间。这是一个分布式系统问题,需要分布式系统工具——而大多数 ML 团队并不具备这些工具。

那些缩小了这一差距的团队——通过将智能体延迟视为 SRE(站点可靠性工程)问题而非单纯的 ML 问题——都发现了同样的事实:瓶颈几乎从未出现在 LLM 调用本身。它出现在编排层、工具执行模式以及围绕 LLM 调用的上下文增长中。修复这些问题不需要更快的模型,而是需要能让这些问题显现出来的观测手段。

第一步与分布式系统领域一直以来的一样:衡量用户的实际体验,而不是衡量那些容易衡量的指标。P99 LLM 延迟只是一个组件指标。你需要的是端到端的实际耗时,并根据其组成层级进行拆解。

References:Let's stay in touch and Follow me for more thoughts and updates