# 处理 LLM 流式响应

## 开启之前确认能力

流式输出让客户端逐步读取内容，但不承诺总耗时更短、费用更低或答案完整。先核对 `/v1/models` 中目标路由声明的能力与模型指南；未声明流式支持时，先用非流式请求，并确认支持后再开启。

对于已核实支持流式的 OpenAI Chat Completions 路由，将 `MODEL_ID` 设置为准确公开 ID。以下命令依赖 `jq`，会发起一次计费用量的生成请求，不自动重试。

```bash
jq -n --arg model "$MODEL_ID" \
  '{model: $model, messages: [{role: "user", content: "Explain HTTP in three steps."}], stream: true}' \
  > request.json
curl --no-buffer --fail-with-body --max-time 120 \
  "https://newrouters.com/v1/chat/completions" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @request.json
```

`--no-buffer` 只调整 curl 的输出缓冲，不配置模型。该命令用于观察事件，不是生产环境解析器。若替换为敏感提示词，不要把 `request.json` 写入日志。

## 解析事件，不按网络分块解析

一个网络块可能只有半个事件，也可能包含多个事件。使用 SSE 解析器或正确配置端点的原生 SDK，跨读取保存未完成的数据，增量解码 UTF-8，只分发完整事件。注释和空增量不能成为用户看到的答案文本。

| 协议 | 流式入口 | 解析对象 |
| --- | --- | --- |
| Chat Completions | `/v1/chat/completions`，`stream: true` | SSE data、choice deltas、终止标记 `[DONE]` |
| Responses | `/v1/responses`，`stream: true` | 带名称的生命周期及输出事件 |
| Claude Messages | `/v1/messages`，`stream: true` | 消息、内容块事件与停止状态 |
| Gemini | `/v1beta/models/{model}:streamGenerateContent?alt=sse` | candidate parts 与结束信息 |

只使用目标路由支持的入口，不要将 Chat Completions 的 `[DONE]` 规则套用到所有协议。用量可能与文本分开发送，应单独保存；缺失用量代表未知，不能当成零。

## 明确展示部分输出状态

区分连接中、接收中、完成和中断。只有收到预期的协议完成信号后才标记完成；连接关闭或 HTTP 200 本身不足以证明成功。截断、拒绝和正常完成也应区别展示。

客户端断开后停止展示并释放本地资源，但取消不证明上游已停止生成或不会计费。已经收到部分文本时，不要自动重新发送整个请求，以免重复输出与花费。参考[错误与重试](/zh/docs/llm/errors-and-retries)，在[评测](/zh/docs/llm/evaluation)中分别记录首段文本时间与总耗时。
