> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openp.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 流式响应

> SSE 流式输出的工作原理与代码示例

启用 `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)

```python theme={null}
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

```ts theme={null}
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

```ts theme={null}
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);
  }
}
```

<Warning>浏览器直连会暴露 API Key,生产请通过自有后端转发。</Warning>

## 流式中的错误

若上游中途出错,会以 SSE 形式发送:

```
data: {"error":{"message":"Upstream timeout","type":"server_error"}}
data: [DONE]
```

客户端务必检查 `error` 字段,而不仅是 HTTP 状态码(流式响应早期已返回 200)。

## include\_usage

在 `stream_options.include_usage: true` 下,流末会多发一条不含 content 但带 `usage` 的 chunk,方便统计:

```json theme={null}
{"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](/api-reference/anthropic/messages)。
* **Gemini**:`?alt=sse` 时按 `data: {...}` 推送,无 `[DONE]` 终结符,通过 `finishReason` 判断结束。

## 注意

* **超时**:OpenPAI 网关默认 600 秒,长输出请确保客户端超时也足够长。
* **中途取消**:客户端断开连接后,网关会立即停止上游请求并按 **已生成 token** 结算。
* **代理 / CDN**:中间链路必须支持 `chunked` 传输,否则 SSE 会被缓冲。
