请求体里带上 "stream": true 即可,返回 Content-Type: text/event-stream:
curl -N https://aiapizz.com/v1/chat/completions \
-H "Authorization: Bearer sk-你的密钥" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","stream":true,"messages":[{"role":"user","content":"数到 10"}]}'
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "数到 10"}],
stream=True,
stream_options={"include_usage": True}, # 想拿到 token 用量就加上
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
curl 一定要加 -N(禁用本地缓冲),否则看起来像「一次性返回」。
标准 OpenAI SSE:每行以 data: 开头,块之间空行分隔,最后以 data: [DONE] 结束。
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"向"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
请求里带 "stream_options": {"include_usage": true} 时,结束前还会多一个 choices 为空、usage 有值的块(网关对 OpenAI 渠道会自动补上这个参数,保证流式也能准确计费)。
网关有两种流式实现,行为不一样:
| 情况 | 实现 | 表现 |
|---|---|---|
| 请求格式 == 上游格式 | 真流式直通,边收边发 | 首字延迟低,逐字吐出 |
| 请求格式 != 上游格式 | 先拿到完整响应,再按你的格式切块包装成 SSE | 首字延迟 = 整段生成时间,之后一次性刷出(看着「像」流式) |
三种格式(OpenAI / Anthropic / Gemini)都能包装,请客户端用哪个格式,包装出来就是哪个格式的事件流,不用避开任何组合。
所以:要真流式(首字快),就选一个上游格式与你请求格式一致的模型(模型广场里详细页会写明支持的格式);跨格式能用,只是首字要等整段生成完。
X-Accel-Buffering: no,但部分配置下仍被覆盖,建议在站点配置里对 PHP 加上:fastcgi_buffering off;
fastcgi_read_timeout 600s;
# 客户端(AI 工具)中途关掉时,不要让 nginx 替你掐断后端 ——
# 掐断了网关就没机会走完结算,上游已经算的这笔钱就白亏了。
fastcgi_ignore_client_abort on;
max_execution_time。网关在收到上游响应前就已调 set_time_limit(0),若 php.ini 里开了 disable_functions 或 max_execution_time 被硬限制,仍需在宝塔「PHP 设置」里放宽。另外把 request_terminate_timeout 设得比最长回答更长(或设 0),它到点会直接杀进程,那种情况下网关的兜底结算也来不及跑;pm.max_children 调大一些;流式与非流式的计价方式完全一致,按最终 usage 结算;上游没返回 usage 时,网关按「1 token ≈ 4 字符」估算并计费。
输入侧的口径是「输入量含缓存命中的部分」,各家格式(OpenAI 的 prompt_tokens、Anthropic 的 input_tokens + cache_read_input_tokens、Gemini 的 promptTokenCount)在网关里统一成了这一种算法,所以同一个提示词用不同格式、流式或非流式请求,日志里的输入量应该在同一量级。
Anthropic 系模型还会额外报一项「缓存写入」(cache_creation_input_tokens,把这段提示词写进缓存的那部分)。这一路单独计量、单独计价,因为上游对写缓存本来就收得比普通输入贵。OpenAI 与 Gemini 的协议里没有这个概念,它们的缓存写入量恒为 0。