启用 stream: true 后,OpenPAI 以 Server-Sent Events(SSE) 推送 chunk,使客户端能边接收边渲染。
协议格式
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: {"id":"chatcmpl-...","choices":[{"delta":{"content":"你"}}]}
data: {"id":"chatcmpl-...","choices":[{"delta":{"content":"好"}}]}
data: {"id":"chatcmpl-...","choices":[{"delta":{},"finish_reason":"stop"}]}
data: [DONE]
注意点:
- 每条 chunk 以
data: 开头,以两个 \n 结束。
- 最后会发送
data: [DONE] 表示结束(Anthropic / Gemini 协议无此约定)。
- 心跳由网关每 15 秒发送一次注释行
:keep-alive,无需客户端处理。
Python(openai SDK)
from openai import OpenAI
client = OpenAI(api_key="sk-...", base_url="https://openp.ai/v1")
stream = client.chat.completions.create(
model="gpt-5.5",
messages=[{"role": "user", "content": "讲故事"}],
stream=True,
stream_options={"include_usage": True},
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.usage:
print("\n用量:", chunk.usage)
Node.js
import OpenAI from "openai";
const client = new OpenAI({ apiKey: "sk-...", baseURL: "https://openp.ai/v1" });
const stream = await client.chat.completions.create({
model: "gpt-5.5",
messages: [{ role: "user", content: "讲故事" }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
浏览器端 / Fetch API
const resp = await fetch("https://openp.ai/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-5.5",
messages: [{ role: "user", content: "hi" }],
stream: true,
}),
});
const reader = resp.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let idx;
while ((idx = buffer.indexOf("\n\n")) !== -1) {
const line = buffer.slice(0, idx).trim();
buffer = buffer.slice(idx + 2);
if (!line.startsWith("data:")) continue;
const payload = line.slice(5).trim();
if (payload === "[DONE]") return;
const json = JSON.parse(payload);
console.log(json.choices[0]?.delta?.content);
}
}
浏览器直连会暴露 API Key,生产请通过自有后端转发。
流式中的错误
若上游中途出错,会以 SSE 形式发送:
data: {"error":{"message":"Upstream timeout","type":"server_error"}}
data: [DONE]
客户端务必检查 error 字段,而不仅是 HTTP 状态码(流式响应早期已返回 200)。
include_usage
在 stream_options.include_usage: true 下,流末会多发一条不含 content 但带 usage 的 chunk,方便统计:
{"choices":[],"usage":{"prompt_tokens":10,"completion_tokens":42,"total_tokens":52}}
Claude / Gemini 流式
- Claude:事件按
message_start → content_block_start → content_block_delta → content_block_stop → message_delta → message_stop 顺序,详见 Messages。
- Gemini:
?alt=sse 时按 data: {...} 推送,无 [DONE] 终结符,通过 finishReason 判断结束。
- 超时:OpenPAI 网关默认 600 秒,长输出请确保客户端超时也足够长。
- 中途取消:客户端断开连接后,网关会立即停止上游请求并按 已生成 token 结算。
- 代理 / CDN:中间链路必须支持
chunked 传输,否则 SSE 会被缓冲。