流式输出不是“模型更快”,而是让客户端在完整结果生成前持续收到增量内容。它能改善首字等待体验,但也会引入 SSE 解析、代理缓冲、连接中断和用量统计 等额外问题。
Link 流式调用的协议与费用核对
Link GPT、Claude 和 Gemini 的流事件需要按实际接口分别解析。Chat Completions 的分块不能直接交给 Messages 或 Responses 解析器;先保存原始事件类型和结束事件,再判断是网络中断还是客户端解析错误。
流式中断不等于没有产生用量。月卡用户应在 Link 使用日志检查失败请求和重试请求的实际消耗,并留意日额度耗尽后是否转扣钱包;不建议无上限重试长输出。
SSE 是否完整:按接口检查,不按模型品牌猜测
HTTP 200、首个文本片段和连接关闭是三个不同信号,都不能单独证明任务完成。先查看最终请求路径,再按下表判断。GPT、Claude 或 Gemini 的名称不能替代协议判断;Gemini 通过 Chat Completions 接入时按该协议检查,原生接口不套用本表。
| 当前接口 | 需要记录的结束证据 | 仍需区分 |
|---|---|---|
| /v1/chat/completions | data: [DONE] 与每个 choice 的 finish_reason | stop 是停止生成;length 是输出上限;tool_calls 需要继续处理工具,不等于业务任务结束 |
| /v1/responses | response.completed、response.failed 或 response.incomplete | response.output_text.done 只结束一个文本部分;failed 与 incomplete 不能记作完整成功 |
| /v1/messages | message_stop 与 message_delta 中的 stop_reason | content_block_stop 只结束一个块;tool_use 需要工具结果回传,流内 error 需独立处理 |
2026-11-04 对照下方官方协议资料整理。这里定义的是验收条件,不是 Link 所有模型已通过测试的声明;同一响应完整结束也不证明工具执行、文件内容或整轮 Agent 任务正确。
两种容易误判的结束情况
以下是简化的教学片段,不是 Link 线上调用记录。第一段即使收到 [DONE],仍因 length 达到输出上限;第二段说明服务端报告未完成,不能只因收到了 text delta 就判成功。
data: {"choices":[{"index":0,"delta":{},"finish_reason":"length"}]}
data: [DONE]
event: response.incomplete
data: {"type":"response.incomplete","response":{"status":"incomplete","incomplete_details":{"reason":"max_output_tokens"}}}
如果连接在任何终止事件前关闭,应记录为中断或结果未知,再查看客户端取消、超时及服务端日志。不要伪造一个 stop 或 [DONE] 补齐残缺流,也不要自动重放可能已经执行的工具。
开始前检查
先完成三个确认:
- 使用非流式请求验证 API Key、模型 ID 和基础接口正常。
- 确认目标模型与接口支持流式输出。
- 确认客户端、反向代理和 CDN 不会缓存或合并事件流。
如果非流式请求也失败,先排查 401、model not found 或接口路径,不要直接从 SSE 开始调试。
配置步骤
1. 使用 curl 查看原始事件流
curl -N --include --connect-timeout 10 --max-time 120 https://www.aifast.link/v1/chat/completions \
-H "Authorization: Bearer ${AIFAST_API_KEY:?Set AIFAST_API_KEY locally before running}" \
-H "Content-Type: application/json" \
-d '{
"model": "YOUR_MODEL_ID",
"stream": true,
"messages": [
{"role": "user", "content": "Explain SSE in three points."}
]
}'
-N 会关闭 curl 的输出缓冲。正常情况下,终端会连续出现多个 data: 数据块,而不是等待全部内容生成后一次性显示。
这条命令会向 Link 发起真实请求,可能产生费用。120 秒是本示例的总时限,不是服务 SLA;达到时限由 curl 主动中止,不能据此断言上游故障。发布日志前删除密钥、Cookie、用户输入和响应正文中的敏感信息。
2. Python SDK 流式读取
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["AIFAST_API_KEY"],
base_url="https://www.aifast.link/v1",
)
stream = client.chat.completions.create(
model="YOUR_MODEL_ID",
stream=True,
messages=[
{"role": "user", "content": "List three API pre-launch checks."}
],
)
for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
不要假设每个数据块都有文本。工具调用、角色信息、结束原因或兼容层事件可能出现在不同字段中,正式代码需要先判断字段是否存在。
3. Node.js SDK 流式读取
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.AIFAST_API_KEY,
baseURL: "https://www.aifast.link/v1",
});
const stream = await client.chat.completions.create({
model: "YOUR_MODEL_ID",
stream: true,
messages: [
{ role: "user", content: "Explain two common streaming failures." },
],
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
4. 记录完整性而不是只显示文字
生产环境至少记录:
| 字段 | 用途 |
|---|---|
| 请求开始时间 | 计算总耗时 |
| 首个响应字节时间 | 计算 TTFB;可能只是响应头或心跳 |
| 首个非空文本增量时间 | 计算客户端观察到的首字延迟;不含角色或空增量 |
| 最后事件或结束原因 | 判断是否完整结束 |
| 请求 ID | 联系支持时定位请求 |
| 已拼接内容长度 | 识别中途断流 |
| usage 与最终结算 | 分别记录客户端统计和账单;缺少统计不代表零消耗 |
Chat Completions 如何获取流式 usage?
只有接口支持该选项时,才在 stream: true 的请求中加入 stream_options: {"include_usage": true}。官方协议会在 [DONE] 前发送统计块,choices 为 [];先读取 usage,再决定是否跳过空 choices。上面的最简 SDK 示例只显示文本,没有采集账单数据。
不要把该选项直接复制到 Responses 或 Messages 请求。如果兼容接口拒绝此字段,应单独核验其支持范围,不能靠忽略错误伪造用量。中断时缺少最终统计,需要回到 Link 使用日志核对。
常见问题
服务端是流式,但网页仍然一次性显示
先用 curl -N 验证源站。如果终端能逐块显示,问题通常在你的后端框架、反向代理或前端读取逻辑。检查是否开启响应缓冲,以及是否真的按流读取 response.body。
输出到一半突然停止
记录最后一个数据块、结束原因和请求 ID。常见原因包括读取超时、网络中断、代理空闲超时、浏览器取消请求或上游生成失败。不要把残缺内容当成成功结果写入数据库。
为什么流式请求没有 usage
部分协议只在最后数据块返回完整 usage。如果连接提前中断,客户端可能拿不到最终统计。账务核对应以控制台和服务端记录为准,不能只依赖前端收到的数据块。
下一步
分别测试短文本、长文本、中文、多字节字符、主动取消、网络断开和代理超时。上线前还应为流式请求设置最大任务时长、断线状态和用户可见的重试入口。
已经遇到中断时,按 502 与断流判断表确定下一项检查;接口通过后,再用 API 成本计算器把实际用量和计费重试纳入预算。计算器不需要 Key,演示值不是 Link 报价。