Guides

流式响应

SSE chunk 格式、增量解析、断流和错误处理方式。

概述

流式响应适合聊天、代码生成和长文本生成。

客户端设置 `stream: true` 后,服务端会按 SSE 格式持续推送增量内容。

UOUODUO Gateway 尽量保持 OpenAI 兼容,同时也会透传部分上游事件。

基本格式

SSE 每个事件通常以 `data:` 开头。

data: {"id":"chatcmpl_...","object":"chat.completion.chunk","choices":[{"delta":{"content":"你好"},"index":0}]}

data: {"id":"chatcmpl_...","object":"chat.completion.chunk","choices":[{"delta":{"content":",世界"},"index":0}]}

data: [DONE]

客户端应逐行读取。

空行表示一个事件结束。

收到 `[DONE]` 后可以关闭连接。

Chat Completions 示例

curl https://api.example.com/v1/chat/completions \
  -H "Authorization: Bearer $UOUODUO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o-mini",
    "stream": true,
    "messages": [
      { "role": "user", "content": "写一个三行项目周报。" }
    ]
  }'

响应里的 `choices[0].delta.content` 是增量文本。

不要假设每个 chunk 都有 content。

有些 chunk 只包含 role、tool call 片段或 finish reason。

Node.js 解析示例

const response = await fetch('https://api.example.com/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.UOUODUO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'gpt-4o-mini',
    stream: true,
    messages: [{ role: 'user', content: '解释 SSE。' }],
  }),
});

const reader = response.body?.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (reader) {
  const { done, value } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });
  const events = buffer.split('\n\n');
  buffer = events.pop() ?? '';

  for (const event of events) {
    for (const line of event.split('\n')) {
      if (!line.startsWith('data:')) continue;
      const payload = line.slice(5).trim();
      if (payload === '[DONE]') return;
      const chunk = JSON.parse(payload);
      process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');
    }
  }
}

Python 解析示例

import json
import os
import requests

resp = requests.post(
    "https://api.example.com/v1/chat/completions",
    headers={
        "Authorization": f"Bearer {os.environ['UOUODUO_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "model": "gpt-4o-mini",
        "stream": True,
        "messages": [{"role": "user", "content": "解释 SSE。"}],
    },
    stream=True,
    timeout=60,
)

for raw_line in resp.iter_lines(decode_unicode=True):
    if not raw_line or not raw_line.startswith("data:"):
        continue
    payload = raw_line[5:].strip()
    if payload == "[DONE]":
        break
    chunk = json.loads(payload)
    print(chunk.get("choices", [{}])[0].get("delta", {}).get("content", ""), end="")

断流处理

网络中断时,客户端可能没有收到 `[DONE]`。

这类请求不一定可以安全重试,因为模型可能已经生成了部分内容。

  • UI 层标记为“响应中断”
  • 保留已收到文本
  • 给用户提供重新生成按钮
  • 对幂等任务使用业务 request ID 去重

错误事件

有些错误会在 HTTP 阶段返回。

客户端解析 JSON 失败时,不要直接崩溃,应把原始片段记录到 debug 日志。

Tool calling

tool call 增量通常分多个 chunk 返回,不要把每个 chunk 当成完整 JSON 参数。

需要按 `tool_calls[index].function.arguments` 逐段拼接,等 finish reason 出现后再解析。