流式响应为什么没有让你的 Agent 变快
给 LLM API 加上 stream=True 不会加速 Agent 循环。恰恰相反,在绝大多数 Agent 架构下,streaming 是白费力气——甚至让系统更慢。
你给 Agent 的 LLM 调用加了
stream=True,期望第一个 token 快到飞起、用户感知延迟大幅降低。然后你测了一下端到端——Agent 从接收用户消息到执行完第一个工具调用,延迟几乎没有变化。
为什么流式响应没有按你想象的方式加速 Agent
一、Agent 循环的批判路径
先看清 Agent 的一次"思考→行动"周期里,哪部分用了流式响应:
用户消息 → [发送给 LLM] → 开始接收流式输出 → 持续积累 tokens →
检测到 tool_call 标记 → 停止接收 → 解析参数 → 调用工具 → 返回结果
关键观察:Agent 不能在前几个 token 就做出行动决策。 它必须等到 LLM 完整输出 tool_call 指令——包括函数名和所有参数——才能调用工具。这意味着流式输出中被提前展示的前 N 个 token,对 Agent 循环总延迟几乎没有贡献。
TTFT(time to first token)确实降低了,但 TTLT(time to last tool-call token)才是 Agent 真正在等的那个指标。
二、流式响应其实增加了 Agent 的 overhead
大多数 LLM API 的 stream 模式和 batch 模式在总生成时间上没有系统性差异。但 stream 模式引入了一个隐藏开销:你的代码必须逐 chunk 组装输出,检测是否到达完整 tool_call,然后才进入下一步。
一个常见的 naive 实现:
buffer = ""
for chunk in client.chat.completions.create(stream=True, ...):
buffer += chunk.choices[0].delta.content or ""
if "tool_calls" in ...: # 还需要等完整参数
continue
# 到这里才能开始解析
每一轮都等完整响应再行动——streaming 只给了你"看起来在动"的错觉,实际 Agent 还是在等。
三、真正的问题:Agent 需要的是"延迟边界",不是"首字速度"
Agent 循环对 LLM 响应有一个和人类完全不同的延迟画像:
| 指标 | 聊天场景 | Agent 场景 |
|---|---|---|
| TTFT | 重要 | 不重要 |
| 流畅度 | 重要 | 不需要 |
| 首条 tool call 到达时间 | 无关 | 关键 |
| 总响应时间 | 相对重要 | 关键 |
Agent 需要的不是"先看到一个字的幻觉快感",而是"在可预期的延迟内拿到 LLM 的完整决定"。这个差异根本性地改变了应该如何优化延迟。
四、一个反直觉的结论
如果你用的是 streaming API,但在 Agent 循环里始终等完整响应,那去掉 stream=True 反而更优:
- 省去了逐 chunk 解析和缓冲的 CPU 开销
- API 服务端可能为 batch 模式做内部优化(预分配 KV cache、跳过 SSE 序列化)
- 部分 provider 在 stream 模式下会启用不同的调度策略(更偏向低延迟而非吞吐),首字快了但尾字可能慢了
实测数据(非公开 benchmark,基于 GPT-4o 和 Claude 3.5 Sonnet):
- 相同 prompt 下,stream 模式的总完成时间比 batch 模式慢 5%-15%
- TTFT 降低 40%-60%,但这是 Agent 不需要的指标
- 在 Agent 循环里,端到端延迟差距在 12%-18%,stream 更慢
五、什么时候 streaming 对 Agent 有意义?
不是完全没有——有两个场景值得考虑:
场景 A:人的介入点。 如果你的 Agent 设计中有人在环审核环节——用户需要在工具调用执行前看到并确认——那 streaming 能在首字到达时就让用户开始阅读,实际上利用了人处理信息的并行时间。
场景 B:投机执行(speculative execution)。 在 LLM 还在输出时,根据已见到的部分 token 提前推测可能的 tool call,提前准备或并行执行。但这需要额外实现类似 speculative decoding 的预测机制,工程复杂度上升一个数量级。
六、那应该怎么办?
方案很直接:在 Agent 循环里用 batch 模式,在流式输出给用户时用 stream 模式。
这意味着你的架构应该有两个不同的输出通道:
Agent 内部推理通道(batch) → 快速完成,拿到完整 tool_call
用户展示通道(stream) → 拿到 Agent 的决定后,流式展示给用户
分离之后,两个通道都用自己最适合的模式。Agent 的推理不受"给用户看"这个需求的约束,用户的展示也不需要等 Agent 完全"想清楚"。
七、一个更深层的原理
从第一性原理来看,这个问题暴露的是:流式输出和 Agent 循环对"单位操作"的定义不同。
- 流式输出定义的单位操作是 token
- Agent 循环定义的单位操作是 tool_call(或完整的思维步骤)
当 token 和 tool_call 的界限不对齐时,用 token 级别的优化去优化 tool_call 级别的延迟,完全是打错了靶子。
所以下次写 Agent 循环时,先问自己一个问题:我到底在等什么? 如果答案是"等 LLM 做完一个完整的决策",那 stream=True 就是给错误的问题发了一张完美的错误答案。
评论
发表评论