← Back to Blog

529 Overloaded 与请求超时、流式中断怎么解决

Zivv11 min read
529超时流式

长任务跑一半失败,是 AI API 使用中最烦人的一类问题:报错可能是 529 Overloaded,可能是 read timeout,也可能是流式输出到一半突然停住。这三类问题经常被混为一谈,但根因位置完全不同——一个在上游容量,一个在你的超时参数,一个在网络链路。方向修错了,问题会一直复发。这篇把三类问题分开讲清楚,并给出可直接使用的超时配置、流式重试代码和长任务拆分方法。

先分清三种"没跑完"

现象典型报错根因位置
请求很快失败529 overloaded_error、503上游服务端容量吃紧
等了很久才失败read timed outtimeout某一层超时设置小于任务耗时
流式输出中途停止连接重置、SSE 中断、内容截断网络链路或上游中断

定位方法看失败发生的时间点:秒级失败且有明确状态码,是上游问题;总是固定在某个时长失败(比如恰好 60 秒),几乎可以断定是某一层的超时设置;输出过程中随机断掉,是链路或上游中断。

三类问题也可能叠加出现:高峰期上游过载(529)导致响应整体变慢,进而触发你设得偏小的 read 超时,最终表现成超时错误。所以排查顺序建议固定:先看状态码,再看时长规律,最后看断点位置。

529:上游过载,不是你的错

529 是 Anthropic 协议特有的状态码,错误类型为 overloaded_error;OpenAI 系接口的对应物是 503。它的含义是:服务端当前容量不足,暂时无法接收请求。要和 429 分清:

  • 429:你超过了自己的限额,问题在调用方,对策是降频和退避
  • 529 / 503:服务端整体过载,与你的 Key、配额无关,对策是重试和错峰

529 适合直接套用指数退避重试,写法与 429 相同,可以复用 429 排查文章 里的实现,把重试条件加上 529 和 503 即可。区别在于预期:429 退避几秒通常就过,529 在高峰期可能持续几分钟,重试上限要放宽,并且准备降级路径——比如高峰期把非关键任务切到另一个模型系列。多模型接入的意义之一就在这里。

另一个实用判断:529 是否集中在特定时段。上游过载有明显的高峰规律,如果你的失败集中在固定的几个小时,把重活挪到低峰段执行,比任何代码改动都有效。

超时参数:大多数"随机失败"的真凶

很多超时问题来自默认值:HTTP 库的默认读取超时普遍只有几秒到几十秒,而一个长输出的推理请求跑几分钟很正常。默认值撑不住,表现就是"长任务必挂,短任务没事"。

关键是分清超时的四个阶段,分别设置:

import httpx

client = httpx.Client(
    timeout=httpx.Timeout(
        connect=10.0,  # 建立 TCP/TLS 连接
        write=30.0,    # 发送请求体,长上下文要留够
        read=300.0,    # 等待并读取响应,长输出任务放大这一项
        pool=10.0,     # 从连接池获取连接
    )
)

两条经验:

  • 非流式请求的 read 超时必须覆盖整个生成时间。模型写两万 Token 可能要几分钟,read 设 30 秒必然中断
  • 流式请求的 read 超时作用于相邻数据块之间的间隔,而不是总时长,所以可以设小一些(比如 60 秒),既能及时发现挂死的连接,又不限制总生成时长

结论:凡是预期输出较长的任务,优先用流式,不仅体验好,超时语义也更合理。

另外要检查中间层:Nginx 反代的 proxy_read_timeout、云函数的执行时长上限、公司网关的空闲连接回收,任何一层小于任务时长都会在那一层掐断请求。遇到固定时长失败时,把这个时长拿去和每一层的配置对号入座,通常一对就中。

排查中间层还有一个技巧:直接从运行环境向 https://zivv.pro/v1 发 curl 测试,绕开自己的代理和网关。curl 直连正常而应用内失败,问题必然在应用与出口之间的某一层。

流式断连:怎么安全重试

流式请求的麻烦在于:断开时你已经拿到一半内容,直接重试意味着这一半的生成作废、重新计费。策略分两层:

  1. 能续写的场景(代码生成、长文写作):把已收到的内容作为上下文,让模型从断点继续,避免整段重来
  2. 不能续写的场景(JSON 等结构化输出):整次重试,但要保证请求幂等,跑两次不会产生副作用

判断"中断"还是"正常结束"也有讲究:OpenAI 兼容协议的流式响应以 data: [DONE] 收尾,Anthropic 协议以 message_stop 事件收尾。没有收到结束标记就断开的,一律按中断处理,即使已收到的内容看起来完整。

捕获中断并重试的基础写法:

import time

import httpx

TIMEOUT = httpx.Timeout(connect=10, write=30, read=60, pool=10)

def stream_with_retry(payload, max_retries=3):
    for attempt in range(max_retries + 1):
        chunks = []
        try:
            with httpx.stream(
                "POST",
                "https://zivv.pro/v1/chat/completions",
                headers={"Authorization": "Bearer sk-your-key"},
                json={**payload, "stream": True},
                timeout=TIMEOUT,
            ) as resp:
                resp.raise_for_status()
                for line in resp.iter_lines():
                    if line.startswith("data: "):
                        chunks.append(line[6:])
                return chunks
        except (httpx.ReadError, httpx.RemoteProtocolError, httpx.ReadTimeout):
            if attempt == max_retries:
                raise
            time.sleep(2 ** attempt)

注意捕获的异常类型:ReadErrorRemoteProtocolErrorReadTimeout 是典型的链路中断信号;4xx 错误不在此列——那是请求本身的问题,重试没有意义。

长任务拆分:最有效的稳定性手段

所有重试策略都有极限。一次让模型连续工作几十分钟的任务是最脆弱的形态:任何一层的抖动都会让全部进度作废。工程上的解法是拆分:

  • 按自然边界拆:按文件、按模块、按章节,每个子任务几分钟内能跑完
  • 中间结果落盘:每一步的产出保存下来,失败只重做当前步,不从头再来
  • 控制单次输出长度:与其让模型一次输出三万 Token,不如分三次各一万,单次失败的代价小得多

Agent 类工具(Claude Code、Codex 等)同理:"把整个项目重构掉"这种指令跑得越久越容易断,拆成"先改数据层""再改接口层"的多个明确子任务,每步验证后再继续,总耗时反而更短。这也是 Vibe Coding 工作流 推荐的做法。

以"翻译一百份文档"为例:不拆分的写法是一个循环跑到底,任何一次中断都要人工判断跑到哪了;拆分的写法是每份文档一个任务、完成即落盘、启动时先扫描已完成列表跳过。前者写起来快五分钟,后者在真实网络环境里能少熬好几个夜。

网关层能解决什么

上面都是调用方能做的。还有一类问题调用方解决不了:上游单点持续过载。直连官方时你只能等;走 Zivv 这类有账号池调度的中转,单个上游账号或节点出问题时后端自动切换,调用方看到的是持续可用。对 Claude Code 重度用户,Claude MAX 分组基于订阅账号池提供原生 Anthropic 协议接入,长会话场景的稳定性正是它的设计目标。各错误码的具体含义可查 错误码说明

要注意的是,网关解决的是"上游单点"的问题,你自己那一侧的超时参数和任务拆分仍然要做——链路上任何一层的配置问题,换多稳的上游都救不了。

三个常见误区

误区一:把所有超时调成无限大。 这只是掩盖问题:挂死的连接永远不释放,占住连接池和并发额度,最后表现为整个系统变慢。read 超时应该设成"略大于正常任务的最大耗时",而不是无限。

误区二:把 529 当成自己代码的 bug。 529 与请求内容无关,反复修改 prompt、参数、SDK 版本都不会改变结果。看到 overloaded_error 就按过载处理:退避、错峰、降级。

误区三:重试不设上限、不记日志。 无上限重试在上游长时间过载时会变成死循环,还把故障时间线搅乱。每次重试都应该记录时间、状态码和第几次尝试,事后才能核对是哪一层出的问题。

常见问题 FAQ

529 重试了十几次还是失败怎么办? 说明上游处于较长时间的过载期。不要再加密重试频率,把任务切到其他模型系列(比如从 Claude 切到 GPT 或 Gemini 完成非关键部分),或者错峰执行。

流式中断的部分会计费吗? 已经生成并传输的 Token 通常会计费,这正是断连成本高的原因。降低损失的方式是拆小任务、支持断点续写。

把 max_tokens 调小能避免超时吗? 能缓解但属于绕路:输出被截断,任务本身没完成。正确做法是把 read 超时设置到位、改用流式,再从任务设计上控制单次输出规模。

需要给长连接加心跳吗? SSE 流式本身有持续的数据传输,一般不需要额外心跳。真正要防的是中间层(代理、网关)的空闲超时,确认它们的阈值大于模型可能的"思考"间隙即可。

非流式请求的 read 超时到底该设多大? 用真实任务的最慢耗时乘以 1.5 到 2。先在低峰期用真实 prompt 测几十次记录耗时分布,拿不准就先设 300 秒,跑一周后按日志收紧。

结论

稳定性排查的顺序:先用状态码和失败时间点分类——529/503 是上游过载、固定时长失败是超时设置、随机断开是链路;然后把超时参数按四个阶段设置正确,流式任务加上安全重试,最后用任务拆分把单次失败的代价压到最小。做完这些还嫌上游不稳,就把调度问题交给平台:注册 Zivv 后一个 Key 接入 100+ 模型,账号池自动调度,三大协议入口一致,长任务不用再赌单一上游的状态。