Skip to main content
有三件事会让一次 LLM 响应在模型自己认为写完之前结束:您请求的输出上限、模型自身强制的上限,以及流式请求下网关的流生命周期限制。本页说明这三者,以及提前结束的请求如何计费。

请求输出上限

Sunra 会把您传入的上限转发给上游 provider。响应因触及该上限而停止时,会返回 finish_reason: "length"(Chat Completions 与 Responses)或 stop_reason: "max_tokens"(Messages)——与您直接调用 provider 得到的信号一致。
在 Chat Completions 上,部分 provider 只认 max_tokens 而忽略 max_completion_tokens——于是您以为设了上限,实际得到的是无上限的补全。对于 deepseek/ 系列模型,网关会代您把 max_completion_tokens 翻译为 max_tokens。两个字段同时传入时,取较小者生效,因为两者都是上限语义。原生支持 max_completion_tokens 的 provider 不做改写,因此您设置的上限只会在一处被执行一次。

模型输出上限

每个模型都有自己的最大输出长度,与您请求的数值无关。请求超过模型允许的值会被拒绝,而不是被静默削平——上游 provider 返回 400,并在消息中给出合法区间。例如 deepseek/deepseek-v4-flashdeepseek/deepseek-v4-pro 最高接受 393,216 个输出令牌,超出即拒绝:
这里返回 400 是刻意设计。被静默降低的上限与”正常跑到上限”无法区分:两者都报 finish_reason: "length"usage 里也没有任何字段能告诉您发生的是哪一种。 输出上限因模型而异,并随 provider 发布新版本而变化。请把 400 当作权威答案,不要在客户端里硬编码某个上限值。

流生命周期

流式请求(stream: true)在网关侧受两条限制约束: 只要流在空闲窗口内持续输出令牌,这两条限制都不会触发——长生成不会仅仅因为”长”而被打断。非流式请求不受这两条限制约束,它们有自己固定的 6 分钟整体上限。

中止帧

任一限制触发时——或流中途上游连接故障时——网关不会裸断连。它会把仍持有的内容刷出,按您所调用 endpoint 的 SSE 方言发送一个 error 帧,然后干净地关闭流。该帧之前已投递的所有内容都是有效输出,可以照常使用。 /v1/chat/completions/v1/responses 上:
[DONE] 哨兵只属于 Chat Completions 方言;Responses 流在 error 帧之后直接结束,没有 [DONE] /v1/messages 上,该帧遵循 Anthropic 方言——一个带类型的 error 事件,且没有 [DONE]
type 恒为 gateway_stream_aborted。在 OpenAI 形状的方言上,code 指明触发的是哪条限制: 没有中止帧的流就是正常结束的流。由于该帧是确定性的,若流被截断且没有中止帧,应视为客户端或网络问题,而不是网关中止。

被中止的流如何计费

被中止的流按已投递给您的内容计费,绝不会按 provider 内部可能继续生成的完整内容计费。
  • 若中止前 provider 已上报最终 usage,则按该上报计费。
  • 否则按保守估计结算已投递的输出——大约每 4 个字符折算 1 个令牌,并以您请求的输出上限封顶。该笔记录会标记 usage_source: "conservative_estimate" 备查。
  • 若没有投递任何内容,或 provider 上报了明确的错误,请求标记为失败并释放预留的额度。失败的请求不计费。

客户端如何处理

关键在于保留这部分输出而不是丢弃:无论如何您都要为它付费,而在长生成场景下,它通常已经是答案的大部分。