Skip to main content
Version: 1.0.0

OpenAI Client with Anthropic Upstream

AISIX lets an application keep the OpenAI Chat Completions request shape while the gateway calls an Anthropic upstream model. Use this pattern when application code is already built around an OpenAI-compatible SDK, but the platform team wants to route that traffic to Claude.

AISIX resolves the model alias, translates the request to Anthropic Messages, calls Anthropic with the stored provider credential, and translates the response back into an OpenAI-compatible chat completion.

Prerequisites

Before starting, prepare the following:

  • A running AISIX gateway that your application can reach.
  • An Anthropic-backed model alias that accepts OpenAI-compatible Chat Completions requests.
  • A caller API key allowed to use that model alias.
  • Node.js 20 LTS or newer with npm for the SDK example, or curl for the HTTP example.

If you have not configured the model alias and caller API key, follow Anthropic for either AISIX Cloud or the open-source AISIX gateway.

Request Flow

The application keeps the OpenAI-compatible client contract. Provider selection and protocol translation stay in the gateway.

The application sends the model alias and caller API key to AISIX. The gateway resolves the upstream model, supplies the stored Anthropic credential, and translates both sides of the exchange. The application continues to send and receive OpenAI-compatible data.

Call the Alias

Export the caller API key and model alias used by both request examples:

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

OpenAI SDK

Install the OpenAI SDK:

npm install openai

Set the OpenAI-compatible base URL. The OpenAI SDK requires the /v1 path:

# The local quickstarts use http://127.0.0.1:3000/v1
export AISIX_BASE_URL="YOUR_AISIX_GATEWAY_URL/v1"

Create a minimal Chat Completions client:

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);

Run the example from the shell where the AISIX values are set:

node anthropic-via-openai-sdk.mjs

HTTP

To inspect the response without an SDK, export the gateway origin and send the same request with curl:

# The local quickstarts use http://127.0.0.1:3000
export AISIX_PROXY="YOUR_AISIX_GATEWAY_ORIGIN"

Send the request:

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."}]
}'

Both examples return the OpenAI-compatible Chat Completions shape. The caller does not receive Anthropic-shaped content blocks:

{
"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
}
}

Translation Behavior

The translation preserves the parts of the OpenAI-compatible contract that common chat applications depend on:

BehaviorWhat AISIX does
Model and authenticationResolves the model alias to the configured Anthropic model, authenticates the caller, and uses the stored provider credential for the upstream request.
Messages and toolsMaps leading system messages, user and assistant messages, function tools, tool calls, and tool results to Anthropic Messages structures.
ResponsesConverts Anthropic text and tool-use blocks, stop reasons, token usage, and streaming events back into OpenAI-compatible fields.
Output limitSupplies max_tokens: 4096 when the OpenAI-compatible request omits an output limit, because Anthropic requires one.
Reasoning effortSends reasoning_effort as output_config.effort, Anthropic's current depth control. minimal maps to low, Anthropic's floor, and none becomes thinking: {"type": "disabled"} instead, because Anthropic has no none tier. AISIX adds no thinking block in the other cases: the request asked for a depth, not a thinking mode, and current Anthropic models apply their own. A reasoning_effort value outside that set is dropped entirely: it corresponds to no known Anthropic tier, and the original field cannot be forwarded in its place either, because /v1/messages rejects unknown top-level fields. An output_config or thinking the request supplies itself is left as written and takes precedence.
caution

When an OpenAI Chat Completions message uses typed content parts, the Anthropic translation preserves text parts but drops non-text parts such as images and audio. Use the Anthropic-style /v1/messages route when image or document content must reach an Anthropic upstream.

For a complete tool loop, see Tool Calling. AISIX can also add Anthropic prompt-cache markers to eligible Chat Completions requests; see Anthropic Prompt Caching.

Use the Anthropic-style /v1/messages route when the application must keep the Anthropic request and response shape, especially for provider-specific content or thinking blocks. See Anthropic-Style Messages API for the native client contract and its compatibility boundaries.

Token Usage

Anthropic and OpenAI count prompt-cache tokens differently, so AISIX converts the counts instead of passing them through. Anthropic reports input_tokens as the non-cached input, with cache_creation_input_tokens and cache_read_input_tokens as separate counters beside it. OpenAI accounting has one prompt_tokens that already includes the cached part, named under prompt_tokens_details.cached_tokens. OpenAI has no cache-write concept at all, so AISIX folds the write into prompt_tokens — it is billed input — and reports it beside the hit.

An upstream response reporting:

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

reaches an OpenAI-compatible caller as:

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

The rules a caller can rely on:

  • prompt_tokens is the full input the model read, cache reads and cache writes included.
  • total_tokens is prompt_tokens + completion_tokens.
  • cached_tokens is a subset of prompt_tokens, and counts only cache reads.
  • cache_creation_tokens is the cache write, also a subset of prompt_tokens. It is billed input but is not a cache hit, so it is reported separately rather than inside cached_tokens. OpenAI has no cache-write concept, so this field appears only when the upstream reported one — which matters because a provider typically bills a write above the plain input rate. On the first turn of a cached conversation, a write with no read, it is the only signal that a cache was involved at all.

The same conversion applies to streaming responses and to the Responses API over an Anthropic upstream.

Logs, metrics, and spend reporting are not converted: they keep Anthropic's own counters, so a call costs the same whichever protocol addressed it. See Anthropic Prompt Caching for the recorded view.

Next Steps

You have now routed an OpenAI-compatible client to an Anthropic upstream. See OpenAI-Compatible API for caller-facing route behavior. Use Anthropic Messages when you want the Anthropic request and response shape end to end, or review Provider Compatibility for endpoint and provider support boundaries.