一个站点返回 HTTP 200,body 里却塞了一句"API key is required for Puter.js"。代理服务看到 200,认为成功,把这坨错误信息原封不动地喂给了 Agent。
Agent 当然懵了。它收到的不是模型回复,是一段莫名其妙的认证报错。没有 fallback,没有重试——因为代理层告诉它"请求成功了"。
这件事让我重新审视了一个问题:当你的 AI Agent 依赖十几个免费 API 站点,"可用"到底意味着什么?
免费站点的生存现状
我接入了 15 个公益 API 站点,提供 GPT 和 Claude 系列模型的转发。这些站点大多由个人维护,跑在各种云服务上——有阿里云函数计算的,有 Cloudflare Workers 的,有 Render 免费层的,还有直接套 Puter.js 平台的。
实际探活一圈,结果是这样的:
- 4 个站点正常返回
- 5 个站点
/v1/models端点返回 403 - 2 个站点被 Cloudflare 人机验证拦截
- 1 个站点返回 200 但内容是错误信息
- 3 个站点没有 GPT/Claude 模型,只提供其他模型
正常率大约 27%。
但 403 不代表不能用。很多站点禁止查询模型列表(/v1/models),但 /v1/chat/completions 接口正常工作。探活 403 和实际不可用是两回事。
这就是第一个教训:健康检查的指标选错了,结论就是错的。

最阴险的故障:HTTP 200 + 错误 body
传统意义上的 API 故障很好处理。超时就重试,4xx 就换站点,5xx 就降级。HTTP 状态码是可靠的信号源。
但有一种故障不在这个范畴里:站点返回 HTTP 200,response body 却不是模型输出,而是一段错误消息。
我遇到的案例:某个站点后端基于 Puter.js 平台搭建。当站点自身的 API key 认证失效时,它不返回 401 或 403,而是返回 200 + 一段 JSON 错误信息 MissingAuthError: API key is required for Puter.js API。
代理服务只看 HTTP 状态码。200 就认为成功,直接把 response 透传给上层。Agent 框架收到一个"成功的请求",尝试解析 response 里的 choices[0].message.content——里面当然不是正常回复。
这种故障的排查路径特别绕:
- 从 Agent 框架的 session transcript 里找到错误时间戳
- 拿时间戳去代理服务的
main.log里找对应的请求 ID - 从请求 ID 关联到使用的 API key(日志里会记录
Use API key sk-X...XXXX) - 用 key 的后几位去本地配置文件里反查,定位到具体站点
四步,跨三个系统的日志。如果代理服务的 debug 日志没开,第 2 步直接断链。

代理服务默认不记日志
这是我踩的第二个大坑。
代理服务开箱即用时,debug 模式和日志写文件都是关闭的。出了错你什么都看不到——没有请求记录,没有上游响应,没有错误详情。
我在排查 Puter.js 那个 200 错误时,翻遍了 error log 一无所获。因为 error log 只记录 HTTP 非 200 的请求。一个返回 200 的"成功"请求,在 error log 里连影子都没有。
后来开启了三个配置项:
debug: true
logging-to-file: true
request-log: true
开启后,main.log 里会记录每个请求的完整链路:用了哪个 API key、请求了哪个模型、走了哪个上游站点、返回了什么状态码。排查效率直接上了一个台阶。
代价是日志文件会膨胀。但在这种多站点轮转的架构下,没日志就等于瞎的。

同一个模型,七个名字
GPT-5.3 和 Claude Sonnet 在不同站点的模型名不统一。有的站点叫 claude-sonnet-4-5-20250929,有的叫 claude-4.5-sonnet,有的叫 claude-sonnet-4.5。
这三个名字指向同一个模型能力,但在代理服务眼里它们是三个不同的模型。代理按模型名做 round-robin 轮转——你请求 claude-4.5-sonnet,只有注册了这个确切名字的站点才会被轮转到。
问题来了:
claude-sonnet-4-5-20250929→ 7 个站点提供claude-4.5-sonnet→ 1 个站点提供claude-sonnet-4.5→ 1 个站点提供
我一开始选了 claude-4.5-sonnet,只有一个站点在撑。那个站点 quota 一耗尽,直接全挂,Agent 降级到国产模型兜底。
换成 7 个站点覆盖的名字后,稳定性有了明显提升。坏一两个站点,还有五六个能兜住。
查模型覆盖度的方法很简单——遍历本地配置文件,统计每个模型名被多少站点注册了:
gpt-5.3-codex → 5 个站点
claude-sonnet-xxx → 7 个站点
claude-4.5-sonnet → 1 个站点 ← 避免选这种
选模型名的原则:覆盖站点数越多越好,单站点独占的名字是定时炸弹。

黑名单:屏蔽一个坏站点
发现某个站点有问题后,处置流程是这样的:
- 把站点名加入远端的黑名单文件(纯文本,一行一个名字)
- 从代理配置里移除该站点条目
- 重启代理服务
反过来,如果站点恢复了,从黑名单移除后需要重新跑一次配置同步,让该站点回到轮转池里。
一个容易忽略的点:每次从本地同步配置到远端时,同步脚本会自动把黑名单里的站点剔除。这意味着你不需要手动维护远端配置和黑名单的一致性——同步脚本替你做了。
但也意味着:如果你先同步再加黑名单,黑名单是生效的;如果你先加黑名单再同步,同步会自动剔除。顺序不影响最终结果,这是幂等设计带来的好处。

探活不等于可用
站点探活我做了两种:
- 被动探活:对每个站点的
/v1/models端点发 GET 请求,看返回状态码 - 主动探活:直接发一个
reply OK的聊天请求,看能不能拿到正常回复
被动探活的问题前面说了——很多站点禁止模型列表查询,返回 403,但实际调用没问题。
主动探活更准确,但有成本:每次探活消耗上游站点的 quota。公益站点的 quota 本来就紧张,频繁探活等于加速耗尽。
我的做法是:日常只看被动探活结果做参考,不自动屏蔽。只有当 Agent 实际调用时报错了,才根据错误日志去定位和处置。
换句话说,让真实流量做探针。
Quota 耗尽的连锁反应
公益站点的 quota 是共享的。你用的同时,其他人也在用。一个热门模型可能在某个站点上半天就被刷光额度。
quota 耗尽的表现不统一:
- 有的站点返回
429 Too Many Requests - 有的返回
insufficient_user_quota(在正常的 JSON error 里) - 有的直接返回
503 Service Unavailable - 有的——如前所述——返回 200 + 一段错误文本
代理服务会自动轮转到下一个站点,所以单个站点 quota 耗尽通常不致命。致命的是"所有提供该模型的站点同时 quota 耗尽"——这时候 Agent 就降级到 fallback 模型了。
减少这种风险的办法就是选覆盖站点多的模型名。5 个站点同时 quota 耗尽的概率,远低于 1 个站点耗尽的概率。

Claude 还是 GPT?
我先用 GPT-5.3 当 primary,后来想试试 Claude Sonnet。切过去用了一个下午,感受是:Claude 在公益站点上的体验明显差于 GPT。
不是模型能力的问题,是供给侧的问题:
- Claude 模型在公益站点上的响应延迟普遍更高(部分请求超过 25 秒)
- Claude 模型的 quota 消耗更快(可能是因为 Anthropic 的定价更高,站点分配的额度更少)
- 经常卡住不回复,没有错误信息,就是挂着
GPT-5.3 在同样的站点池子里,5 个站点覆盖,响应快得多,几乎没出过卡死的情况。
结论:在公益站点的场景下,优先选供给充裕的模型,而不是"理论上更强"的模型。可用性比能力上限重要。

配置同步的意外需求
远端服务器的配置必须从本地单向同步。但同步不是简单的文件复制,有几个约束:
- 不能覆盖远端的机器相关配置(端口、目录、服务密钥)
- 不能覆盖远端的兜底模型(国产模型必须保留)
- 同步完要自动应用黑名单
- 远端只有 Python 3.6,不能用 Ruby(本地用 Ruby 做 YAML 合并是可以的)
最后的方案是:本地用 Ruby 做配置合并(因为 Mac 上 Ruby 处理 YAML 很方便),合并完的文件 rsync 到远端,远端用 Python 做黑名单过滤和站点探活。
两种语言干同一件事,维护成本高。但远端装不了 Ruby,也没有 Node.js 环境,只有系统自带的 Python 3.6。在受限环境下,用什么语言不是你选的。

可复用 > 快速修复
整个过程中最深刻的教训:调试时图快手动 SSH 操作完全可以理解,但修完之后一定要把操作固化到脚本里。
我最后写了两个脚本:一个管配置同步,一个管模型切换和运维。每个命令都是幂等的,跑多少次结果一样。脚本自动备份配置、自动重启服务、自动应用黑名单。
下次某个站点又出幺蛾子,我不需要 SSH 进服务器翻日志改配置。一行命令:
# 屏蔽故障站点
./manage.sh blacklist add "站点名"
# 查看当前状态
./manage.sh status
# 切换模型
./manage.sh switch proxy/gpt-5.3-codex
事后看这套东西
用十几个不稳定的免费站点拼出一个相对可用的 AI API,这件事本身就挺魔幻的。你在做的不是传统的 API 集成,更像是在搭一个分布式系统——节点随时可能挂,quota 随时可能耗尽,返回值随时可能撒谎。
能稳定跑起来靠的不是每个节点的可靠性,而是整个系统的容错设计:多站点覆盖做冗余,round-robin 做负载均衡,黑名单做故障隔离,fallback 链做降级保护,日志做事后追溯。
每一层都不复杂,但少了任何一层都会在某个深夜突然出问题,然后你对着一个没有日志、没有错误码、返回 200 的"成功请求"发呆。
建议把日志开上。这不是可选的。