整体时限不能当首字节时限用

**`AbortSignal.timeout(N)` 是「这次请求从头到尾只给 N 秒」的整体时限,不是「N 秒内没响应就换下一个」的首字节时限。** 把它当成后者写进中转/网关的重试逻辑,会掐死所有慢启动的请求。

判据与现象:

  • 推理模型(如 gemini-3.x)先产出大量思考 token 才吐正文,首字节常在 6~28 秒;给 8 秒整体时限,等于必然在思考阶段就掐断。
  • 症状是「回答说到一半没了」,且耗时集中在同一个值附近(如每次 17~19 秒挂断),说明是固定超时切断连接,而不是模型自己停的。
  • 想要「不响应就换 key/模型」的语义,要用首字节超时(连上并收到第一片后才开始计时)或连接超时 + 读超时分离,而不是一个总时长。

修法优先级:

  1. 网关侧(根因):把流超时从 ~20s 大幅调高;若必须在超时前收尾,要主动 flush 一个 finish_reason: "length" 而不是直接断流。
  2. 服务端补偿:流自然结束但没见到 finish_reason 时,向前端发明确的 {truncated: true} 事件。
  3. 前端可感知:收到 truncated 时提示「回答被上游截断,可重试」,不要静默当完整答案渲染。

同族判断见 长任务不要用固定超时判定:凡是拿「时长」当「是否完成/是否可用」的代理,都会在真实耗时分叉时误杀。

相关