流式输出
以各厂商原生的 SSE 格式流式接收 Claude、GPT 和 Gemini 的输出。了解早期失败如何被隐藏、卡顿如何呈现,以及取消流如何计费。
流式输出完全沿用各厂商的设计:厂商自己格式的 server-sent events,逐字节转交给你。网关只是在旁边读一遍,用来统计用量、替你挡住出故障的渠道。你现有的流解析代码不用改。
开启流式输出#
| API | 如何请求流式响应 | 最后一个事件 |
|---|---|---|
| Anthropic Messages | 请求体中 "stream": true | event: message_stop |
| OpenAI Chat Completions | 请求体中 "stream": true | data: [DONE] |
| Gemini | POST /v1beta/models/{model}:streamGenerateContent?alt=sse | candidate 带有 finishReason 的事件 |
各 SDK 的代码见对应的 API 指南:Anthropic、OpenAI、Gemini。
首个 token 前暂扣,之前的失败自动重试#
有时渠道接下了请求,却在产出任何内容之前就放弃了,比如先发 message_start,紧接着报过载错误。为了把这种情况藏起来,网关会把每个流的开头暂扣住,直到第一段真正的内容(文本、思考或工具调用)到达。渠道在这之前失败,说明还什么都没发给你,网关就悄悄把请求转给另一个渠道,你从头到尾收到的是一个干净的流。
启动太慢的渠道也一样处理。对未开启扩展思考的 Claude 模型,渠道在 10 秒内(提示词每多 1 万 token 再加 1 秒)还没产出 token,就会被放弃、换成另一个,除非它已是最后一个可用渠道。开启思考的请求,模型想多久就等多久。非流式请求同样遵循这条规则。
对你的客户端来说,这意味着:
- 状态码和响应头随第一个 token 一起到达:首字节时间就等于首 token 时间。
- 如果所有渠道都在第一个 token 前失败,你收到的是一个普通的 HTTP 错误响应(按你所用 API 的错误格式),而不是一个断掉的流。
- 暂扣最多持续 15 秒。模型静默思考超过这个时间,就会从那一刻开始推流,请求也从那一刻起绑定在该渠道上。
- 给响应留出至少 30 秒的启动时间。请求在此之前还可能要排队等几秒容量,见速率限制与故障转移。
流的样例#
event: message_start
data: {"type":"message_start","message":{"id":"msg_01Qe4n","type":"message","role":"assistant","model":"claude-sonnet-5-5","content":[],"stop_reason":null,"usage":{"input_tokens":12,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":1}}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"红、绿、蓝。"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":7}}
event: message_stop
data: {"type":"message_stop"}流进行中断开#
第一个 token 发出之后,请求就归属于这个渠道,不能再迁移。此时仍有两种情况会让它提前结束。
长时间无响应。 渠道连续 3 分钟没有发送任何内容,网关会用你所用 API 格式的错误事件结束这个流:
event: error
data: {"type":"error","error":{"type":"api_error","message":"Upstream stopped responding mid-stream"}}渠道自己报错,比如模型过载。你收到的是按你的格式生成的通用错误事件,而不是渠道的原始措辞:渠道繁忙时为 overloaded_error(OpenAI 为 rate_limit_exceeded,Gemini 为 RESOURCE_EXHAUSTED),其他情况为 api_error(Gemini 为 UNAVAILABLE),消息是 The model provider returned an error. Please retry.
两种情况下,流都没有正常的收尾事件(message_stop、data: [DONE] 或 finishReason)。凡是缺少收尾事件的流,都应视为不完整:保留或丢弃已收到的部分,需要完整回答时就整体重发请求。Anthropic 和 OpenAI 的 SDK 收到错误事件时会抛出异常。
提前停止#
关闭连接即可停止生成:跳出循环,或者调用 stream.close() / controller.abort()。网关会停掉模型,只按到此为止已生成的内容、以官方标价的一半计费:
- 输入:按流中已报告的数量(Anthropic 在开头就会报告);没有报告时,按你的请求约每四个字符一个 token 估算。
- 输出:取「已报告的数量」与「已发送给你的文本约每四个字符一个 token」中的较大值。提前结束的流很少会报告最终输出数,所以通常以估算值为准。