跳到主要内容
版本:1.4.0

OpenAI 客户端接入 Anthropic 上游

AISIX 允许应用继续使用 OpenAI Chat Completions 请求格式,同时由网关调用 Anthropic 上游模型。当应用代码已经围绕兼容 OpenAI 的 SDK 构建,但平台团队希望把这类流量路由到 Claude 时,可以使用这一模式。

AISIX 会解析模型别名,将请求转换为 Anthropic Messages 格式,使用已保存的服务提供方凭证调用 Anthropic,并把响应转换回兼容 OpenAI 的 Chat Completions 格式。

准备工作​

请先准备以下内容:

  • 一个正在运行且应用可以访问的 AISIX 网关。
  • 一个由 Anthropic 支持,且接受兼容 OpenAI 的 Chat Completions 请求的模型别名。
  • 一个有权使用该模型别名的调用方 API Key。
  • 如需运行 SDK 示例,请准备 Node.js 20 LTS 或更新版本以及 npm;如需运行 HTTP 示例,请准备 curl。

如果尚未配置模型别名和调用方 API Key,请按照 Anthropic 文档为 AISIX Cloud 或开源 AISIX 网关完成配置。

请求流程​

应用继续保持兼容 OpenAI 的客户端契约。服务提供方选择和协议转换都留在网关中完成。

应用将模型别名和调用方 API Key 发送到 AISIX。网关会解析上游模型、提供已保存的 Anthropic 凭证,并转换请求和响应。应用仍然发送和接收兼容 OpenAI 的数据。

调用模型别名​

导出两个请求示例都会用到的调用方 API Key 和模型别名:

export AISIX_API_KEY="YOUR_CALLER_API_KEY"
export AISIX_MODEL="claude-sonnet-prod"

OpenAI SDK​

安装 OpenAI SDK:

npm install openai

设置兼容 OpenAI 的基础 URL。OpenAI SDK 要求 URL 中包含 /v1 路径:

# 本地快速入门使用 http://127.0.0.1:3000/v1
export AISIX_BASE_URL="YOUR_AISIX_GATEWAY_URL/v1"

创建一个最小化 Chat Completions 客户端:

anthropic-via-openai-sdk.mjs
import OpenAI from "openai";

const client = new OpenAI({
apiKey: process.env.AISIX_API_KEY,
baseURL: process.env.AISIX_BASE_URL,
});

const completion = await client.chat.completions.create({
model: process.env.AISIX_MODEL,
messages: [{ role: "user", content: "Say hello from AISIX." }],
});

console.log(completion.choices[0]?.message.content);
console.log(completion.usage);

在已设置 AISIX 相关值的 Shell 中运行示例:

node anthropic-via-openai-sdk.mjs

HTTP​

如需在不使用 SDK 的情况下查看响应,请导出网关源站地址,并使用 curl 发送相同的请求:

# AISIX_PROXY 不含尾部斜杠或端点路径
# 本地快速入门使用 http://127.0.0.1:3000
export AISIX_PROXY="YOUR_AISIX_GATEWAY_URL"

发送请求:

curl -sS -X POST "$AISIX_PROXY/v1/chat/completions" \
-H "Authorization: Bearer $AISIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "'"$AISIX_MODEL"'",
"messages": [{"role":"user","content":"Say hello from AISIX."}]
}'

两个示例都会返回兼容 OpenAI 的 Chat Completions 结构。调用方不会收到 Anthropic 风格的内容块:

{
"object": "chat.completion",
"model": "claude-sonnet-prod",
"choices": [
{
"message": {
"role": "assistant",
"content": "Hello from AISIX."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 5,
"total_tokens": 14
}
}

转换行为​

协议转换会保留常见聊天应用所依赖的兼容 OpenAI 契约:

行为AISIX 的处理方式
模型与身份验证将模型别名解析到已配置的 Anthropic 模型,对调用方进行身份验证,并使用已保存的服务提供方凭证发送上游请求。
消息与工具将开头的系统消息、用户和助手消息、函数工具、工具调用及工具结果映射为 Anthropic Messages 结构。
响应将 Anthropic 文本和工具使用内容块、停止原因、Token 用量及流式事件转换回兼容 OpenAI 的字段。
输出长度限制当兼容 OpenAI 的请求未指定输出长度限制时,自动提供 max_tokens: 4096,因为 Anthropic 要求必须设置这一字段。
结构化输出在 Claude 4.5 及更新的模型上,将 response_format 发送为 Anthropic 原生的 output_config.format;在其他模型上则改为一个被强制调用的合成工具。参见结构化输出。
推理力度将 reasoning_effort 发送为 Anthropic 当前的深度控制字段 output_config.effort。minimal 映射为 Anthropic 的下限档位 low;none 则转为 thinking: {"type": "disabled"},因为 Anthropic 没有 none 这一档。其余情况下 AISIX 不会额外添加 thinking 块:请求要的是深度而不是思考模式,当前的 Anthropic 模型会采用自己的默认模式。取值不在上述集合内时该设置整体丢弃:它对应不到任何已知的 Anthropic 档位,而原字段本身也无法代为转发,因为 /v1/messages 会拒绝未知的顶层字段。请求自己携带的 output_config 或 thinking 保持原样,并优先于该转换。
警告

当 OpenAI Chat Completions 消息使用带类型的内容部分时,Anthropic 转换会保留文本部分,但会丢弃图片和音频等非文本部分。如果必须将图片或文档内容发送到 Anthropic 上游,请使用 Anthropic 风格的 /v1/messages 路由。

如需了解完整的工具调用循环,请参阅工具调用。AISIX 还可以为符合条件的 Chat Completions 请求添加 Anthropic Prompt Cache 标记;请参阅 Anthropic Prompt Caching。

当应用必须保持 Anthropic 请求和响应格式,特别是使用服务提供方专属内容或 thinking blocks 时,请使用 Anthropic 风格的 /v1/messages 路由。有关原生客户端契约及其兼容性边界,请参阅 Anthropic 风格的 Messages API。

结构化输出​

Chat Completions 请求中的 response_format 会传达给模型。Responses 桥接由 text.format 生成的 response_format,以及 Anthropic Messages 桥接由 output_config.format 生成的 response_format,都走同一套转换。

上送到 Anthropic 上游的具体形状取决于目标模型:

  • Claude 4.5 及更新的模型使用 Anthropic 自己的控制字段:schema 写入 output_config.format,并与该载体中已有的内容(例如由 reasoning_effort 转换而来的 effort)合并。请求原生携带的 format 是对同一设置更具体的表述,优先保留。不发送任何 beta 请求头,当前 API 无需它即可接受该字段。
  • 其余所有模型——更早的 Claude 系列,以及通过 Anthropic 兼容端点提供的非 Claude 模型——走下文的合成工具路径,该路径只要求模型支持工具调用。

AISIX 没有模型能力表,因此从上游模型名读取系列版本。Anthropic 的两种命名顺序都能识别(claude-3-5-haiku-... 和 claude-sonnet-4-5-...),并且把结尾的八位发布日期当作日期而不是次版本号:claude-sonnet-4-20250514 是 4.0,而 claude-sonnet-4-5-20250929 是 4.5。claude-sonnet-4-5@20250929 这种写法同样可以识别。

{"type": "json_object"} 和 {"type": "text"} 都没有指定 schema,而 Anthropic 在两条路径上的 JSON 控制都以 schema 为准,因此二者都不会在 Anthropic 请求体上写入任何内容。response_format 本身也从不进入该请求体:/v1/messages 会拒绝未知的顶层字段。

合成工具路径​

当模型没有原生控制字段时,schema 会搭载在一个名为 json_tool_call 的合成工具上,该工具的入参本身就是答案。它被追加到调用方发送的工具列表之后,并通过 tool_choice 强制调用——正是这一步让回复成为 JSON,而不是模型可以忽略的建议。有两种情况优先于这一强制,此时合成工具仍然提供给模型,由模型按自己的 auto 决定是否调用:

  • 请求自己声明了 tool_choice——任何取值都算,包括 auto。auto 并不是没有意图:它表示由模型自行决定,运行 Agent 循环的客户端每一轮都会连同自己的工具一起发送它;如果在这种情况下强制调用合成工具,只要设置了 response_format,客户端自己的工具就永远无法被调用。显式的 JSON null 视为未声明,因为这是 SDK 表达"该可选字段缺省"的写法。
  • 开启了扩展思考(extended thinking),Anthropic 会直接拒绝与强制工具选择同时出现的请求。

回复会在返回调用方之前被翻译回去。当合成工具调用是唯一的调用时,其入参会替换消息内容,不带 tool_calls,finish_reason 为 stop——不能告诉一个从未提供过工具的客户端,模型是为了调用工具才停止的。当模型同时调用了调用方真正提供的工具时,调用方本来就要求了工具调用并会自行解析,因此这些调用及其结束原因都保留下来,JSON 则追加到随之返回的文本之后。

这条路径上的流式请求以模拟流式的方式返回。工具调用在完成之前无法流式传输,因此 AISIX 会发起一次非流式的上游请求,再把结果渲染成常规的 role、content、finish 和 usage 数据块;下游编码器和用量统计看到的都是一次正常的流式响应。代价是首字节延迟——客户端等待的是整个生成过程而不是第一个 Token——并且只影响在没有原生控制的模型上要求 schema 的请求。这一段按模型的端到端 timeout 计时,而不是 stream_timeout,因此比请求超时更短的分块间隔超时不会把它掐断。

AISIX 如何调整 schema​

Anthropic 会把 schema 编译成解码语法,并对其文档所列子集之外的任何关键字返回错误,因此原样转发会让一个原本在 OpenAI 上游可用的 schema 直接请求失败。AISIX 在两条路径上都会先调整它:

  • 密封而非闭合。 schema 中的每个对象都会加上 Anthropic 要求的 additionalProperties: false,而 required 保持调用方原本的写法:Anthropic 把它当作普通的 JSON Schema 关键字,未列入其中的属性仍然是可选的,只是在输出中排在必填属性之后。像 OpenAI 严格模式那样把所有已声明属性提升为必填,会让可选字段只在这一个服务提供方上变成必填。
  • 收窄到受支持的子集。 minimum、maximum、exclusiveMinimum、exclusiveMaximum、multipleOf、minLength、maxLength、maxItems、uniqueItems,以及取值不为 0 或 1 的 minItems 会被移除,并改写进该属性的 description——例如 "full name (maxLength: 20)"——这样即使解码器不再强制该约束,它仍然以一句话的形式传达给模型。
  • oneOf 改写为 anyOf。Anthropic 只记载了 anyOf 而没有 oneOf,而就约束输出而言,"恰好满足其一"与"至少满足其一"的差别并不起作用,因为模型生成的文档必然匹配它所选择的那个分支;直接丢弃该关键字则会连同各个分支一起丢掉。
  • 内部 $ref、$defs 和 definitions 是 Anthropic 支持的,予以保留。递归或外部 $ref 原样保留,交给上游去拒绝,而不是被悄悄改坏。

Token 用量​

Anthropic 与 OpenAI 对 Prompt Cache Token 的计数方式不同,因此 AISIX 会转换这些计数,而不是原样透传。Anthropic 的 input_tokens 表示未命中缓存的输入,cache_creation_input_tokens 和 cache_read_input_tokens 是与之并列的独立计数器。OpenAI 的口径只有一个 prompt_tokens,它已经包含缓存命中的部分,并通过 prompt_tokens_details.cached_tokens 标明。OpenAI 完全没有缓存写入这一概念,因此 AISIX 把写入也折进 prompt_tokens(它属于计费输入),并与命中并列单独报告。

上游返回以下用量时:

{
"usage": {
"input_tokens": 40,
"output_tokens": 10,
"cache_creation_input_tokens": 30,
"cache_read_input_tokens": 70
}
}

兼容 OpenAI 的调用方会收到:

{
"usage": {
"prompt_tokens": 140,
"completion_tokens": 10,
"total_tokens": 150,
"prompt_tokens_details": {
"cached_tokens": 70,
"cache_creation_tokens": 30
}
}
}

调用方可以依赖以下规则:

  • prompt_tokens 是模型读取的完整输入,包含缓存读取和缓存写入。
  • total_tokens 等于 prompt_tokens + completion_tokens。
  • cached_tokens 是 prompt_tokens 的子集,只统计缓存读取。
  • cache_creation_tokens 是缓存写入,同样是 prompt_tokens 的子集。它属于计费输入但不是缓存命中,因此单独报告而不计入 cached_tokens。OpenAI 没有缓存写入这一概念,所以该字段仅在上游报告了写入时出现——这一点很重要,因为服务提供方对写入的计费通常高于普通输入。在缓存会话的第一轮(只写不读)中,它是唯一能表明缓存参与了本次请求的信号。

流式响应,以及经由 Anthropic 上游的 Responses API,都适用同样的转换。

日志、指标和消费统计不做转换:它们保留 Anthropic 自身的计数器,因此同一次调用无论用哪种协议发起,成本都相同。记录侧的口径请参阅 Anthropic Prompt Caching。

下一步​

你已经将兼容 OpenAI 的客户端路由到 Anthropic 上游。请参阅兼容 OpenAI 的 API了解面向调用方的路由行为。当你希望端到端使用 Anthropic 请求和响应结构时,请参阅 Anthropic Messages;如需了解端点与服务提供方支持边界,请查看服务提供方兼容性。