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 出现后再解析。