OpenAI API 连接超时:长连接、流式输出与节点的三角关系
一句话结论
普通聊天是短请求,断一次几乎无感;API 的流式输出和 Codex 的长任务是持续数分钟的单条连接,中途任何一次链路抖动、空闲超时或中转切换都会直接中断。所以问题往往不在「能不能访问」,而在链路的连续性:跳数越多、空闲回收越激进的线路,越容易在长任务上翻车。
同一个订阅,浏览器里聊一下午没出过问题,换到终端上调 API,跑到第三分钟抛出连接重置;agent 类工具跑一个稍长的任务,输出到一半突然截断,重来一次又断在另一个位置。这种「每次断的地方都不一样」的特征,基本可以排除地区判定和账号资格——那两类问题的表现是稳定复现的拒绝,而不是断在半路。
真正的变量是时间。网页版的一次问答是几秒钟的短请求,链路即使抖了一下,前端悄悄重试一次,你根本不知道发生过。而流式输出和长任务是一条持续几分钟甚至更久的连接,链路上任何一个环节在这段时间里做了一次会话回收、节点切换或者重传失败,整条连接就没了,也没有谁替你重试。
所以这类问题的排查方向不是「这个节点能不能访问」,而是「这条链路能让一条连接活多久」。下面按数据包从客户端到落地经过的顺序,把可能掐断它的环节逐个点名。
为什么网页版好好的,API 一跑长任务就断
三种用法对链路施加的压力,完全不在一个量级。
| 使用方式 | 单条连接持续时间 | 中途断开时的表现 | 是否有自动重试 |
|---|---|---|---|
| 网页版对话 | 数秒到数十秒 | 页面转圈后自动恢复 | 前端通常会重试 |
| API 流式输出 | 数十秒到数分钟 | 输出截断、抛出连接错误 | 默认没有,要自己写 |
| 命令行 agent/长任务 | 数分钟到数十分钟 | 任务半途终止,上下文丢失 | 部分工具会重来一整轮 |
同一条链路,故障率不变,暴露概率却随连接时长线性上升。一条平均每十分钟抖动一次的线路,对网页版意味着「偶尔卡一下」,对二十分钟的长任务意味着「几乎必然失败」。
判断依据:如果多个不同地区的节点都在同一个位置报同样的错,先怀疑账号或平台侧;如果断点每次都不同、且重试有时能成功,那就是链路连续性问题,属于本文讨论的范围。
流式输出到底对链路提了什么额外要求
流式输出用的是 SSE(服务端推送事件,服务器在一条不关闭的 HTTP 连接上持续往下写数据)。它的流量特征很特别:总量不大,但持续时间长、包很小、间隔不规则。
这带来三个普通网页浏览提不出来的要求。
| 要求 | 破坏它的链路现象 | 你看到的症状 |
|---|---|---|
| 连接从头到尾不能重建 | 节点自动切换、会话表被清空 | 输出到一半直接断,错误提示是连接被重置 |
| 包序完整且能持续送达 | 中间某段丢包严重、重传超时 | 输出突然卡住十几秒,然后报错 |
| 大包不能被静默丢弃 | 路径 MTU 黑洞(超过某个尺寸的包被丢且不返回通知) | 握手成功、短请求正常,一发长内容就卡死 |
第三条最容易被误判成「节点慢」。MTU(一个数据包在这条路径上允许的最大尺寸)如果被中间某一跳压低,而沿途设备又不回送提示报文,超尺寸的包就会消失得无声无息。表现是能连上、能发短消息,一旦请求体变大或返回内容变长就永久挂起。
空闲超时与连接回收:谁在中途把连接掐了
一条连接从你的设备到落地,途中会被好几个组件登记为「会话」,每个组件都有自己的回收计时器。任何一个先到期,连接就结束了。
| 环节 | 常见空闲回收区间(参考值) | 触发条件 |
|---|---|---|
| 本机代理客户端 | 数分钟量级 | 无数据传输且未开 keepalive |
| 家用路由器/运营商 NAT | TCP 较宽松,UDP 明显更短 | 会话表项长时间无包 |
| 机场入口机与中转节点 | 按各家配置差异很大 | 连接闲置、或节点重启维护 |
| 平台侧网关 | 有上限,长任务需分段 | 单连接超过服务端允许时长 |
上表的区间只是行业常见量级,不同设备、不同运营商差别很大,请当成排查思路而不是精确数字。
关键在于「空闲」的定义:这些组件多数只看有没有数据包经过,不理解应用层语义。推理模型在长时间思考、agent 在本地执行工具调用时,连接上确实一个字节都不走,在计时器眼里和已经废弃的连接没有区别。开启 TCP keepalive,让连接周期性发出探测包,正是为了避免这种误判。
中转跳数、负载均衡和长连接为什么天然冲突
每多一跳,就多一张会话表、多一个可能重启的进程、多一个独立的超时计时器。对短请求来说这些几乎不可见;对长连接来说,它们是串联关系——任意一环出事,整条连接就断。
| 线路结构 | 对长任务的主要风险 |
|---|---|
| 直连落地 | 环节最少,但跨境段拥堵时丢包直接命中长连接 |
| 单跳中转 | 增加一个会话表与一次转发进程重启的可能 |
| 多跳/动态优选中转 | 后端出口可能在任务中途更换,连接必然重建 |
| 客户端负载均衡组 | 定时测速触发切换,正在跑的连接被一并丢弃 |
最容易被忽略的是最后一行。很多人把 AI 相关规则指向一个 url-test 或 fallback 策略组,图的是自动挑最快节点,结果这个组每隔一段时间就重新评估一次,切换发生的瞬间,正在跑的长任务一起陪葬。想理解跳数与出口结构的差别,可以先看中转与直连的路段拆解。
顺带说一句,出口在任务中途更换,除了断连还会引出另一个问题:平台看到同一会话里出口 IP 变了,可能触发风控。那属于出口 IP 漂移引发的验证码与登出,和本文的传输层断开是两种失败。
协议选择会影响长连接的存活时间吗
会,但方向不像很多人想的那样单一。
| 协议族 | 对长连接的优势 | 需要留意的风险 |
|---|---|---|
| TCP 系(Trojan、VLESS 等) | 行为可预期,NAT 对 TCP 会话通常更宽容 | 高丢包链路上重传堆积,卡顿明显 |
| QUIC/UDP 系(Hysteria2、TUIC) | 丢包环境恢复更快,部分实现支持连接迁移 | 本地对 UDP 限速或阻断时,长时间传输更容易被压制 |
结论是:在丢包率高但 UDP 通畅的网络里,UDP 系协议对长任务更友好;在校园网、公司网这类对 UDP 做 QoS 的环境里,反而是老实的 TCP 系更能撑。两者的设计差异见Hysteria2 与 TUIC 的拥塞控制对比,而怀疑本地 UDP 被限速时,按UDP 被限速的判断与回退流程先做验证再换协议。
客户端侧可以先调的四个参数
在换机场之前,这四项调整成本最低,且经常能直接解决问题。
- 把 AI 相关规则从自动优选组改为固定节点。 目的是消除任务中途的节点切换,这是长任务最常见的单点故障。稳定优先于最快。
- 开启 TCP keepalive 并把间隔设得比链路上最短的回收计时器更小。 目的是让空闲期间也有包在走,避免被 NAT 或中转当成废弃会话清掉。
- 适当下调 MTU。 目的是绕开路径上的 MTU 黑洞。判断方法是:短请求正常、长响应必挂,调低后症状消失,基本可以确认。
- 在调用侧加上超时与断点续传逻辑。 目的是承认链路总会抖,把「断一次就全废」变成「断一次重连一段」。这是唯一不依赖机场质量的那一项。
下面是一段结构示例,展示前两项在配置里的位置,主机名一律用占位域名,不能直接使用:
# 结构示例,非可用配置;example.invalid 为占位域名
proxies:
- name: 'ai-fixed'
type: trojan
server: node.example.invalid
port: 443
tcp-keep-alive-interval: 30 # 空闲期也保持有包在走
mtu: 1380 # 怀疑 MTU 黑洞时下调试试
rules:
- DOMAIN-SUFFIX,api.example.invalid,ai-fixed # 固定出口,不进优选组
参数名与可用字段随客户端版本变化很大,上面只演示「该在哪一层动手」。实际写法请以你所用客户端的当前文档为准,改完务必备份原配置。
怎么用一次可复现的长任务测试对比两条线路
要比较两条线路谁更适合长任务,测速跑分没有意义——它测的是短时间峰值带宽,恰好回避了连续性这个唯一重要的变量。用下面这个流程,半小时就能得出可比结论。
- 固定除线路以外的所有条件。 同一台设备、同一个时间段(建议放在晚间高峰)、同一个请求内容、同一个客户端版本。变量只留线路一个。
- 发起一次持续足够久的流式请求,并给每一行输出打上时间戳。 目的是把「断在第几秒」记录下来,而不是只知道「失败了」。
- 同一条线路连测三轮。 单次结果没有意义,长连接问题的典型特征就是概率性发生。
- 换到第二条线路,重复第二、三步。
- 对照三项指标下结论: 完成率(三轮中跑完几轮)、平均断点位置、断开时的错误类型。
# 结构示例:给流式输出的每一行打时间戳,端点用占位域名
curl -N -sS https://api.example.invalid/v1/stream \
-H "Authorization: Bearer <你的密钥>" \
-d '{"stream": true, "prompt": "输出一段足够长的内容"}' \
| while IFS= read -r line; do
printf '%s %s\n' "$(date +%H:%M:%S)" "$line"
done
记录成这样一张表,两条线路的差别通常一眼就能看出来:
| 线路 | 三轮完成情况 | 平均断点 | 报错类型 |
|---|---|---|---|
| A | 3/3 | — | — |
| B | 1/3 | 约第 2 分钟 | 连接被重置 |
单次成功不代表线路可用,单次失败也不代表线路不行。平台侧同样会限流和抖动,所以任何结论都要基于多轮记录,并注明测试时间与网络环境——不写条件的测试结果没法复核,第二天复现不出来也说不清是哪里变了。
小结
网页版正常而 API 频繁断开,症结在连接的持续时间,不在地区判定。链路上的会话回收计时器、中转跳数、优选组的自动切换和路径 MTU,共同决定了一条连接的存活上限。先在客户端侧固定节点、打开 keepalive、下调 MTU、给调用加上重连,再谈换线路。判断线路优劣时,用带时间戳的多轮长任务记录,不要用测速软件的峰值数字。把这一项和其他节点要求放在一起权衡,可以接着看AI 场景的节点选型清单。
常见问题
网页版 ChatGPT 一直很稳,为什么同一个节点跑 API 就断?
网页版的一次问答是几秒钟的短请求,而且前端会自动重试,链路抖动你感知不到。API 的流式输出是一条持续数分钟的单条连接,中途任何一次回收、切换或重传失败都会直接终止,并且没有默认重试。
报错里出现 connection reset 和 incomplete chunked read,说明是哪一侧的问题?
这两类报错的共同点是连接在传输中途被断开,而不是握手被拒绝。握手层面的失败通常表现为 403、地区不支持或证书错误。中途断开更多指向链路上的会话回收、节点切换或 MTU 问题。
推理模型思考很久没输出,会不会被当成空闲连接回收掉?
有可能。部分链路环节按「一段时间没有数据包」来判断会话是否失效,而长时间思考期间确实没有应用层数据。开启 TCP keepalive 让连接周期性发出探测包,可以降低被中途回收的概率。
换成 Hysteria2 这类协议能让长任务更稳吗?
不一定。它在高丢包链路上的恢复能力更强,但如果本地网络对 UDP 做限速或阻断,持续数分钟的大流量传输反而更容易被压制。协议要按你所在网络的实际表现选,不能只看理论。