How to Translate Between OpenAI Chat Completions and Anthropic Messages API Formats in Switchyard

Switchyard automatically translates between OpenAI Chat Completions and Anthropic Messages API formats through its switchyard-translation crate, which uses provider-specific codecs to decode requests into a canonical LlmRequest model and encode responses back to the target schema.

NVIDIA's Switchyard is an open-source LLM routing proxy that lets you standardize on the OpenAI API format while seamlessly calling Anthropic, OpenAI, or other backends. The translation layer abstracts provider differences behind a unified wire-format interface, eliminating the need for multiple client implementations.

Wire Format Abstraction in Switchyard

The foundation of format translation is the WireFormat enum defined in [crates/protocol/src/format.rs](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/protocol/src/format.rs):

pub enum WireFormat {
    OpenAiChat,          // /v1/chat/completions
    OpenAiResponses,     // /v1/completions (legacy)
    AnthropicMessages,   // Anthropic Messages API
}

Each variant maps to a codec that handles bidirectional translation. Codecs implement two core operations:

  • decode_request — parses provider-specific JSON into the internal LlmRequest model
  • encode_response — renders AggLlmResponse back to the provider's expected schema

OpenAI Chat Completions Codec Implementation

The OpenAiChatCodec in [crates/switchyard-translation/src/codecs/openai_chat/buffered.rs](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/switchyard-translation/src/codecs/openai_chat/buffered.rs) handles OpenAI's /v1/chat/completions format.

It parses standard fields:

  • model — target model identifier
  • messages — conversation history with role and content
  • temperature, top_p, max_tokens — sampling parameters
  • tool_calls / functions — function calling extensions

Unknown extensions are preserved through the translation pipeline, ensuring forward compatibility with new OpenAI features.

Anthropic Messages Codec Implementation

The AnthropicCodec in [crates/switchyard-translation/src/codecs/anthropic/buffered.rs](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/switchyard-translation/src/codecs/anthropic/buffered.rs) manages Anthropic's distinct format.

Key Anthropic-specific constructs it handles:

  • Role values — assistant, user, system, tool (mapped from OpenAI equivalents)
  • Content blocks — type: "text" or type: "image" with source sub-objects
  • Tool blocks — tool_use (model invokes tool) and tool_result (tool returns data)
  • Anthropic-only fields — max_tokens (required), metadata, system (top-level system prompt)

Common utilities in codecs/common.rs eliminate duplication:

  • decode_openai_content / decode_anthropic_content — normalize content representations
  • role_from_openai / role_from_anthropic — bidirectional role mapping

How the Translation Engine Orchestrates Format Conversion

The translation engine at [crates/switchyard-translation/src/engine.rs](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/switchyard-translation/src/engine.rs) coordinates codec selection and execution:

Request Path (Decode)

  1. Server configuration determines source WireFormat from the format key
  2. Engine selects matching codec (OpenAiChatCodec or AnthropicCodec)
  3. decode_request produces normalized LlmRequest for downstream processing

Response Path (Encode)

  1. Engine receives AggLlmResponse from the backend
  2. Target codec selected based on route's declared format
  3. encode_response produces provider-conforming JSON payload

Cross-format forwarding is fully supported — you can send OpenAI-formatted requests to Anthropic backends, with Switchyard handling both request transformation (to Anthropic) and response re-encoding (back to OpenAI).

Configuring Format Translation in Routes

Route configuration controls which codecs are applied. In your TOML configuration (e.g., [examples/experimental/litellm/benchmark-route.toml](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/examples/experimental/litellm/benchmark-route.toml)):

format = "openai_chat"          # or "anthropic_messages"

The server registers /v1/chat/completions for OpenAI format by default, but the same endpoint proxies to any backend format through on-the-fly translation.

Practical OpenAI-to-Anthropic Translation Example

This Python client sends OpenAI-style requests to Switchyard, which translates to Anthropic and back:

import os
import json
import httpx
import asyncio

# 1️⃣ Launch Switchyard with an Anthropic backend route

#    $ switchyard launch claude --model anthropic-route --config routes.toml

async def main():
    url = "http://localhost:4000/v1/chat/completions"
    
    # Standard OpenAI Chat Completions payload

    payload = {
        "model": "claude-2.0",
        "messages": [
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "What is the capital of France?"}
        ],
        "temperature": 0.7
    }

    async with httpx.AsyncClient() as client:
        r = await client.post(url, json=payload, timeout=30)
        print("Switchyard response (OpenAI shape):")
        print(json.dumps(r.json(), indent=2))

asyncio.run(main())

Translation Chain Explained

Step Action Component
1 Detect WireFormat::OpenAiChat from endpoint Server routing
2 Decode OpenAI JSON → LlmRequest OpenAiChatCodec::decode_request
3 Route to Anthropic backend Router
4 Encode LlmRequest → Anthropic format AnthropicCodec::encode_request
5 Receive Anthropic response HTTP client
6 Decode Anthropic → AggLlmResponse AnthropicCodec::decode_response
7 Re-encode to OpenAI format OpenAiChatCodec::encode_response

The client receives standard OpenAI-shaped JSON despite the actual backend being Anthropic.

Validation Through Cross-Format Tests

The test suite in [crates/switchyard-translation/tests/response_translation.rs](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/switchyard-translation/tests/response_translation.rs) guarantees fidelity:

  • openai_chat_response_translates_to_anthropic_message — Chat Completions response renders as Anthropic message with preserved content and tool calls
  • anthropic_request_translates_to_openai_chat_without_anthropic_only_fields — inverse conversion drops Anthropic-specific fields lacking OpenAI equivalents

Key Source Files Reference

File Purpose
[crates/protocol/src/format.rs](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/protocol/src/format.rs) WireFormat enum definition
[crates/switchyard-translation/src/codecs/openai_chat/buffered.rs](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/switchyard-translation/src/codecs/openai_chat/buffered.rs) OpenAI Chat Completions codec
[crates/switchyard-translation/src/codecs/anthropic/buffered.rs](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/switchyard-translation/src/codecs/anthropic/buffered.rs) Anthropic Messages codec
[crates/switchyard-translation/src/engine.rs](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/switchyard-translation/src/engine.rs) Translation orchestration
[crates/switchyard-server/src/config.rs](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/switchyard-server/src/config.rs) Route format configuration
[crates/switchyard-translation/tests/response_translation.rs](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/switchyard-translation/tests/response_translation.rs) Cross-format round-trip tests

Summary

  • Wire formats are abstracted through the WireFormat enum with dedicated codecs per provider
  • OpenAI Chat Completions uses OpenAiChatCodec in codecs/openai_chat/buffered.rs
  • Anthropic Messages uses AnthropicCodec in codecs/anthropic/buffered.rs
  • The translation engine selects codecs based on route format configuration and executes bidirectional conversion
  • Cross-format proxying works transparently — clients use OpenAI format while backend speaks Anthropic
  • Shared utilities in codecs/common.rs prevent duplication of role mapping and content decoding logic

Frequently Asked Questions

Does Switchyard support streaming responses across formats?

Yes. Both OpenAiChatCodec and AnthropicCodec implement streaming variants that handle server-sent events. The translation engine applies the same codec selection logic to SSE chunks, ensuring real-time responses conform to the client's expected format regardless of backend provider.

What happens to fields that exist in only one format?

Anthropic-only fields like metadata or top-level system are dropped when translating to OpenAI format. OpenAI extensions without Anthropic equivalents are similarly discarded in that direction. The test anthropic_request_translates_to_openai_chat_without_anthropic_only_fields explicitly validates this behavior to prevent malformed requests.

Can I chain multiple format translations in one request?

Translation is determined per route, not per request chain. A single request undergoes one decode (source format → LlmRequest) and one encode (AggLlmResponse → target format). To chain formats, you would configure intermediate routes that act as both consumer and producer, though this is uncommon in practice.

How do I debug translation failures?

Enable debug logging for the switchyard_translation crate. The engine logs codec selection decisions and serialization errors. For request debugging, capture the canonical LlmRequest representation before encoding to verify the decode phase succeeded correctly.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →