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 结构。developer 消息无论出现在会话的哪个位置,都会并入顶层 system 字段。函数工具的 strict 会复制到 Claude 工具上。旧版 functions、function_call 和 function 角色会映射为工具,参见旧版函数调用。 |
| 工具选择 | 把 none、auto、required 和指定函数分别发送为 Anthropic 的 none、auto、any 和 tool。allowed_tools 或 custom 形式的选择不设置任何选择。其他取值会以点名 tool_choice 的 400 拒绝;缺少 id 或函数名的回放助手工具调用也会这样拒绝。 |
| 响应 | 转换文本和 tool_use 块、停止原因以及 Token 用量,非流式和流式响应均适用。流式响应中,每个 tool_use 块成为一个带独立 index 的流式工具调用,index 只在工具调用之间按到达顺序编号:第一个数据块携带调用的 id 和函数 name,后续数据块携带参数片段。未流式返回任何参数的调用会得到 "{}"。stop_reason: tool_use 转换为 finish_reason: tool_calls,refusal 转换为 content_filter,model_context_window_exceeded 转换为 length,pause_turn 转换为 stop。 |
| 思考 | 将 Anthropic thinking 块返回为 message.reasoning_content(多个块以换行符连接),将流式 thinking_delta 事件返回为 delta.reasoning_content。redacted_thinking 块和签名不会返回。请求历史中的 reasoning_content 字段不会发送给 Anthropic。只携带推理内容的 assistant 轮次(无论推理内容是 reasoning_content,还是 Mistral thinking、Databricks reasoning 等推理内容部件)会被排除在请求之外。 |
| 输出长度限制 | 将 max_completion_tokens 发送为 Anthropic 的 max_tokens;请求同时发送两者时以它为准,reasoning_effort 的思考预算也按它确定。请求两者都未发送时,自动提供 max_tokens: 4096,因为 Anthropic 要求必须设置输出长度限制。max_completion_tokens 不是非负整数或超过 4294967295 时,会以点名该字段的 400 拒绝。 |
| 停止序列 | 将 stop 发送为 stop_sequences,字符串会作为只有一个元素的数组发送。请求自己发送的 stop_sequences 优先于 stop。null 或空数组不设置任何值,其他形态的值会以点名 stop 的 400 拒绝。 |
| 流式选项 | 不会把 stream_options 发往上游。设置 stream_options.include_usage: true 时,流会在结束分片之后以一个仅含用量的分片结束,参见流式响应。 |
| 结构化输出 | 在 Claude 4.5 及更新的模型上,将 response_format 发送为 Anthropic 原生的 output_config.format;在其他模型上则改为一个被强制调用的合成工具。参见结构化输出。 |
| OpenAI 专属字段 | 丢弃 Anthropic 没有对应位置的 OpenAI 字段:seed、n、logprobs、top_logprobs、logit_bias、frequency_penalty、presence_penalty、store、prompt_cache_key、prompt_cache_retention、verbosity、modalities、audio、prediction 和 web_search_options。user 和 safety_identifier 会变成 metadata.user_id:非空的 safety_identifier 优先,否则使用 user,两者都不会以原名到达 Claude。请求自带的 metadata.user_id 优先于两者,metadata 只保留 user_id。service_tier 以及 OpenAI 未定义的字段(例如有意放进 chat 请求体的 Anthropic 字段)按原样转发。 |
| 推理力度 | 将 reasoning_effort 发送为 Anthropic 当前的深度控制字段 output_config.effort。minimal 映射为 Anthropic 的下限档位 low;none 则转为 thinking: {"type": "disabled"},因为 Anthropic 没有 none 这一档。其余情况下 AISIX 不会额外添加 thinking 块:请求要的是深度而不是思考模式,当前的 Anthropic 模型会采用自己的默认模式。不是字符串的 reasoning_effort 会以点名该字段的 400 拒绝。不在上述集合内的字符串取值会整体丢弃:它对应不到任何已知的 Anthropic 档位,而原字段本身也无法代为转发,因为 /v1/messages 会拒绝未知的顶层字段。请求自己携带的 output_config 或 thinking 保持原样,并优先于该转换。拒绝 output_config.effort 的 Claude 系列改为收到思考预算,参见旧版 Claude 模型的推理力度。 |
输出长度限制、停止序列和流式选项的处理适用于所有 Claude 上游:Anthropic 及兼容 Anthropic 的服务提供方、Vertex AI 上的 Claude 以及 Amazon Bedrock 上的 Claude,流式和非流式均适用。OpenAI 专属字段和工具选择的处理,适用于请求以 Anthropic Messages 请求体到达 Claude 的所有情况:Anthropic 及兼容 Anthropic 的服务提供方、Vertex AI 上的 Claude,以及 Amazon Bedrock InvokeModel 上的非流式 Claude 请求。Bedrock Converse 对这些字段的放置方式参见 Converse 请求字段。1.6.x 及更早版本的网关会把 max_completion_tokens、stop 和 stream_options 原样转发给 Anthropic 和 Vertex AI,并被 Anthropic 以 400 拒绝。1.7.0 及更早版本的网关会把 OpenAI 专属字段转发给 Claude(Anthropic 会以 400 拒绝),并静默丢弃无效的 tool_choice 或 reasoning_effort。这一转换背后的规则参见请求参数处理。
当 OpenAI Chat Completions 消息使用带类型的内容部分时,Anthropic 转换会把 image_url 内容和 PDF file 内容作为 Anthropic image 和 document 块发送。https:// URL 会作为 URL 来源交给 Anthropic,http:// URL 则由网关下载后内联发送。Claude 不接受音频输入,因此 input_audio 内容或 Claude 无法接受的其他内容会以 400 拒绝并指明出错的内容,而不是被丢弃。参见媒体输入与 URL 获取。
如需了解完整的工具调用循环,请参阅工具调用。AISIX 还可以为符合条件的 Chat Completions 请求添加 Anthropic 提示词缓存标记;请参阅 Anthropic 提示词缓存。
当应用必须保持 Anthropic 请求和响应格式,特别是使用服务提供方专属内容或 thinking blocks 时,请使用 Anthropic 风格的 /v1/messages 路由。有关原生客户端契约及其兼容性边界,请参阅 Anthropic 风格的 Messages API。
旧版 Claude 模型的推理力度
Claude Haiku 4.5、Claude Sonnet 4.5 以及 4.5 之前的所有 Claude 系列会以 400 拒绝 output_config.effort。对于这些模型,AISIX 改为把力度档位作为扩展思考预算发送,即 thinking: {"type": "enabled", "budget_tokens": N},不再携带 output_config.effort:
reasoning_effort | budget_tokens |
|---|---|
minimal、low | 1024 |
medium | 2048 |
high | 4096 |
xhigh | 8192 |
max | 16384 |
Anthropic 要求 max_tokens 大于思考预算:
- 未携带
max_tokens的请求会被设置为预算加4096,例如high对应max_tokens: 8192。 - 携带
max_tokens的请求保留原值,预算最多降为max_tokens - 1。 max_tokens不超过1024的请求不开启思考,因为 Anthropic 允许的最小预算是1024。
请求自己携带的 thinking 块或 output_config.effort 优先,按原样发送;none 仍然转为 thinking: {"type": "disabled"}。开启思考后,请求受 Anthropic 扩展思考规则约束:Anthropic 会拒绝不等于 1 的 temperature 和强制使用某个工具的 tool_choice,工具调用循环中也必须把助手的 thinking 块原样发回。AISIX 会照常透传 temperature 和 tool_choice,由 Anthropic 给出拒绝。
AISIX 根据上游模型名判断,该转换同样适用于 Anthropic、Google Vertex AI 和 Amazon Bedrock。Claude Opus 4.5、Claude 4.6 及更新的模型,以及无法识别出 Claude 版本的模型名(例如通过 Anthropic 兼容端点接入的其他厂商模型),仍然只收到 output_config.effort。3.7 之前的 Claude 系列没有扩展思考,请求仍会被拒绝。
结构化输出
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 数据块;下游编码器和用量统计看到的都是一次正常的流式响应。如果回复带有推理内容,它会先于正文返回:/v1/chat/completions 上为 delta.reasoning_content,/v1/messages 上为排在文本块之前、signature: "" 的 thinking 块,/v1/responses 上为推理摘要增量,且推理输出项排在消息输出项之前。代价是首字节延迟——客户端等待的是整个生成过程而不是第一个 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原样保留,交给上游去拒绝,而不是被悄悄改坏。