超时与流式中断

网关等待上游时有 6 分钟的空闲超时。连接上游失败或等待超时,网关会先换下一个通道,全部失败后返回 500 或 504,响应体是 HTML 页面而不是 JSON。流式请求一旦开始输出就不再换通道,中途出错时网关在流末尾补发一个错误事件后结束连接。客户端应把没有收到结束标记的流当作失败处理,并自行决定重试和去重。

504 / 500:响应体是 HTML 页面

状态码 原因 日志「状态说明」
504 等待上游响应超时,或连接上游超时 「网关」+「上游请求超时」
500 连接上游失败(例如连接被拒绝),或读取上游响应时出错 「网关」+「连接上游失败,未收到响应」或「上游请求或响应读取失败」
  • 这两种响应没有 JSON 错误体,返回的是一个 HTML 错误页,部分 SDK 会报「无法解析响应」一类的错误。请按 HTTP 状态码处理,不要依赖 error.message。
  • 返回之前,网关已经按失败重试与备用模型的规则尝试过其他通道,所有候选都失败后才返回最后一次的结果。
  • 「状态说明」的来源显示「网关」,因为是网关终止了这次请求,但原因在网关与上游之间的连接。持续出现时,查看服务状态或换用其他模型。

上游超时怎么计算

网关调用上游时的读超时为 360 秒(6 分钟):

  • 非流式请求:上游 6 分钟内没有返回响应即超时。生成很长的内容时,请改用 stream: true。
  • 流式请求:按相邻两段数据之间的间隔计算,只要上游持续有输出就不会超时;开始输出之前同样最多等待 6 分钟。
  • 换通道后重新计时:一个通道超时后,网关改用下一个通道并重新等待,所以一次请求的总耗时可能超过 6 分钟。

流式输出开始后出错

流式请求的响应头和第一段内容一旦发出,网关就不能再换通道。此时上游中断或超时,网关在流的末尾补发一个错误事件,然后结束连接:

Chat Completions:

data: {"error": {"message": "upstream timed out after the response had started", "type": "api_error", "code": "stream_interrupted"}}

Anthropic Messages(/v1/messages):

event: error
data: {"type": "error", "error": {"type": "api_error", "message": "upstream timed out after the response had started"}}

Responses(/v1/responses):补一个 response.failed 事件,response.error.code 为 server_error。

event: response.failed
data: {"type": "response.failed", "sequence_number": 42, "response": {"id": "resp_...", "object": "response", "status": "failed", "error": {"code": "server_error", "message": "upstream timed out after the response had started"}}}
  • 超时时 message 为 upstream timed out after the response had started;连接或读取失败时为 upstream failed after the response had started。
  • 上游服务商自己在流中发出的错误事件(例如 Anthropic 的 overloaded_error)会按你调用的协议转给你。
  • OpenAI 和 Anthropic 的官方 SDK 收到对应格式的错误事件会抛出 API 错误。
  • 在日志中,这类请求的「状态」是 200(响应头已经发出),「状态说明」显示「网关」+「上游请求超时」或「上游请求或响应读取失败」。

没有结束标记就当作失败

出现错误事件后,流中不会再有结束标记。请以协议规定的结束标记判断输出是否完整,没有收到就把这次调用视为失败:

接口 正常结束的标记
Chat Completions data: [DONE]
Anthropic Messages message_stop 事件
Responses response.completed 事件

上游直接断开连接、没有发出任何错误事件时,流也会在没有结束标记的情况下结束,同样按失败处理。各协议的事件结构见流式响应。

客户端与代理设置

  • 客户端超时:设得比 6 分钟长,避免客户端先断开;流式请求优先设置读取间隔超时,而不是整次请求的总超时。
  • 按完整事件解析:SSE 事件以空行分隔,一个网络数据块可能只包含半个事件,也可能包含多个事件,不要把每个数据块当作一段完整 JSON。
  • 自建代理:如果应用和 LYRouter 之间还有 Nginx 等反向代理,请关闭响应缓冲(Nginx 中设置 proxy_buffering off),并把代理的读超时设得比 6 分钟长,否则流式内容会被攒成一大块才到达,或被代理提前断开。

重试与去重

  • 流式中断:收到 stream_interrupted、response.failed,或没有收到结束标记时,不要把已经收到的部分输出当作完整结果。重新调用会重新生成,内容可能不同,也可能再次触发工具调用;已经执行过的工具操作需要由应用去重,例如为每个操作携带业务上的幂等标识。
  • 504 / 500:网关已经在内部换过通道,客户端使用有上限的指数退避重试即可,不要无间隔地连续重试。
  • 持续失败:同一个 API Key 在 60 秒内大量请求失败会被临时熔断,返回 429 api_key_circuit_open,见429:限流与配额。

用日志定位

在请求日志中找到这次请求,打开详情:

  • 各阶段耗时:「④ 上游调用」占「总耗时」的大部分,说明时间花在上游;旁边显示「· N 次尝试,最长 X」时,说明换过通道,每次尝试都可能等待了接近 6 分钟。
  • 首 Token:日志列表的「首 Token」列(在 自定义列 中打开)显示上游开始输出前等待了多久。
  • 路由追踪:「上游调用尝试」中每次尝试的状态显示「超时」「连接失败」等,见查看实际路由。

在日志详情的各阶段耗时中比较总耗时、上游耗时和首 Token 时间。

图 1:在日志详情的各阶段耗时中比较总耗时、上游耗时和首 Token 时间。 截图为演示数据,点击图片可查看原图。

常见问题

客户端报超时,日志里这次请求却是 200?

答: 通常是客户端或中间的代理先断开了连接,网关仍然完成了这次调用。用日志的「总延迟」和客户端的超时设置比较;日志的「时间」是请求结束的时间,减去「总延迟」就是请求到达网关的时间。把客户端超时调到 6 分钟以上,或改用流式请求。

流式响应很久没有第一段内容?

答: 先看日志的「首 Token」。首 Token 很长说明上游迟迟没有开始输出,可以稍后重试或换用其他模型;首 Token 正常但客户端迟迟收不到内容,检查中间代理是否缓冲了响应。

流式响应解析 JSON 失败,应该检查什么?

答: 确认解析器按完整 SSE 事件读取,并能处理 data: [DONE] 这类不是 JSON 的行。如果请求在输出开始前就失败,返回的是普通的错误响应而不是事件流:网关拒绝的请求通常是 JSON 错误体,500、504 是 HTML 页面,请先检查状态码和 Content-Type。

提交超时问题时需要提供哪些信息?

答: 网关的响应中不包含请求 ID。请在日志中找到这次请求,附上日志详情顶部的 Request ID、调用时间、模型、是否流式、客户端的超时设置,以及客户端收到的报错原文或最后收到的几个事件。