流式输出

使用 Server-Sent Events (SSE) 实现实时逐 token 输出,提升用户体验。

什么是流式输出

流式输出(Streaming)基于 SSE(Server-Sent Events) 协议,让服务器在生成回答的过程中逐 token 将内容推送给客户端,而不是等全部生成完毕后一次性返回。

这意味着用户可以在模型"思考"的同时实时看到文字逐字出现,显著降低首字延迟(Time to First Token),特别适合聊天机器人、写作助手等交互式场景。

提示:WLON API 的流式输出与 OpenAI 格式完全兼容,任何支持 OpenAI Streaming 的 SDK 和框架均可直接使用。

启用流式输出

只需在请求体中设置 stream: true,即可开启流式响应:

{
  "model": "gpt-4o",
  "messages": [
    {"role": "user", "content": "给我讲一个故事"}
  ],
  "stream": true
}
说明:streamtrue 时,响应的 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
usageToken 用量统计,仅出现在最后一个 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 或第三方库(如 eventsource-parser)来处理 SSE 边界情况,例如跨 chunk 的数据分割。

注意事项

不要设置 Accept-Encoding: gzip

流式请求中请勿手动设置 Accept-Encoding: gzip 请求头。压缩会导致服务器缓冲响应直到压缩块足够大才发送,破坏逐 token 推送的实时性。大多数 HTTP 客户端默认就不会对 SSE 连接启用压缩。

处理连接中断

网络不稳定时,流式连接可能在中途断开。建议:

Token 用量统计

usage 字段(包含 prompt_tokenscompletion_tokenstotal_tokens)仅出现在流的最后一个 chunk 中(即 finish_reason 不为 null 的那个 chunk)。如果需要统计用量,请确保解析到最后一个 chunk。

注意:如果连接中途断开,你将无法获取 usage 信息。此时扣费仍然会发生(按实际生成的 token 数计算),可在控制台的用量明细中查看。

与非流式请求的差异

特性非流式流式
响应格式完整 JSONSSE 事件流
首字延迟较高(需等待全部生成)较低(首个 token 即返回)
Content-Typeapplication/jsontext/event-stream
usage 字段在响应体顶层在最后一个 chunk 中
错误处理标准 HTTP 错误码错误可能出现在流中间