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 internalLlmRequestmodelencode_response— rendersAggLlmResponseback 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 identifiermessages— conversation history withroleandcontenttemperature,top_p,max_tokens— sampling parameterstool_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"ortype: "image"withsourcesub-objects - Tool blocks —
tool_use(model invokes tool) andtool_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 representationsrole_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)
- Server configuration determines source
WireFormatfrom theformatkey - Engine selects matching codec (
OpenAiChatCodecorAnthropicCodec) decode_requestproduces normalizedLlmRequestfor downstream processing
Response Path (Encode)
- Engine receives
AggLlmResponsefrom the backend - Target codec selected based on route's declared
format encode_responseproduces 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 callsanthropic_request_translates_to_openai_chat_without_anthropic_only_fields— inverse conversion drops Anthropic-specific fields lacking OpenAI equivalents
Key Source Files Reference
Summary
- Wire formats are abstracted through the
WireFormatenum with dedicated codecs per provider - OpenAI Chat Completions uses
OpenAiChatCodecincodecs/openai_chat/buffered.rs - Anthropic Messages uses
AnthropicCodecincodecs/anthropic/buffered.rs - The translation engine selects codecs based on route
formatconfiguration and executes bidirectional conversion - Cross-format proxying works transparently — clients use OpenAI format while backend speaks Anthropic
- Shared utilities in
codecs/common.rsprevent 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →