# How Switchyard Translates OpenAI Responses to Anthropic Messages Format

> Learn how Switchyard translates OpenAI responses to Anthropic Messages format using its codec-based engine. Discover seamless integration and data transformation.

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

---

**Switchyard converts OpenAI‑style chat completion responses into Anthropic Messages format through a codec‑based translation engine that decodes OpenAI wire format into an internal `Response` model, then re‑encodes it using the Anthropic codec.**

The NVIDIA NeMo Switchyard project provides a unified translation layer for large language model (LLM) wire protocols. When downstream systems need to consume OpenAI API responses through an Anthropic-compatible interface, Switchyard's `switchyard‑translation` crate handles the protocol conversion automatically.

## The Translation Pipeline Architecture

Switchyard's translation follows a **decode‑transform‑encode pattern**. The engine (`Engine::translate_response`) orchestrates the process by selecting codec pairs based on the source and target `WireFormat` variants.

### Step‑by‑Step Conversion Flow

The translation proceeds through six distinct mapping operations:

| Step | OpenAI Response Element | Anthropic Messages Target | Implementation Location |
|------|------------------------|---------------------------|------------------------|
| 1 | `id`, `model`, `created` metadata | Preserved as top‑level fields | Direct field copy in decoders |
| 2 | `choices[].message` (role + content) | `content` array of `Message` objects with `type`, `text`, or `image` blocks | [`codecs/openai_chat/buffered.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/codecs/openai_chat/buffered.rs) → [`codecs/anthropic/buffered.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/codecs/anthropic/buffered.rs) |
| 3 | `usage` (prompt/completion/total tokens) | `usage` (`input_tokens`, `output_tokens`) | Field remapping with numeric transformation |
| 4 | `tool_calls` arrays | `tool_result` objects (`tool_use_id`, `content`, `cache_control`) | Special‑case handling in both codecs |
| 5 | `finish_reason` (`stop`, `length`, `tool_calls`) | `stop_reason` (`stop_sequence`, `max_tokens`, `tool_use`) | Enum mapping with priority rules |
| 6 | `logprobs`, OpenAI extensions | **Dropped** — no Anthropic equivalent | Silent filtering during encode |

## Core Source Files and Their Roles

### `crates/switchyard‑translation/src/engine.rs`

The **central dispatcher** implements `Engine::translate_response()`. This function receives raw bytes, selects the appropriate decoder based on `WireFormat::OpenAIChat`, and routes the intermediate `Response` struct to the encoder for `WireFormat::AnthropicMessages`.

```rust
use switchyard_translation::engine::Engine;
use switchyard_translation::protocol::WireFormat;

let engine = Engine::new();

// Decode OpenAI chat completion response
let openai_resp = engine.decode(&openai_body, WireFormat::OpenAIChat)?;

// Re‑encode as Anthropic Messages format
let anth_msg = engine.encode(openai_resp, WireFormat::AnthropicMessages)?;

println!("{}", serde_json::to_string_pretty(&anth_msg)?);

```

### `crates/switchyard‑translation/src/codecs/openai_chat/buffered.rs`

The **OpenAI decoder** extracts structured data from JSON responses. It handles:

- **Message parsing**: Extracts `role` (`system`, `user`, `assistant`, `tool`) and `content` strings
- **Tool call detection**: Identifies `tool_calls` arrays and constructs internal `ToolUse` representations
- **Usage normalization**: Converts token counts to internal numeric types
- **Finish reason capture**: Maps `stop`, `length`, `content_filter`, `tool_calls` to internal enums

Key structures include the `OpenAIChatDecoder` and `BufferedOpenAIChatCodec` implementations.

### `crates/switchyard‑translation/src/codecs/anthropic/buffered.rs`

The **Anthropic encoder** rebuilds the response according to the Messages API specification:

- **Content block assembly**: Creates `ContentBlock` variants (`Text`, `Image`, `ToolUse`, `ToolResult`)
- **Role translation**: Maps OpenAI roles to Anthropic's stricter `user`/`assistant` dichotomy, with tool messages rendered as `ToolResult` blocks
- **Stop reason priority**: Implements the rule that `max_tokens` stop reasons take precedence over generic `stop` — as verified in test `anthropic_message_stop_does_not_overwrite_max_tokens_stop_reason`

## Field‑Level Mapping Details

### Messages and Content Blocks

OpenAI's flat `message.content` string becomes Anthropic's structured `content` array:

```json
// OpenAI input
{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "The answer is 42."
    }
  }]
}

// Anthropic output
{
  "content": [{
    "type": "text",
    "text": "The answer is 42."
  }]
}

```

For multi‑part content including images, the decoder splits on content type and the encoder builds appropriate `image` blocks with `source` objects.

### Tool Call Handling

When OpenAI returns tool invocations:

```json
// OpenAI tool_calls
{
  "tool_calls": [{
    "id": "call_abc123",
    "type": "function",
    "function": {
      "name": "get_weather",
      "arguments": "{\"location\":\"Paris\"}"
    }
  }]
}

```

The translation produces Anthropic `tool_use` and `tool_result` blocks:

```json
// Anthropic equivalent
{
  "content": [{
    "type": "tool_use",
    "id": "call_abc123",
    "name": "get_weather",
    "input": {"location": "Paris"}
  }]
}

```

### Usage Token Accounting

The usage translation handles field renaming and aggregation:

| OpenAI Field | Anthropic Field | Transformation |
|-------------|-----------------|----------------|
| `prompt_tokens` | `input_tokens` | Direct rename |
| `completion_tokens` | `output_tokens` | Direct rename |
| `total_tokens` | *implicit sum* | Validated against input + output |

### Finish Reason Priority Logic

The encoder implements careful precedence handling. Per the test suite in [`stream_translation.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/stream_translation.rs), an Anthropic `stop` reason will **not** overwrite a `max_tokens` stop reason already set. This ensures accurate reporting of truncation conditions.

## Practical Usage Example

```python
import switchyard
from switchyard.protocol import WireFormat

# Raw OpenAI chat completion response body

openai_json = b'''
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1677652288,
  "model": "gpt-4",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "Hello! How can I help?"
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 12,
    "total_tokens": 21
  }
}
'''

engine = switchyard.translation.Engine()

# Decode from OpenAI format

openai_resp = engine.decode(openai_json, WireFormat.OPENAI_CHAT)

# Translate to Anthropic Messages format

anthropic_msg = engine.encode(openai_resp, WireFormat.ANTHROPIC_MESSAGES)

# Result ready for Anthropic /v1/messages endpoint

print(anthropic_msg)

```

## Verification Through Test Suite

The translation logic is validated across multiple test files:

- **[`response_translation.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/response_translation.rs)**: Round‑trip correctness between OpenAI and Anthropic formats
- **[`stream_translation.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/stream_translation.rs)**: Streaming response handling, including the finish reason priority rules
- **[`tool_call_translation.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/tool_call_translation.rs)**: Tool use and tool result block accuracy

These tests confirm that edge cases — such as empty content, multiple choices, and mixed tool/text responses — all translate correctly.

## Summary

- Switchyard uses a **codec‑based architecture** with `switchyard‑translation` as the core crate
- The `Engine` struct in [`engine.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/engine.rs) dispatches decode/encode operations based on `WireFormat` enums
- **OpenAI responses** are parsed by [`codecs/openai_chat/buffered.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/codecs/openai_chat/buffered.rs) into an internal `Response` model
- **Anthropic Messages** are generated by [`codecs/anthropic/buffered.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/codecs/anthropic/buffered.rs) from that same model
- Key transformations include: content block restructuring, tool call reformatting, usage field renaming, and finish reason priority handling
- The implementation is **verified by comprehensive tests** covering streaming, tools, and edge cases

## Frequently Asked Questions

### What is the internal representation between OpenAI and Anthropic formats?

Switchyard uses a **protocol‑agnostic `Response` struct** defined in the translation crate. This intermediate representation captures messages, usage statistics, tool calls, and metadata in a normalized form. Both codecs operate on this struct, enabling N‑to‑M format translation rather than hardcoded 1‑to‑1 mappings.

### Does Switchyard support streaming translation between these formats?

Yes. The [`buffered.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/buffered.rs) implementations in both codecs handle **incremental SSE (Server‑Sent Events) streams**. The OpenAI decoder parses `data:` lines into partial responses, and the Anthropic encoder emits compatible incremental deltas. The [`stream_translation.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/stream_translation.rs) test file specifically validates this behavior, including finish reason preservation across chunk boundaries.

### Are tool calls fully compatible between OpenAI and Anthropic formats?

Full compatibility requires **semantic transformation**. OpenAI's `tool_calls` with `function` objects become Anthropic's `tool_use` blocks with `input` fields. The translation preserves `id` values for correlation and handles the round‑trip when tool results return from the client. Complex argument schemas pass through JSON parsing without modification.

### What happens to OpenAI‑specific fields that Anthropic doesn't support?

Fields like `logprobs`, `content_filter_results`, and `system_fingerprint` are **silently discarded** during encoding. The translation prioritizes schema compliance over data preservation — if Anthropic's Messages API has no equivalent field, the information is not included in the output.