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

> Effortlessly translate between OpenAI Chat Completions and Anthropic Messages API formats using Switchyard. Learn how its translation crate and codecs streamline your LLM integrations.

- Repository: [NVIDIA-NeMo/Switchyard](https://github.com/NVIDIA-NeMo/Switchyard)
- Tags: how-to-guide
- Published: 2026-08-15

---

**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)](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/protocol/src/format.rs):

```rust
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)](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)](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`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/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)](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)](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/examples/experimental/litellm/benchmark-route.toml)):

```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:

```python
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)](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)](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)](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)](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)](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)](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)](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`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/codecs/openai_chat/buffered.rs)
- **Anthropic Messages** uses `AnthropicCodec` in [`codecs/anthropic/buffered.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/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`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/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.