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 客户端不能直接对着它发请求,需要一层转换。
- 适用:想用同一套调用栈横向切换模型、做对比评测或多模型兜底的团队。
关键差异集中在三处:
- 请求体:Anthropic 用
system顶层字段 +messages里content为块数组;OpenAI 把 system 放进 messages,content通常是字符串。 - 响应体:Anthropic 返回
content: [{type: "text", text: ...}];OpenAI 返回choices[0].message.content。 - 流式: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 走,与走哪条路线无关。