接了好几家模型 API,怎么统一封装才不乱?

2026-07-27

数据截至 2026-07,价格与限额以各官网为准。

多家模型 API 接乱掉,几乎从来不是因为”接口不一样”,而是因为你把「选哪个模型」这件事写进了业务代码。真正管用的封装只做三件事:把模型变成一行配置、只统一各家都有的那部分能力、把重试降级和用量记账收敛到唯一的一个出入口。剩下的差异,老老实实留在外面,别硬抹平。

先说一个很常见的误解:不少人以为”大家都兼容 OpenAI 格式,那我写个 if provider == 'x' 就够了”。前两家的确够,第三家开始就不够了。真正让代码烂掉的不是分支本身,而是这些分支会像藤蔓一样爬进业务层——重试逻辑写在 A 处、降级模型硬编码在 B 处、成本统计又在 C 处各算各的。等到你想换个默认模型,得改七个文件。这篇按”从外往里”的顺序,讲清楚每一层该拦下什么。

第零步:先判断你要不要自己做聚合层

在动手写抽象之前,值得先问一句:这层聚合是不是必须自己写?

市面上已经有现成的聚合服务,OpenRouter 是其中比较典型的一个。它的做法是:用一个 API key、一套 OpenAI 兼容接口,去调上百个模型(覆盖 OpenAI GPT 系列、Anthropic Claude 系列、Google Gemini 系列,以及 Qwen、DeepSeek、Llama、Gemma 等大量开源模型),并且在某个提供商报错时自动切到下一个可用的提供商。官方对这个 fallback 的描述是”对用户透明地发生”,让生产应用更有韧性;同时官方称对失败请求不计费(Zero Completion Insurance),只按成功请求收费。

它在计费上的模式是预充值 credits,没有订阅制。官方明确声明不在模型调用费上加价——原话是”We do not mark up provider pricing”,模型目录里显示的价格就是你付的价格。平台自身的收入来自充值环节的手续费:信用卡/借记卡(Stripe)充值收 5.5%、最低 $0.80;加密货币充值收 5%、无最低限制。另外要留意 credits 的到期规则,FAQ 里保留了”购买满一年未使用可能清零”的权利。

所以判断标准其实挺朴素:

  • 如果你的诉求是”少写点胶水代码、快速试多个模型、账单收在一处”,直接用现成聚合层,省下来的时间远比 5.5% 的充值手续费值钱。
  • 如果你的诉求是”必须直连某几家、要用各家私有能力、对数据链路有合规要求”,那就得自己封装,但也别把范围扩得太大。
  • 现实里最常见的是混合:自己写一层薄封装,但底下允许挂”直连某厂商”和”走聚合层”两种通道。

第一层:把模型变成配置,而不是分支

这一层是收益最大的。做法很简单:业务代码永远只说”我要一个便宜快的”或”我要一个能扛长上下文的”,绝不出现具体模型名。

# providers.yaml —— 唯一允许出现模型名的地方
routes:
  cheap_fast:
    - {channel: aggregator, model: "google/gemini-2.5-pro"}
  long_context:
    - {channel: aggregator, model: "anthropic/claude-opus-4.7"}
    - {channel: aggregator, model: "openai/gpt-5.5"}   # 降级备选
  offline_batch:
    - {channel: direct_cn, model: "<各厂商官方文档里的模型名>"}

这里有个细节值得单独说:走聚合层时,模型名是带命名空间前缀的,形如 openai/xxxanthropic/xxxgoogle/xxx;直连各厂商时用的是各家自己的模型 ID,两套命名不通用。所以配置里必须同时记 channelmodel,只记模型名迟早会张冠李戴。

业务侧的调用就变成这样:

answer = llm.ask("long_context", messages)

换模型时改 YAML,不动一行业务代码。这条纪律听起来朴素,但它是后面几层能成立的前提——只有模型名收敛到一处,重试、降级、记账才有可能统一。

第二层:只统一”最小公共面”,别硬抹平差异

新手做封装最容易犯的错,是想设计一个能表达所有厂商全部能力的万能接口。结果就是参数表越滚越大,最后没人敢改。

务实的做法是划一条线:只有各家都支持、语义也一致的东西,才进统一接口。典型就这几样——多轮 messages(role/content)、温度之类的基础采样参数、流式开关、超时。

各家私有的能力(推理档位、缓存控制、特定的结构化输出格式、工具调用的细节差异),别塞进统一签名,用一个透传字段兜住:

class LLMClient:
    def ask(self, route: str, messages: list[dict],
            *, stream: bool = False, timeout: float = 60,
            extra: dict | None = None) -> str:
        """extra 原样透传给底层 SDK,不做任何语义翻译。"""

extra 这个口子的意义在于:它把”我用了厂商私有能力”这件事变得显式可见。将来 grep 一下就知道哪些调用点是有绑定的,迁移时心里有数。反过来,如果你把私有参数硬翻译成统一参数,表面上干净了,实际上是把绑定藏起来了,那才叫乱。

调用侧如果走 OpenAI 兼容的聚合接口,底层其实很薄——把官方 OpenAI SDK 的 base_url 指过去就行:

from openai import OpenAI

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key="<OPENROUTER_API_KEY>",
)

对应的 Chat Completions 端点是 https://openrouter.ai/api/v1/chat/completions。另外它支持两个可选请求头 HTTP-Referer(站点 URL)和 X-Title(站点名称),官方文档都标注为 Optional,用于站点排行榜统计,不传不影响调用——这种”可选但有副作用”的头,建议在封装层做成一个开关,别让每个调用点各写各的。

第三层:重试、降级、超时,只准存在于一个地方

这是最容易散落的一层,也是最该锁死的一层。规则很简单:业务代码里不允许出现 try/except 包着 LLM 调用再手动换模型的写法。所有失败处理都在封装层。

封装层需要区分三类失败,处理方式完全不同:

  • 网络层失败(连不上、超时):可以原地重试,但要退避,别 3 次连打;重试上限建议做成配置,不同路由不一样。
  • 限流(HTTP 429):不能立刻重试,按响应里的等待信息退避;如果这条路由配了降级链,直接换下一个候选更划算。
  • 参数/模型错误(模型名不存在、参数不被支持):重试毫无意义,必须立刻抛出来。把这类错误也吞进重试逻辑,是线上排查最痛苦的场景之一——你会看到一个请求慢了三倍,然后报一个跟根因无关的超时。

如果底层走的是自带 fallback 的聚合服务,这一层可以写得更薄:提供商级别的故障转移由平台处理,你只需要保留”聚合层整体不可用时切直连通道”这一级。但要注意别做双重重试——上下两层各重试 3 次,实际就是 9 次,账单和延迟都会失控。

第四层:用量记账要在封装层做,不要事后对账单

多家 API 混用时,“这个月钱花哪了”是个真问题。等到看平台账单再倒推,基本查不出是哪个业务模块。

可行的做法是在封装层的唯一出口处,把每次调用的 route、channel、model、输入输出 token 数、耗时、是否降级过,落成一条结构化日志。有了这条日志,成本归因就是一次 group by 的事。

单价这块提醒一点:各模型价格差得很远,而且会随提供商调整变动。从聚合平台官方模型页核实到的三条当前价格(每百万 token,2026-07)可以感受一下量级:

模型输入价输出价上下文窗口
OpenAI GPT-5.5$5$301M(922K 输入 + 128K 输出)
Anthropic Claude Opus 4.7$5$251M
Google Gemini 2.5 Pro$1.25$101M

其余型号的价格以官方模型列表实时页面为准,别把任何单价硬编码进代码里——写成配置,并且在日志里记下”当时用的是哪条价格配置”,否则历史成本没法复算。更细的价格拆解可以看这篇

顺带说一下 BYOK(自带各厂商 key 走聚合层转发)这条路:它的费用规则在官方两处页面上口径不完全一致,一处按请求次数表述、一处按金额表述,我没能核实两者的关系,具体以官网当前页面为准。如果你打算靠 BYOK 省钱,务必先自己去官网确认当期条款,别照抄任何二手教程里的数字。

第五层:免费额度只当测试用,别混进生产路由

聚合平台通常有免费模型档。以 OpenRouter 为例,存在专门的免费模型路由 openrouter/free,会在可用的免费模型里做筛选分配,官方页面显示约 26 个免费变体(多来自 Qwen、DeepSeek、Llama、Gemma 等开源模型)。限速是明确的:没有充值过的账户每天 50 次、每分钟 20 次;历史充值达到 $10 的账户提升到每天 1000 次,但 20 RPM 的限制仍在。

这个额度用来跑本地 demo、写单元测试的 mock 数据、临时验证一下 prompt 改动,完全够;但它不适合放进生产路由。原因不是”不够用”这么简单——免费档下面具体挂哪个模型是会变的,你的 prompt 在某次调用里表现正常,下一次可能落到另一个模型上,输出风格和结构都不一样。生产系统需要的是可复现,不是省那点钱。

封装层可以做的事情是:把免费路由做成一个显式的 route: "sandbox",并在配置里禁止生产环境加载它。这样谁也没法”临时试一下忘了改回来”。

网络可达性:这条得在架构阶段就想清楚

有一条现实必须诚实说明:OpenRouter 是境外(美国)托管的服务,它的官网和 API 在中国大陆网络环境下普遍需要网络代理才能稳定访问。这不是它独有的问题,它底层依赖的 OpenAI、Anthropic、Google 等官方服务同样不面向中国大陆开放直连。

市面上确实存在第三方中转、镜像类服务声称可以国内直连,但这类渠道的合规性、稳定性和数据安全性无法从官方信息核实,变动也快,这里不推荐、不背书任何具体渠道。如果你的项目必须在大陆稳定运行,比较务实的方向是:评估企业出海的合规网络方案,或者直接把国内可合规触达的厂商模型作为主通道、海外模型只做可选增强——这也正好是前面”channel”字段存在的意义。相关的通道选择差异可以参考API 聚合与中转平台对比国产 API 价格对比

什么时候不该自己封装

说了这么多做法,也得说不适用的场景:

  • 只接一家、且短期不打算换:直接用官方 SDK,封装纯属自找麻烦。抽象的收益来自”换”,不换就没收益。
  • 强依赖某家私有能力:比如你整个产品的核心就建立在某个厂商特有的机制上,硬做统一抽象只会让那部分能力被削平,得不偿失。
  • 只是做实验和调研:Notebook 里怎么快怎么来,别提前架构。等到确实有第二个模型要长期共存了再动手也不迟。

还有一个反模式值得提醒:不要在封装层里做”自动选最便宜模型”这类聪明逻辑。看起来省钱,实际上让线上行为变得不可预测,出问题时你连”当时用的是哪个模型”都要靠翻日志。选型这件事应该是显式的、写在配置里的。

小结

多家模型 API 的封装,核心不是抹平接口差异,而是把”变化”关进笼子。第一件事是让模型名只出现在配置文件里,业务代码只说意图;第二件事是只统一各家都有的最小公共面,私有能力用透传字段显式保留;第三件事是把重试、降级、超时、用量记账全部收敛到唯一出口,并且注意别和聚合平台自带的 fallback 形成双重重试。免费额度留给测试,生产路由要可复现。最后,海外模型的网络可达性是架构问题不是运维问题,得在选型阶段就摆到桌面上,而不是等上线前一天才发现连不通。

接下来看什么

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。