06 - 流式响应中的 usage 字段¶
面向对象:调 LiteLLM proxy /v1/chat/completions 流式接口、想在客户端拿到 prompt_tokens / completion_tokens / total_tokens 的下游开发人员。
核心结论:流式响应默认不返回 usage 字段,这是 OpenAI 协议标准行为。要拿到 usage 必须显式传 stream_options.include_usage=true。
1. 行为矩阵¶
下表是 scripts/test_chat_completions_usage_field.py 对本 proxy 上 8 个能跑通的模型逐个验证的结果,所有模型在同一模式下表现完全一致:
| 调用方式 | 客户端能拿到 usage? | 说明 |
|---|---|---|
stream=false(非流式) |
✅ 永远有 | response 顶层 usage 字段总在 |
stream=true + stream_options.include_usage=true |
✅ 永远有 | 在 [DONE] 之前会多发一个 choices=[] 的 chunk,里面带 usage |
stream=true(未指定 stream_options) |
❌ 不会有 | OpenAI 默认行为 |
stream=true + stream_options.include_usage=false |
❌ 不会有 | 客户端显式不要 |
重要前提:proxy 内部计量不受影响。无论客户端传不传 include_usage,proxy 都会在内部把所有 chunk 聚合一次算出 usage,并写入 LiteLLM_SpendLogs / Prometheus / 任何已配置的 success_callback。扣费、限额、审计、监控 永远是准确的,受影响的只是 HTTP 响应给客户端这一段。
代码依据:
- 决定是否把 usage chunk 发给客户端:litellm/litellm_core_utils/streaming_handler.py:181-185 的
check_send_stream_usage - 内部 usage 计算(无论客户端有没有要):litellm/litellm_core_utils/streaming_handler.py:1808-1811,进的是
response._hidden_params["usage"],不会进 SSE - OpenAI 上游也是同样规则:参见 OpenAI Chat Completions API
stream_options参数
2. 流式 + include_usage=true 的 SSE 形态¶
携带 stream_options.include_usage=true 时,SSE 的最后两条数据形如:
data: {"id":"...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":null}
data: {"id":"...","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":35,"completion_tokens":5,"total_tokens":40}}
data: [DONE]
注意:
1. 倒数第二条 chunk 的 choices 是空数组 [],仅承载 usage。很多手写的流式解析逻辑会假设每个 chunk 都有 choices[0],遇到这条会 IndexError,需要先判空。
2. 之前所有正常的 delta chunk 里也会带 "usage": null(不会缺这个 key),但只有最后一个 chunk 里 usage 是真值。
3. [DONE] 之后没有 chunk。
3. 各客户端怎么传¶
3.1 curl¶
curl -N http://your-proxy/v1/chat/completions \
-H "Authorization: Bearer $LITELLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role":"user","content":"hi"}],
"stream": true,
"stream_options": {"include_usage": true}
}'
3.2 openai-python(v1.x)¶
from openai import OpenAI
client = OpenAI(base_url="http://your-proxy/v1", api_key="...")
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "hi"}],
stream=True,
stream_options={"include_usage": True}, # ← 关键
)
for chunk in stream:
if chunk.usage is not None:
print("FINAL USAGE:", chunk.usage)
elif chunk.choices:
delta = chunk.choices[0].delta
if delta.content:
print(delta.content, end="")
openai-python>=1.26 才支持 stream_options 参数;老版本不会报错,会被 SDK 直接丢掉。
3.3 langchain-openai¶
ChatOpenAI 默认不传 include_usage,需要在构造时显式开:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
base_url="http://your-proxy/v1",
model="gpt-4o-mini",
streaming=True,
stream_usage=True, # langchain-openai >= 0.1.9 暴露的开关
)
stream_usage=True 内部会自动注入 stream_options={"include_usage": True}。
3.4 LangGraph / LangChain.js¶
同上,构造 ChatOpenAI 时传 streamUsage: true。
3.5 axios / fetch / httpx 手写 SSE¶
只要在 body 里加 "stream_options": {"include_usage": true} 即可,没有 SDK 层差异。
4. 常见错误模式¶
| 现象 | 根因 | 解决 |
|---|---|---|
| 流式响应里没有 usage chunk | 客户端没传 include_usage=true |
加上 stream_options.include_usage=true |
| 同样的代码,非流式有 usage、流式没有 | 流式分支没传 stream_options | 流式分支独立加 stream_options.include_usage=true |
| 升级 SDK 后 usage 突然丢失 | 部分 SDK 在某个版本前会自动注入 include_usage,新版本变成显式 opt-in |
检查 SDK changelog,显式传 stream_options |
| 自己解析 SSE 时在最后一个 chunk 上 IndexError | usage chunk 的 choices 是 [] |
解析时先判 if chunk["choices"]: ... 再取 choices[0] |
usage chunk 里 prompt_tokens / completion_tokens = 0 |
上游 provider 未返回 token 计数,LiteLLM 在某些 provider 下会回退到 0 | 单独排查该 provider,提 issue |
5. 如果你只是想做计费 / 审计 / 监控¶
别想从客户端响应里抓 usage。正确路径:
- 数据库:查
LiteLLM_SpendLogs表,按request_id/api_key维度都能查到 usage - Prometheus:
litellm_input_tokens_total/litellm_output_tokens_total指标 - S3 logs:
standard_logging_payload里有prompt_tokens / completion_tokens / total_tokens完整字段
这些数据源不依赖客户端是否传 include_usage,永远准确。
6. 历史背景¶
如果你之前看到"返回结果有时候没有 usage 字段"的现象,最可能的来源是:
- 同一服务里不同业务方用了不同的 SDK / 调用方式,有的传了
include_usage,有的没传 - 同一段代码在非流式路径下能拿到 usage,切到流式就拿不到了
- SDK 升级前后默认行为变化
这不是间歇性 bug——在固定调用方式下 LiteLLM 的行为完全确定,与上游 OpenAI 协议保持一致。回归脚本 scripts/test_chat_completions_usage_field.py 用本 proxy 上 8 个不同 provider 的模型验证过,所有模型在 4 种模式下的结果与本文表格完全吻合。