起因:一个月里,我把默认模型换了三次
第一次是因为新模型代码能力更强,第二次是旧模型开始限流,第三次是价格结构变了想换一档。每次换,代价都不一样:
- 在 opencode 里改配置文件;
- 在 Cursor 里重新填一遍自定义服务商;
- 在 Claude Code 里改环境变量,顺便确认工具调用没被悄悄关掉;
- 自己写的那两个小脚本改 base_url,再跑一遍冒烟测试。
第三次改完我决定停下来:这些改动本来应该是"改一个配置项",而不是"改一堆散落的地方"。 于是把模型访问收拢到一层网关,下面记的是这次改造的过程,以及我踩到的五个坑。
一、为什么没选择直连三家
直连当然最干净:少一跳、少一个依赖、出问题直接找厂商。但我这边有三个现实约束:
- 协议不止一种。我的工具链里有的说 OpenAI 那套,有的只认 Anthropic 的 /v1/messages(Claude Code),还有的走 /v1/responses(Codex)。如果直连,我得为每种协议各接一次。
- 密钥和额度会散。三家三把 Key,散在三台机器和两个 CI 里,谁在用、还剩多少,说不清。
- 对账只看总数没用。我要能回答"这笔钱花在哪个模型上",不然预算没法调。
所以我要的不是"更便宜的模型",而是一个统一的入口:一把 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 之后好很多。
五、排障:三个我常用的抓手
- 错误体是 OpenAI 那一套,{"error":{"message":…,"type":…,"code":…}}。原来处理 OpenAI 报错的代码不用改,新加一个 code 分支就够。
- 每个响应都带 x-request-id(req_…)。反馈问题时把这个 ID 一起给,定位快得多。
- 模型目录不是公开接口:拉模型清单的接口不带 Key 会被直接拒掉(错误码 unauthorized)。我是想写个脚本核对模型 ID 时撞上的 —— 顺手说,它返回里就带 MA 单价,做成本预估可以直接读。
- 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