事件:2026-10-15 DeepSeek 网页/API 部分中断(官方口径 08:11--13:04,Asia/Shanghai)。
官方通报 :status.deepseek.com/incidents/7116348214287
本文只记录一件事:同一时段、同一个官方端点、同一把 key,为什么"用代码直调 API"失败率很高,而 Agent 类工具(ZCode / Codex / DSH 等)看起来一直稳定有输出 。文中"代码直调侧"指我的 AI 助手进程(直接调用官方 API,不经任何中转)。
标注约定:【事实】 =有实测/日志证据;【观点】 =我的解释与推断;【未证】 =尚无证据支持。
数据来源 :Agent 侧数字取自该客户端自身的本地诊断日志(事件类型、耗时、状态码、重试记录);代码侧数字取自进程自己的调用记录,以及一个独立探针(同机、同一把 key、同一个官方端点)。
口径 :尝试级失败率 =失败尝试数 / 总尝试数;请求级失败率 =失败请求数 / 总请求数(一次请求的重发算同一请求);用户可见失败率 =用户最终看到失败的比例。日志随使用持续增长,涉及总量的数字是测量时刻的快照。
边界 :本文只描述客户端可观测到的差异,不推断服务商内部实现;样本来自单次事件、单台机器、单个账号。
三句话结论
- 那天服务端没有全挂 ,而是长时间处于"部分中断"状态 。官方状态页的色条是贯穿事件全程(08:11--13:04)的时间轴:两条 API 服务在窗口内大部分时间是橙色=部分中断 (Flash 起始处另有很小一段黄色=性能下降 ),绿色只在窗口两端的刻度之外 、窗口之内从未回到绿色,两条 API 全程未转红;红色只出现在对话服务上(完全中断 )。表现就是按比例拒掉一部分请求、把一部分请求拖慢。
- 同一时段,两个客户端的失败率差不多 ,但失败形态完全不同:Agent 侧几乎全是"秒回的 503"(可重发),代码直调侧多是"一个字节都不回的静默"(不可重发)。
- 差别出在客户端的三个选择:是否流式 (决定失败可不可重发)、是否重发 (决定失败变不变成延迟)、单次死线多长(决定"慢"算不算失败)------与身份、密钥、网络无关。
目录
- [1. 事件本身:官方说的"部分失败"到底是什么意思](#1. 事件本身:官方说的"部分失败"到底是什么意思)
- [2. 同一时段两边的实测对照](#2. 同一时段两边的实测对照)
- [2.1 Agent 侧(以 ZCode 为例,数据取自它自己的诊断日志)](#2.1 Agent 侧(以 ZCode 为例,数据取自它自己的诊断日志))
- [2.2 代码直调侧(我的 AI 助手进程)](#2.2 代码直调侧(我的 AI 助手进程))
- [2.3 逐项排除掉的"嫌疑"(都实测过)](#2.3 逐项排除掉的"嫌疑"(都实测过))
- [3. 两种失败形态,以及它们各自的指纹](#3. 两种失败形态,以及它们各自的指纹)
- [4. 结论:为什么"代码直调失败、Agent 稳定"](#4. 结论:为什么"代码直调失败、Agent 稳定")
- [4.1 能复现到什么程度](#4.1 能复现到什么程度)
- [5. 尚未证实、值得后续验证的点](#5. 尚未证实、值得后续验证的点)
- [6. 给"用代码直调 API"的工程建议](#6. 给"用代码直调 API"的工程建议)
1. 事件本身:官方说的"部分失败"到底是什么意思
【事实】 官方状态页时间线(出处见文首「官方通报」链接):08:11 开始排查 → 09:06/09:08 定位原因 →
12:49 已实施修复、正在监控 → 13:04 恢复。总时长 4 小时 52 分。
【事实】 同一页「受影响组件」的色条是时间轴 ,两端有刻度,只有这两个刻度之外是绿色 ,如图:

【事实】 四种颜色的含义可以从色条的悬停提示 读到(页面本身没有文字图例):黄=性能下降 、
橙=部分中断 、红=完全中断 ;绿 悬停无提示,即"正常"。对应关系是:
绿=正常;黄=性能下降(变慢但仍在服务);橙=部分中断(按比例失败);红=完全中断(基本不可用)。
【观点】 于是"部分中断"的准确含义是:在官方划定的这 4 小时 52 分里,API 几乎没有回到过正常状态 ------
两个刻度之间那一整段都没有绿色:Pro 全程 是橙色,Flash 只在开头有很小一段"性能下降"(黄)、随后一路橙色。
绿色只出现在刻度之外,也就是故障开始之前 与恢复之后 。换句话说,这 5 小时里 API 大多数时刻都在
"按比例失败",但它始终没有转红(没有整段停机);红色只落在对话服务那一条上(那是网页/客户端的服务,
不是 API)。两条 API 的差别也值得注意:
Flash 是先变慢、随后才升格为部分失败;Pro 没有黄色段,从 08:11 起就直接是部分中断。
另外,本文的实测样本(09:50 之后)全部落在橙色段内,黄色段(08:11 起的那一小段)本机没有留下数据。
2. 同一时段两边的实测对照
2.1 Agent 侧(以 ZCode 为例,数据取自它自己的诊断日志)
【事实】
| 指标 | 数值 |
|---|---|
| 失败尝试数 | 45 次:服务端 503 共 40 次 (reason=server_error)+ 客户端取消 5 次 ;超时类 0 次 |
| 重发 | 40 次,间隔 1~14 秒;配置上限为 11 次尝试/请求,实测最多用到第 5 次 ;重发用尽 0 次 |
| 成功请求耗时 | 中位 ≈4.2 秒、p90 ≈13.3 秒、最长 23.7 秒 ;约 17% 超过 10 秒 |
| 最长的一次"失败" | 等了 55.2 秒才认输(说明它有长死线,不是无限等) |
| 尝试级失败率 | ≈12%(失败尝试 45 次 / 总尝试约 370 次,抽样快照;其中服务端 503 为 40 次) |
| 用户可见失败率 | ≈0 |
| 传输 | 当天全部请求 transport=sse(流式),无一条非流式 |
| 请求形态 | 带 tools,回答是 tool_calls(toolCallCount 中位 1、最大 4) |
【事实】 从该客户端实现可查证的几点:它对这类自定义 provider 走的是标准 OpenAI 兼容的
chat-completions 协议(内部 kind openai-compatible 映射为 API 格式 openai-chat-completions);
它基于 Vercel AI SDK 的流式接口(streamText),这也解释了"为什么它总是流式"------该接口默认就是
stream: true 的 SSE;另外它不发送 stream_options / include_usage 这类字段,
即请求体里没有超出常见实现之外的"特殊参数"。
2.2 代码直调侧(我的 AI 助手进程)
【事实】 当时的客户端配置:非流式(stream: false)、失败不重发、单次 30 秒硬超时。
| 时段 | 结果 |
|---|---|
| 09:55--10:15(与 Agent 活跃期重叠) | 10 次请求:成功 4(1.8~3.1s)、静默 4 (各 30.1s)、503 2 (0.12~0.71s)→ 请求级成功率 40% |
| 10:15:50--10:39:17 | 32 次回答全部失败 :静默 28 次(各自卡满 30.12~30.40s)+ 503 4 次(0.11~0.29s),0 成功 |
【事实】 独立探针(同一个 Python 环境、同一把 key、同一个官方端点)在故障窗内的表现:
- 10:08 连发 6 次 → 4 次秒回 503 (0.2~0.5s)+ 3 次静默(卡满 30s)。同一秒发出的请求,命运分成两拨。
- 10:50 同分钟单变量对照 (同一份 body,只翻
stream):流式 3/3 秒回 503 (0.11~0.17s)、
非流式 3/3 正常出答案(4.4~4.8s)。 - 11:44--11:49 连续 70 秒 :多轮尝试(数十次)全部静默 ,其中流式请求也被吞(4/5 静默)。
2.3 逐项排除掉的"嫌疑"(都实测过)
【事实】
| 嫌疑 | 结论 | 依据 |
|---|---|---|
| key / 账号不同 | 排除 | 逐字节比对:Agent 配置里的 key 与代码侧环境变量完全相同 |
| 端点 / 模型不同 | 排除 | 同为 https://api.deepseek.com + deepseek-flash,均为官方直连、无中转 |
| 协议版本不同 | 排除 | 服务端支持 h2,但 Python 侧未安装 h2 包 → 只能 HTTP/1.1;Agent 侧(Node)默认同为 1.1 |
| IPv6 黑洞 | 排除 | 该域名当天只有 3 条 IPv4 记录、无 AAAA;本机连这 3 个 IP 各 8~14ms 就通 |
| 后端 IP 差异 | 排除 | 按 IP 交错打:同一窗口里三个 IP 一起挂、一起通(单点测试曾给出"两个 IP 黑洞"的假象,是撞上时间窗口) |
| 重发节奏 | 不是主因 | 用 Agent 式节奏(1.2/2/3.6/5.4/7.8 秒间隔、最多 6 次、70 秒预算)复测:6 发全挂 |
| 连接冷热 | 未验 | 冷/热交错测过,但只落在健康窗口(51/51 与 50/50),故障窗口没拿到样本 |
| 流式 vs 非流式 | 有关,但不是万能 | 见 10:50 对照(单变量影响失败类型);但 11:44--11:49 深窗口里连流式也被吞 |
3. 两种失败形态,以及它们各自的指纹
【事实】 在本次事件中,客户端观察到的失败只有两种形态(外加正常成功):
| 形态 A:当场拒绝 | 形态 B:静默(收下不理) | |
|---|---|---|
| 服务端给了什么 | HTTP 503 + JSON(code=service_unavailable_error、message=Service is too busy...、带 request_id) |
什么都没给:无状态行、无响应头、无 body、无 request_id |
| 到达时间 | 0.1~0.7 秒 | 正好等于客户端自己配的超时值:30.12~30.40 秒(另一次是 8.13~8.23 秒) |
| 日志长相 | InternalServerError: Error code: 503 ... |
APITimeoutError: Request timed out. ------ 这是客户端自己造的异常,不是服务端回的错 |
| 可重发吗 | 可以:几百毫秒后就是一次新的抽签 | 基本不行:实测静默型重发 14 次全挂(换新连接也一样) |
【事实】 一个必须说准的细节:APITimeoutError 区分不了 "TCP 连接没建成"与"连上了但一个字节没回"
------openai SDK 把 httpx 的 ConnectTimeout 与 ReadTimeout 归一成同一个异常。所以形态 B 严格表述是
"在超时之内没拿到任何响应"。
【观点】形态 B 的成因 :非流式 等于客户端说"你收下、稍后给我都行",服务端因此获得了"把请求挂起来"
的资格;流式 等于"现在就要",服务端只剩两条路------当场服务或当场拒绝,拒绝就是秒回 503。
这解释了 10:50 那个单变量对照,也解释了为什么 Agent 侧的失败清一色是 503。
4. 结论:为什么"代码直调失败、Agent 稳定"
【观点】 三件事叠加,全部在客户端侧,与身份、密钥、网络无关:
- 服务端长时间处于"部分中断"(橙)、按比例失败 (没全挂:两条 API 全程未转红),所以在两边同时发请求的那段重叠期(09:55--10:15)里,两边都在挨打 ------Agent 侧当天尝试失败率也有 ≈12%,只是一次都没报给用户。需要说明的是:重叠期只有这一段,最惨的两段(代码侧 32 连败的 10:15:50--10:39:17、探针 70 秒全静默的 11:44--11:49)里 Agent 零请求,两边基本是错开的。
- Agent 把失败吸收成延迟 :流式(保证失败是"秒回可重发的 503",而不是"静默不可重发")+ 重发
(间隔 1~14 秒、上限 11 次尝试,实测最多用到第 5 次)+ 长死线 (成功里有 17% 超过 10 秒、最长 23.7 秒;有一次失败等到 55.2 秒)。
用户看到的是"这次慢了一点",看不到底下的 503。 - 代码直调把同一份失败率原样落成失败 :非流式(失败形态变成静默、不可重发)+ 零重发
- 30 秒硬超时(连"慢但活着"的答案也一起判死)。
一句话 :流式本身不带来成功,它只是把失败从"静默不可重发"变成"秒回可重发";真正把失败洗掉的是重发。
而重发只有配上流式才有意义------非流式的失败大多进的是"收下不理"的队列,重发还是进同一条队列。
4.1 能复现到什么程度
【观点】 代码侧只要满足三个条件,就能复现 Agent 的观感(失败被吸收、用户不可见):
- 流式请求(把失败从"静默"变成"秒回 503",让重发有对象);
- 重发(间隔要够开,1 秒起;实测 0.25 秒级的密集重发全落在同一个坏窗口里,等于没重发);
- 不设短死线(把单次上限放到几十秒级------Agent 实测一次失败等满 55.2 秒,它不是无限等)。
【观点】 但复现的是"同样的输入 → 同样的待遇 ",不是"一定成功":在服务端完全不响应 的窗口里
(我实测到连续 70 秒一个字节都不回,那一刻连流式也被吞),任何客户端都拿不到输出,区别只是
报不报错------Agent 表现为"一直转圈",代码直调表现为一行失败。
【观点】 顺带纠正一个常见误读:"一般调用不会加这些东西"只对一半:重发在很多 SDK 里是默认开的
(openai-python 默认 2 次、自带退避),真正一般没人加的是流式 与长死线;而且重发必须配流式才有意义。
【未证】 Agent 工具的"稳定"里也有多少来自"它们的活跃时段没落在最黑的窗口里",无法从数据上断定:
最黑的那两段(连续 70 秒、以及一段 23 分钟)恰好没有 Agent 的样本。只能推断:它们若在场,大概率也拿不到输出。
5. 尚未证实、值得后续验证的点
【未证】 按嫌疑强弱排序:
- 请求负载的形状 :Agent 侧每次带 tools、回答是 tool_calls、思考未显式关闭(其 usage 里带 reasoningTokens 字段)、
prompt 体量大得多;代码侧是纯文本小请求。这些是否影响服务端的准入/路由决策(比如"大预留的请求不予排队、
直接拒绝"),未验证。 - 连接模式 :Agent 每分钟 3~5 个请求、长驻进程、连接长期热;代码侧每几分钟一个请求、连接大概率冷。
冷/热对照只在健康窗口测过(无差异),故障窗口无样本。 - HTTP 层指纹 :UA、
x-stainless-*之类的自定义头、TLS ClientHello 特征、连接复用/空闲关闭策略。
协议版本、IP 族、DNS 均已排除,头与指纹完全没验。
【观点】定论方法(任选其一即可把上面三条钉死):
- 抓包:本地挂 HTTPS 代理(mitmproxy),把 Agent 的真实请求体与请求头原样录下来,逐字段与代码侧对照;
- 读源码 :该 Agent 客户端是开源的,直接看它构造请求的那段代码,就能确定
stream/max_tokens/
temperature/ tools schema / 请求头 / 重试与超时的默认值; - 坏窗口同时打 :同一个进程同时发"Agent 形状"和"现状形状"两条请求并交错,比较失败类型分布
(秒回 503 还是静默)。坏窗口一出现即可分辨,是三种方法里最直接的因果验证。
6. 给"用代码直调 API"的工程建议(不依赖上面未证项)
- 区分失败类型再决定要不要重发 :拿到 5xx(有状态码)→ 立刻重发;卡满超时(无任何响应)→ 多发无益,
要么换链路、要么报告失败。 - 重发必须够开:间隔从 1 秒起步、退避翻倍;密集重发(几百毫秒级)在"按比例失败"的场景里等于没重发。
- 上游"慢"和"死"要用流式来分辨 :流式下首字到达即证明链路活着,可以继续等;非流式下两者在客户端
完全同形,只能靠一个拍脑袋的超时值区分------这是非流式最贵的隐性代价。 - 短超时会制造假的失败 :对面变慢时,8/15/30 秒的硬超时会把"慢但活着"的正确答案一并判死。超时值应
按"可接受的最长等待"来定,而不是按"平时多快"来定。 - 算清失败率的两个口径 :尝试级失败率 (更接近服务端真实水位)与用户可见失败率
(≈ 尝试失败率 ^ (1 + 重发次数))。Agent 当天的观感好,就是因为前者 ≈12% 而后者 ≈0------
不是因为它没被打到。 - 别把"看起来稳定"当成"服务健康" :同一天里,同一个端点对同一个账号,一边是"偶尔慢"、一边是"几乎全挂",
差别全在客户端处理策略;只看自己的失败率会得出错误结论。