给只认一个 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 重放,否则工具会收到重复内容。正确做法是在收到上游响应头之前完成重试判断,把流式转发推迟到确定这次请求成功之后再开始。
重试策略
一个够用的策略:
- 按优先级顺序取 key(或轮询)
- 请求失败且错误可重试时,标记该 key 短暂冷却,取下一把
- 总尝试次数设上限,避免无限循环
- 全部 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 工具获得故障转移能力,而不需要碰工具的任何代码。