# 流式输出

按协议读取 SSE 增量，正确处理完成事件、工具参数、错误与连接中断。

## 流式输出的工作方式 [#流式输出的工作方式]

在支持流式的文本模型上设置 `stream: true`，服务端使用 Server-Sent Events（SSE）逐步返回结果。SSE 是以空行分隔的事件，不是一份完整 JSON。一个网络数据块可能只包含半个事件，也可能包含多个事件；不能把每次网络读取直接交给 JSON 解析器。

流式不会减少模型需要生成的内容或保证降低费用，也不替代超时和并发控制。本文示例仅展示格式与处理方法，不提供实际延迟数据。

## 三种端点的事件差异 [#三种端点的事件差异]

| 端点 | 主要增量位置 | 结束信号 | 当前注意事项 |
| --- | --- | --- | --- |
| `POST /v1/chat/completions` | `choices[].delta.content` | `data: [DONE]`，同时检查 `finish_reason` | 工具参数和推理文本有各自字段，不应并入最终文本 |
| `POST /v1/responses` | `response.output_text.delta` 事件的 `delta` | `response.completed` | 当前桥接主要转换文本，不完整保留工具或推理事件 |
| `POST /v1/messages` | `content_block_delta` 的 `delta.text` | `message_stop` | 区分文本、Thinking 与工具内容块；工具流为基础兼容实现 |

成功建立 SSE 后，响应 `Content-Type` 为 `text/event-stream`。建立前的鉴权、参数或上游连接错误仍可能返回普通 JSON 和非 2xx HTTP 状态。请先判断 HTTP 状态及内容类型，再选择解析器。

## 完整调用示例 [#完整调用示例]

`curl -N` 关闭客户端输出缓冲。Python 示例依赖 `openai` 包，关闭自动重试，并显式判断是否见到选择项的结束原因。环境变量 `GFW_API_KEY` 必须提前设置。



**cURL**


```bash
curl --no-buffer --fail-with-body --silent --show-error \
  --max-time 120 \
  https://api.gfw.net/v1/chat/completions \
  -H "Authorization: Bearer ${GFW_API_KEY:?请先设置 GFW_API_KEY}" \
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream' \
  --data '{
    "model": "deepseek-v4-pro",
    "messages": [{"role": "user", "content": "用三句话说明连接池的作用。"}],
    "max_tokens": 512,
    "stream": true
  }'
```



**Python**


```python
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["GFW_API_KEY"],
    base_url="https://api.gfw.net/v1",
    timeout=120.0,
    max_retries=0,
)
stream = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=[{"role": "user", "content": "用三句话说明连接池的作用。"}],
    max_tokens=512,
    stream=True,
)

finish_reason = None
try:
    for chunk in stream:
        if chunk.usage is not None:
            print("\nusage:", chunk.usage)
        for choice in chunk.choices:
            if choice.delta.content:
                print(choice.delta.content, end="", flush=True)
            if choice.finish_reason is not None:
                finish_reason = choice.finish_reason
finally:
    stream.close()

print()
if finish_reason is None:
    raise RuntimeError("未收到选择项的结束原因，请检查流是否中断")
if finish_reason == "length":
    raise RuntimeError("输出达到 Token 上限，请检查内容是否完整")
print("finish_reason:", finish_reason)
```




示例只处理文本。工具场景要额外收集 `delta.tool_calls`，推理场景要单独处理模型提供的推理字段。用量事件可能没有 `choices`，客户端必须允许空数组；也不要假设每次流式调用都能得到完整 `usage`。

## 事件与响应示意 [#事件与响应示意]

下面为 Chat Completions SSE 结构示意，事件顺序与文本仅用于说明：

```text
data: {"id":"chatcmpl_example","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}

data: {"id":"chatcmpl_example","choices":[{"index":0,"delta":{"content":"连接池"},"finish_reason":null}]}

data: {"id":"chatcmpl_example","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

```

Responses 的文本事件结构示意：

```text
event: response.output_text.delta
data: {"type":"response.output_text.delta","delta":"连接池"}

```

Anthropic Messages 的文本事件结构示意：

```text
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"连接池"}}

```

解析命名事件时同时保留 `event` 名称和 JSON 内 `type`。不要将 `message_stop`、`response.completed` 与 Chat 的 `[DONE]` 混为一种统一的字符串终止符。

## 中断、错误与应用状态 [#中断错误与应用状态]

流开始后 HTTP 状态已固定，后续异常可能表现为流内 `error`、连接断开，或缺失预期内容。HTTP 200 和收到结束事件都不足以证明答案完整；Chat 转发在部分异常结束后仍会补发 `[DONE]`，Responses 桥接也可能发出最终标签。因此必须结合异常、内容、结束原因和应用校验判断。

推荐保存 `receiving`、`completed`、`failed` 等应用状态：收到增量时只追加到临时结果，完成并通过校验后再提交业务结果。连接中断时保留已经收到的文本，但标为不完整。重新发起请求可能重复生成与计费，不应自动把新流追加到旧流。

工具参数必须按调用索引收集完整 JSON 后再解析和执行。针对 Anthropic 工具流，当前桥接存在非完整原生事件兼容边界，复杂工具流程优先使用非流式返回。

## 接入注意与相关文档 [#接入注意与相关文档]

保持 SDK 的连接读取超时，并为业务操作设置总时限。中间反向代理若启用了响应缓冲，界面可能直到结束才收到文本；排查时比较直接 cURL 输出与应用读取结果，不要据此推断模型没有流式能力。

用量和账单以 [用量与调用日志](/docs/usage-logs) 及 [计费与充值](/docs/billing) 为准。相关文档：[OpenAI 兼容 API](/docs/openai-api)、[Responses API](/docs/responses)、[Anthropic 兼容 API](/docs/anthropic-api)、[工具调用](/docs/tool-calls)、[错误码与重试](/docs/errors)。
