使用指南
流式输出
让模型边生成边返回:各协议怎么开启流式、返回的数据长什么样、用量(usage)在哪里。
「流式输出」是指模型一边生成、一边把内容一小段一小段地发回来,而不是等全部写完再一次性返回。聊天界面里「字一个个蹦出来」的效果就是这样实现的。回答很长或模型需要思考很久时,流式能让用户更早看到内容,也能避免连接因为长时间没有数据而被中间网络断开。
流式和非流式的价格完全相同,只是返回方式不同。
四种协议都用 SSE(Server-Sent Events)格式返回流式数据:每条消息是一行 data: ...,消息之间用空行隔开。
| 协议 | 怎么开启 | 结束标志 |
|---|---|---|
| OpenAI Chat Completions | 请求体加 "stream": true |
最后一行 data: [DONE] |
| OpenAI Responses | 请求体加 "stream": true |
response.completed 事件 |
| Anthropic Messages | 请求体加 "stream": true |
message_stop 事件 |
| Gemini | 把地址里的 :generateContent 换成 :streamGenerateContent?alt=sse |
连接关闭 |
# -N 让 curl 收到一段就打印一段,不做缓冲curl -N https://noviahub.com/v1/chat/completions \ -H "Authorization: Bearer $NOVIAHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-v4-flash", "stream": true, "messages": [{"role": "user", "content": "写一首四行短诗"}] }'import os
from openai import OpenAI
client = OpenAI( base_url="https://noviahub.com/v1", api_key=os.environ["NOVIAHUB_API_KEY"],)
stream = client.chat.completions.create( model="deepseek-v4-flash", messages=[{"role": "user", "content": "写一首四行短诗"}], stream=True,)
for chunk in stream: # 最后一段只带用量、没有 choices,要先判断 if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True)Chat Completions:用量在最后一段
Section titled “Chat Completions:用量在最后一段”用 Chat Completions 流式调用时,NoviaHub 默认会在结束前多发一段只包含用量(usage)的数据:这一段的 choices 是空数组 [],usage 里是本次的 Token 数。即使你没有在请求里写 stream_options,也会收到这一段。
下面是在测试环境里抓到的一次完整返回(内容来自测试用的模拟上游,仅作格式示例):
data: {"id":"chatcmpl-mock123","object":"chat.completion.chunk","created":1790609866,"model":"deepseek-v4-flash","choices":[{"index":0,"delta":{"content":"Hello","role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-mock123","object":"chat.completion.chunk","created":1790609866,"model":"deepseek-v4-flash","choices":[{"index":0,"delta":{"content":" from the mock upstream."},"finish_reason":null}]}
data: {"id":"chatcmpl-mock123","object":"chat.completion.chunk","created":1790609866,"model":"deepseek-v4-flash","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-mock123","object":"chat.completion.chunk","created":1790609866,"model":"deepseek-v4-flash","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":7,"total_tokens":19}}
data: [DONE]- 如果你的程序遇到
choices为空的数据会出错,要么在代码里先判断choices是否为空(如上面的 Python 示例),要么在请求里加上"stream_options": {"include_usage": false},这时 NoviaHub 就不会发这段用量数据。 - 无论你是否要这段用量,NoviaHub 都会按实际用量计费。
Anthropic Messages:用量在 message_delta
Section titled “Anthropic Messages:用量在 message_delta”Anthropic 格式的流式返回依次是 message_start、content_block_start、若干 content_block_delta、content_block_stop、message_delta、message_stop。
以 message_delta 事件里的 usage 为准。 当模型需要经过协议转换(例如用 Anthropic 格式调用一个 GPT 或 DeepSeek 模型)时,message_start 里的 input_tokens 是 NoviaHub 在开始时的估算值,和最终数值可能不同。
Gemini:用量在 usageMetadata
Section titled “Gemini:用量在 usageMetadata”Gemini 流式返回的每一段都是一个完整的 GenerateContentResponse 对象,每一段都带有 usageMetadata 字段。中间各段的数值不一定是最终结果(经过协议转换时,前面几段的输入 Token 是估算值、输出 Token 为 0),以最后一段的 usageMetadata 为准。
其他注意事项
Section titled “其他注意事项”- 以冒号开头的行:SSE 规定以
:开头的行是注释。网关可以配置为在长时间等待时发送这类行(: PING)来保持连接。自己解析 SSE 时,遇到以:开头的行直接忽略即可;标准的 SSE 客户端库会自动处理。 - 空闲超时:如果上游模型长时间没有发来任何数据,网关会结束这次流式响应。
- 在使用日志里区分:使用日志的「耗时」列会标出「流」或「非流」。流式请求还会显示「首字」,即从发出请求到收到第一段内容用了多久。