sdk-anthropic-claude-client-shim · ZH · 2026-10-14

Anthropic SDK 能直接指向聚合站吗,还是必须加一层适配

Anthropic SDK 可以指向聚合站,前提是把 base_url 指向聚合站的 Anthropic 原生兼容端点;但大多数聚合站只提供 OpenAI 兼容接口,这时就需要一层薄适配。本文对比直调与适配两条路线的取舍,并给出用 Anthropic 客户端配合 OpenAI 兼容端点时的适配层写法,最后说明计费(用户 ×1.3、贡献 key 返 ×1.1/×1.2)在两条路线下如何体现。

先弄清“能不能直连”的判定条件

Anthropic SDK(anthropic Python / @anthropic-ai/sdk Node)在构造客户端时通常支持两个关键参数:apikey 和 baseurl(Python 里是 baseurl,Node 里是 baseURL)。只要聚合站暴露的是 Anthropic Messages API 的原生兼容端点,把 baseurl 指过去、api_key 换成聚合站签发的 key,就可以直接调用,不需要任何包装代码。

判断方法只有一条:聚合站的文档里有没有告诉你能把 base URL 换成它,并且返回体结构与 Anthropic 官方一致(content 数组、stopreason、usage.inputtokens 等字段位置相同)。有,就是直连;没有,就得适配。

两条路线对比

路线 A:Anthropic SDK + 原生兼容端点

  • 优点:请求/响应结构、流式事件类型、工具调用(tool use)格式都按 Anthropic 规范走,SDK 自带的类型提示、重试、超时逻辑全部可用。
  • 代价:这条路只覆盖 Claude 系列,想在同一套代码里调别的模型,需要再建一个客户端。
  • 适用:已经深度使用 Anthropic SDK 的类型和工具调用,且短中期只关心 Claude 的队伍。

路线 B:OpenAI 兼容端点 + 适配层

多数聚合站的主入口是 OpenAI 兼容的 /v1/chat/completions,用一个 key 调多个模型(Claude、GPT、DeepSeek、Qwen、GLM、Kimi 等)。

  • 优点:一个 base URL、一个 key、一套鉴权跑通所有模型;换模型只是改 model 字段。
  • 代价:OpenAI 与 Anthropic 两套消息格式不同,Anthropic 客户端不能直接对着它发请求,需要一层转换。
  • 适用:想用同一套调用栈横向切换模型、做对比评测或多模型兜底的团队。

关键差异集中在三处:

  1. 请求体:Anthropic 用 system 顶层字段 + messages 里 content 为块数组;OpenAI 把 system 放进 messages,content 通常是字符串。
  2. 响应体:Anthropic 返回 content: [{type: "text", text: ...}];OpenAI 返回 choices[0].message.content。
  3. 流式:Anthropic 是 contentblockdelta 等事件;OpenAI 是 choices[0].delta.content 增量。

薄适配层怎么写

如果你有一个只依赖 Anthropic 接口风格的上层代码,又想让底层跑 OpenAI 兼容端点,最稳妥的做法是适配在边界处,不要改上层。思路是写一个“看起来像 Anthropic 客户端”的包装:

class AnthropicShim:
    def __init__(self, openai_client, model):
        self._oai = openai_client
        self._model = model
        self.messages = self  # 让 client.messages.create 可用

    def create(self, *, model=None, system=None, messages=None,
               max_tokens=1024, stream=False, **kwargs):
        oai_messages = []
        if system:
            oai_messages.append({"role": "system", "content": system})
        for m in messages or []:
            oai_messages.append({
                "role": m["role"],
                "content": self._flatten(m["content"]),
            })
        resp = self._oai.chat.completions.create(
            model=model or self._model,
            messages=oai_messages,
            max_tokens=max_tokens,
            stream=stream,
            **kwargs,
        )
        return self._to_anthropic(resp, stream)

    @staticmethod
    def _flatten(content):
        if isinstance(content, str):
            return content
        return "".join(b.get("text", "") for b in content if isinstance(b, dict))

要点:

  • 只转换结构,不做业务判断。system 提升、content 拍平、字段改名,都属于机械映射;任何“模型能力差异”的补偿都放到上层。
  • 流式单独处理。非流式把 choices[0].message.content 包成 content 数组即可;流式要把 delta.content 逐个转成 contentblockdelta 风格的事件,供上层已有的回调消费。
  • 保留 usage 映射。OpenAI 的 usage.prompttokens / completiontokens 映射到 Anthropic 的 inputtokens / outputtokens,方便你沿用已有的用量统计。
  • 未知字段透传。**kwargs 原样传给底层客户端,避免你在适配层把 temperature、top_p 这类参数吞掉。

如果上层还用到了工具调用,适配层就要把 OpenAI 的 tools/toolcalls 和 Anthropic 的 tools/tooluse 块互转。这部分映射比文本复杂,建议单独成模块并配单元测试,别混在消息转换里。

选型建议

  • 只想调 Claude、且不需要多模型:优先找聚合站的 Anthropic 原生端点,直连最省事。
  • 需要在多个模型间切换:接受 OpenAI 兼容路线,把适配层当作一次性投入。适配层写好后,换模型只改一个字符串。
  • 两条路都保留:适配层不必删掉 Anthropic 直连路径。你可以让上层依赖一个抽象接口,运行时决定注入原生客户端还是适配器。

计费在哪一层发生

无论直连还是适配,计费都发生在聚合站侧,与你的客户端写法无关。理解这一点对成本估算有用:

  • 你是使用者:按官方价 ×1.3 扣费。适配层不会改变计费口径,它只转格式。
  • 你是贡献者:贡献 key 按官方价 ×1.1 返还,质量高的 key 按 ×1.2 返还,以 USDC 结算。

也就是说,直连与适配的差别只在工程复杂度和可调模型范围,不在单价。做成本对比时,先把两边都按同一套计价口径(官方价 ×1.3)折算,再决定要不要为了多模型能力加那层适配。

落地检查清单

  • 确认聚合站是否提供 Anthropic 原生兼容端点,以及 base URL 的准确写法(以官方文档为准)。
  • 若走 OpenAI 兼容路线,先写一个最小适配器,跑通非流式文本;再加流式;最后加工具调用。
  • 为适配器补三类测试:system 提示、多轮消息、usage 字段映射。
  • 保留 usage 日志,方便对照账单核对扣费口径。
  • 不要把 key 写死在代码里;适配层和原生客户端都应从环境变量读取。

一句话总结:能直连就直连,不能直连就加一层只做结构转换的薄适配;计费按官方价 ×1.3 走,与走哪条路线无关。