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 客户端:
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,客户端自己的工具就永远无法被调用。显式的 JSONnull视为未声明,因为这是 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;如需了解端点与服务提供方支持边界,请查看服务提供方兼容性。