key-pool-failover-cli · ZH · 2026-10-28

给只认一个 base URL 的命令行工具接上多 key 故障转移池

面向只接受单 base URL 和单 key 的 CLI 工具的本地故障转移垫片方案:用一个本地反向代理统一入口,内部维护多 key 池,按状态码自动重试切换,无需改动工具配置,并说明健康检查、超时与日志要点。

问题:工具只认一个 base URL

很多命令行 AI 工具(编辑器插件、脚本封装、终端助手)的配置只接受一个 base_url 和一个 API key。这在单一账号下没问题,但一旦你手里有多把 key、或者想在不同模型供应商之间做冗余,就会卡住:工具本身不支持 key 池,也不支持失败重试切换。

直接改工具源码通常不现实。可行的办法是在工具和上游之间加一层本地故障转移垫片:工具仍然只看到一个 base URL 和一把 key,垫片在背后管理整个池子。

垫片的核心结构

垫片是一个跑在本机的小型 HTTP 反向代理,职责只有三件:

  • 接收工具发出的 OpenAI 兼容请求(/v1/chat/completions 等)
  • 从池子里挑一把 key 转发到上游聚合站
  • 根据响应结果决定是返回给工具,还是换下一把 key 重试

整体链路:

CLI 工具 → http://127.0.0.1:PORT/v1 → 垫片(选 key、重试)→ 上游聚合站 /v1

工具配置里只填 http://127.0.0.1:PORT/v1 和垫片自己签发的一个占位 key。真正的上游 key 全部放在垫片的配置文件或环境变量里,工具完全看不到。

什么时候该切 key

故障转移的关键是判断哪些失败值得重试。不是所有错误都应该换 key:

  • 应该切换:认证失败(key 失效)、限流(429)、上游 5xx、连接超时、明显的单 key 配额耗尽
  • 不应该切换:请求格式错误(400 且与 key 无关)、内容被拒、流式响应已经开始输出后中途断开

最后一条尤其要注意:如果响应已经以 SSE 流的形式返回给工具,就不能再换 key 重放,否则工具会收到重复内容。正确做法是在收到上游响应头之前完成重试判断,把流式转发推迟到确定这次请求成功之后再开始。

重试策略

一个够用的策略:

  1. 按优先级顺序取 key(或轮询)
  2. 请求失败且错误可重试时,标记该 key 短暂冷却,取下一把
  3. 总尝试次数设上限,避免无限循环
  4. 全部 key 都失败时,把最后一个错误原样返回给工具

冷却时间不必复杂,按失败类型给不同档位即可。重点是把「某把 key 刚失败」这个状态记住,让后续请求优先绕开它,而不是每次都从头试。

流式请求的处理

流式是最容易出错的部分。要点是:

  • 垫片先向上游发起请求,等到确认上游返回 2xx 且开始吐数据后,再向工具写出响应头
  • 在这之前的所有失败都可以安全地换 key 重试
  • 开始向工具写数据后,垫片只做透传,不再介入

这样工具看到的仍然是一个正常的长连接流,不会感知到背后发生过重试。

健康检查与 key 状态

池子里的 key 会动态变化:有的临时限流,有的余额耗尽,有的被上游封禁。垫片需要维护每把 key 的状态:

  • 成功请求:清零失败计数
  • 可重试失败:增加失败计数并设置冷却截止时间
  • 冷却到期后放回候选池,允许再次尝试

不需要主动探测上游,被动记录请求结果就够了。这样不会产生额外流量,也不会在 key 全部失效时反复打上游。

日志应该记什么

垫片是排障的唯一入口,日志要能回答「这次请求用了哪把 key、为什么换、最后成没成」:

  • 请求 ID、时间、路径
  • 每次尝试使用的 key 标识(不要记录完整 key)和结果状态
  • 重试次数与最终结果
  • 上游返回的错误摘要

日志写到本地文件即可,注意不要把请求体和完整 key 落盘。

和聚合站配合的方式

这类垫片和聚合站的定位是互补的:聚合站负责用一把 key 调多个模型,垫片负责在本地用多把 key 做冗余。如果你用的是支持一个 key 调多模型的聚合服务,垫片里每把 key 背后本身就是一组模型,故障转移的粒度就是 key 级别。

把垫片放在本机还有一个好处:工具、脚本、IDE 插件都可以指向同一个本地地址,key 池只需要维护一份,换 key 时不用逐个改配置。

落地检查清单

  • 工具侧只改 base_url 和 key 两项配置
  • 垫片监听 127.0.0.1,不要暴露到公网
  • 明确区分可重试与不可重试错误
  • 流式请求在写出响应头前完成重试决策
  • 每把 key 有冷却状态,避免反复打失效 key
  • 日志记录尝试链路但不落敏感信息

按这个结构做,一个几十行的本地代理就能让原本只认单 key 的 CLI 工具获得故障转移能力,而不需要碰工具的任何代码。