一次接入层改造:把三套客户端收拢到一把 Key(opencode / Cursor / Claude Code)

0 阅读6分钟

起因:一个月里,我把默认模型换了三次

第一次是因为新模型代码能力更强,第二次是旧模型开始限流,第三次是价格结构变了想换一档。每次换,代价都不一样:

  • 在 opencode 里改配置文件;
  • 在 Cursor 里重新填一遍自定义服务商;
  • 在 Claude Code 里改环境变量,顺便确认工具调用没被悄悄关掉;
  • 自己写的那两个小脚本改 base_url,再跑一遍冒烟测试。

第三次改完我决定停下来:这些改动本来应该是"改一个配置项",而不是"改一堆散落的地方"。 于是把模型访问收拢到一层网关,下面记的是这次改造的过程,以及我踩到的五个坑。

一、为什么没选择直连三家

直连当然最干净:少一跳、少一个依赖、出问题直接找厂商。但我这边有三个现实约束:

  1. 协议不止一种。我的工具链里有的说 OpenAI 那套,有的只认 Anthropic 的 /v1/messages(Claude Code),还有的走 /v1/responses(Codex)。如果直连,我得为每种协议各接一次。
  2. 密钥和额度会散。三家三把 Key,散在三台机器和两个 CI 里,谁在用、还剩多少,说不清。
  3. 对账只看总数没用。我要能回答"这笔钱花在哪个模型上",不然预算没法调。

所以我要的不是"更便宜的模型",而是一个统一的入口:一把 Key、一个地址、一套错误处理。

二、改造前确认的四件事

在动手之前我先确认了这层网关本身靠不靠谱(充值方式与到账时间以充值页说明为准),这四条也是我判断同类服务的标准:

  • 地址和协议:macdecloud.com/v1,OpenAI 兼容为主,另外原生提供 /v1/messages 和 /v1/responses;
  • 模型来源是否标注:目录里既有直连厂商、也有经聚合目录接入的模型,每个模型在模型页标来源,能核对——这条比"便宜多少"重要;
  • 计费口径:按 token,输入输出分别计价;用量明细在控制台用量页,能逐笔核;
  • 失败怎么算:上游出错导致的失败不扣余额,余额为 0 时直接返回 402,不会静默失败。

三、动手:三套客户端的实际配置

opencode(项目根目录或全局 opencode.json,项目配置优先):

``json { "$schema": "<opencode 的 schema 地址>", "provider": { "decloud": { "npm": "@ai-sdk/openai-compatible", "name": "deCloud", "options": { "baseURL": "macdecloud.com/v1", "apiKey": "{env:DECLOUD_API_KEY}" }, "models": { "moonshotai/kimi-k3": { "name": "Kimi K3" } } } } } `

Cursor / Chatbox 这类图形客户端:新建一个「自定义服务商(OpenAI 兼容)」,填四样——地址 macdecloud.com/v1、Key、模型完整 ID、协议选 OpenAI 兼容。不要改内置的那个 OpenAI 项,它的地址是写死的。

Claude Code:走 Anthropic 协议,地址不带 /v1:

bash export ANTHROPIC_BASE_URL="https://macdecloud.com" export ANTHROPIC_AUTH_TOKEN="dcld-sk-..." export ANTHROPIC_MODEL="deepseek/deepseek-v4-pro"

Codex:用 Responses 协议,配置写在 config.toml 的 provider 段里,和上面那套环境变量不是一回事。

opencode / Cursor / Chatbox / Claude Code / Codex 五套的完整配置,官方文档里逐篇写了(macdecloud.com/docs/clients),我基本是照着抄的。

四、五个坑

坑 1:模型卡上没有 tools 的,别拿去跑 Agent。 模型的能力是分标签的。anthracite-org/magnum-v4-72b、cognitivecomputations/dolphin-… 这类角色扮演模型没有工具调用能力,你让 Agent 调它,工具那一步直接失败。选型时先看模型卡上的能力标签,别只看名字。

坑 2:模型名要照抄完整 ID。 写成简称会拿到 404 model_not_found。带厂商前缀的完整 ID(例如 deepseek/deepseek-v4-pro、z-ai/glm-5.2)才认。

坑 3:浏览器里别想直接调。 我图省事在前端 fetch 试了一次,预检就被挡:对 /v1/chat/completions 发 OPTIONS 返回 405(allow: POST),也没有 Access-Control-Allow-Origin。必须走后端转发(本来也该这样,Key 不该出现在浏览器里)。

坑 4:流式返回里,用量在最后一块。 流式走标准 SSE,最后一块才带 usage。如果你按块累加 token 自己估算,账会和实际有偏差——我一开始就是这么算的,后来改成读最后一块的 usage。

坑 5:撞 429 要按 Retry-After 退避,别硬重试。 限流返回 429 rate_limit_exceeded,响应头里有 Retry-After。我最早的写法是固定 sleep 1 秒重试,限流反而更久;改成读 Retry-After 之后好很多。

五、排障:三个我常用的抓手

  1. 错误体是 OpenAI 那一套,{"error":{"message":…,"type":…,"code":…}}。原来处理 OpenAI 报错的代码不用改,新加一个 code 分支就够。
  2. 每个响应都带 x-request-id(req_…)。反馈问题时把这个 ID 一起给,定位快得多。
  3. 模型目录不是公开接口:拉模型清单的接口不带 Key 会被直接拒掉(错误码 unauthorized)。我是想写个脚本核对模型 ID 时撞上的 —— 顺手说,它返回里就带 MA 单价,做成本预估可以直接读。
  4. 402 和 403 不是一回事:402 是账户余额为 0;403 常见的是这把 Key 的问题——没绑定该模型(model_not_allowed),或者这把 Key 自己的额度用完了(quota_exhausted)。我一开始把 403 当成"账号被封",白查了半天。

六、账单这件事

  • 计费按 token,输入输出分别计价;
  • 用量明细可以逐笔核,不用靠估算;
  • 失败不计费:上游出错导致的失败不扣余额;
  • 边界说清楚:这层网关负责转发与记账,不改变模型本身的能力——模型擅长什么、上下文多长,还是看模型自己的规格。

七、什么时候我不建议加这一层

  • 只需要一家的旗舰模型:直连更省事,少一跳少一个依赖;
  • 需要厂商独有的能力(私有参数、微调、专属工具链):中间隔一层反而受限;
  • 有强合规/私有化要求:该直接与厂商签协议,别走第三方;
  • 延迟极度敏感:多一跳就多一份延迟,极限场景自己压测再决定。

这次改造真正的收益不是"多接了几个模型",而是把"换模型"从一次小重构变成改一个配置项:

  • 三套客户端共用一把 Key、一个地址;协议差异由网关兜住;
  • 排错有秩序:401 看 Key、402 看余额、403 看模型绑定与 Key 额度、404 看模型名、429 按 Retry-After 退避;
  • 选这类服务的两个硬指标:模型来源是否逐个标注、用量是否能逐笔核。

我用的是 deCloud,入口在 macdecloud.com, 文档在 macdecloud.com/docs 客户端配置在 macdecloud.com/docs/clients