流式输出
使用 Server-Sent Events (SSE) 实现实时逐 token 输出,提升用户体验。
什么是流式输出
流式输出(Streaming)基于 SSE(Server-Sent Events) 协议,让服务器在生成回答的过程中逐 token 将内容推送给客户端,而不是等全部生成完毕后一次性返回。
这意味着用户可以在模型"思考"的同时实时看到文字逐字出现,显著降低首字延迟(Time to First Token),特别适合聊天机器人、写作助手等交互式场景。
启用流式输出
只需在请求体中设置 stream: true,即可开启流式响应:
{
"model": "gpt-4o",
"messages": [
{"role": "user", "content": "给我讲一个故事"}
],
"stream": true
}
stream 为 true 时,响应的 Content-Type 为 text/event-stream,HTTP 状态码在第一个 chunk 发送时即返回。SSE 数据格式
流式响应中每一行均以 data: 开头,后跟一个 JSON 对象。每个 JSON 块包含一个 delta 字段,表示当前增量内容。流结束时会发送一个特殊终止信号。
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1719000000,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1719000000,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"从前"},"finish_reason":null}]}
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1719000000,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"有一个"},"finish_reason":null}]}
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1719000000,"model":"gpt-4o","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":12,"completion_tokens":158,"total_tokens":170}}
data: [DONE]
关键字段说明
| 字段 | 说明 |
|---|---|
choices[].delta.content | 本次增量生成的文本片段 |
choices[].delta.role | 仅在第一个 chunk 中出现,值为 assistant |
choices[].finish_reason | 生成结束原因,未结束时为 null,结束时为 stop / length 等 |
usage | Token 用量统计,仅出现在最后一个 chunk 中 |
data: [DONE] | 流终止信号,非 JSON,表示传输结束 |
Python 示例
使用 openai Python SDK,设置 stream=True 后遍历返回的 chunk 即可:
from openai import OpenAI
client = OpenAI(
api_key="sk-your-api-key",
base_url="https://api2everything.xyz/v1"
)
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "用 Python 实现快速排序"}],
stream=True
)
for chunk in stream:
delta = chunk.choices[0].delta
if delta.content:
print(delta.content, end="", flush=True)
# 输出完成后换行
print()
flush=True 确保每个 token 立即输出到终端,而不是等缓冲区满后才显示。Node.js 示例
使用 openai Node.js SDK(v4+),通过异步迭代器消费流:
import OpenAI from "openai";
const client = new OpenAI({
apiKey: "sk-your-api-key",
baseURL: "https://api2everything.xyz/v1",
});
async function main() {
const stream = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "用 JavaScript 实现快速排序" }],
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) {
process.stdout.write(content);
}
}
console.log(); // 换行
}
main();
cURL 示例
使用 -N 标志禁用缓冲,实时查看流式输出:
curl -N https://api2everything.xyz/v1/chat/completions \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "你好,介绍一下你自己"}],
"stream": true
}'
-N(--no-buffer)告诉 cURL 不要缓冲输出,这样每个 SSE 事件都会立即显示在终端中。前端 JavaScript 示例
在浏览器中使用 fetch + ReadableStream 实现流式渲染:
async function streamChat(prompt) {
const response = await fetch("https://api2everything.xyz/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer sk-your-api-key",
},
body: JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: prompt }],
stream: true,
}),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let result = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value, { stream: true });
const lines = text.split("\n").filter((line) => line.startsWith("data: "));
for (const line of lines) {
const data = line.slice(6); // 去掉 "data: " 前缀
if (data === "[DONE]") break;
try {
const json = JSON.parse(data);
const content = json.choices[0]?.delta?.content;
if (content) {
result += content;
// 更新 DOM,例如:
document.getElementById("output").textContent = result;
}
} catch (e) {
// 忽略解析错误(可能是不完整的 chunk)
}
}
}
return result;
}
// 使用示例
streamChat("写一首关于春天的诗");
eventsource-parser)来处理 SSE 边界情况,例如跨 chunk 的数据分割。注意事项
不要设置 Accept-Encoding: gzip
流式请求中请勿手动设置 Accept-Encoding: gzip 请求头。压缩会导致服务器缓冲响应直到压缩块足够大才发送,破坏逐 token 推送的实时性。大多数 HTTP 客户端默认就不会对 SSE 连接启用压缩。
处理连接中断
网络不稳定时,流式连接可能在中途断开。建议:
- 在客户端实现重试逻辑,检测到连接断开后重新发起请求
- 记录已接收的内容,避免重复展示
- 设置合理的超时时间(建议 60-120 秒)
Token 用量统计
usage 字段(包含 prompt_tokens、completion_tokens、total_tokens)仅出现在流的最后一个 chunk 中(即 finish_reason 不为 null 的那个 chunk)。如果需要统计用量,请确保解析到最后一个 chunk。
usage 信息。此时扣费仍然会发生(按实际生成的 token 数计算),可在控制台的用量明细中查看。与非流式请求的差异
| 特性 | 非流式 | 流式 |
|---|---|---|
| 响应格式 | 完整 JSON | SSE 事件流 |
| 首字延迟 | 较高(需等待全部生成) | 较低(首个 token 即返回) |
| Content-Type | application/json | text/event-stream |
| usage 字段 | 在响应体顶层 | 在最后一个 chunk 中 |
| 错误处理 | 标准 HTTP 错误码 | 错误可能出现在流中间 |