Dialect Translation
When an llm · client speaks a different dialect from the request, Zilla translates the request into the client's API and the reply back into the request's API. The application sees only its own API, whichever provider answers.
When Translation Happens
Translation happens when the request's dialect differs from the dialect of the llm · client its route leads to, for example an OpenAI SDK request for claude-haiku-4-5 routed to an Anthropic client, or an Anthropic SDK request for gpt-4o-mini routed to an OpenAI client.
When the two dialects match, Zilla forwards the request and the reply unchanged, with every setting the caller sent.
What Is Translated in a Request
Zilla translates the conversation, section by section:
| Section | OpenAI API | Anthropic API |
|---|---|---|
| System instruction | A system or developer message | The top-level system |
| User text | Message content | Text blocks |
| Images | image_url, a URL or data: URL | image blocks with a URL or base64 source |
| Documents | file parts | document blocks |
| Assistant text | Message content | Text blocks |
| Tool definitions | tools[].function with parameters | tools[] with input_schema |
| Tool calls | tool_calls, with arguments as a JSON string | tool_use blocks, with input as an object |
| Tool results | tool messages | tool_result blocks |
The output token limit is carried across too:
| Setting | OpenAI API | Anthropic API |
|---|---|---|
| Output token limit | max_completion_tokens or max_tokens | max_tokens |
When a request without a limit is sent to Anthropic, Zilla sets max_tokens to 4096. With Zilla Plus, the limit is also carried to and from the AWS Bedrock Converse API, as inferenceConfig.maxTokens.
The settings that the APIs share are carried across too, each in the form of the receiving API: temperature, top_p, stop, seed, the penalties, tool_choice, parallel_tool_calls, response_format, the reasoning effort, user, and the service tier. A setting the receiving API has no equivalent for is dropped, and one it can't represent, such as stop for an OpenAI Responses request, is rejected.
What Isn't Translated in a Request
Other top-level settings are not carried across when they have no form shared by the APIs.
Some content can't be translated:
| Content | From OpenAI to Anthropic | From Anthropic to OpenAI |
|---|---|---|
| Audio input | Rejected | — |
| Video input | Rejected | Rejected |
| Typed server tools, such as a built-in web search tool | — | Rejected |
Reasoning (thinking), server tool results, citations | — | Dropped |
A request with content that is rejected gets 400 Bad Request, with a message naming the section, such as tool-definition cannot be converted from anthropic to openai.
OpenAI Chat Completions and Responses
The openai dialect covers two APIs, Chat Completions and Responses. A request in one is translated when the llm · client serves only the other, as set by its apis option, and the reply is translated back.
Members that belong to one OpenAI API are not carried to the other, except store, metadata, prompt_cache_key, user, include_obfuscation, and service_tier, which mean the same in both. Some members are rejected rather than dropped, because dropping them would change what the request does. A request that needs translating to the other API, or to another dialect, gets 400 Bad Request with type invalid_request_error when it carries:
| Request | Rejected |
|---|---|
| Responses | previous_response_id, background, conversation |
| Chat Completions | n greater than 1, logprobs set to true, audio, modalities |
The message names the member and the APIs, with openai/ccs for Chat Completions and openai/rs for Responses. Harmless values, such as null, n set to 1, and logprobs or background set to false, are dropped.
Model Capabilities
Some providers and models don't accept every setting. An llm · client can describe them, so that Zilla drops or rejects a setting before it sends the request, instead of the provider rejecting the request:
unsupportedlists the settings the provider doesn't honor. An advisory setting, such asseed, is dropped. A behavioral one,stop,tool_choice,parallel_tool_calls, orresponse_format, gets400 Bad Requestnaming the setting, unless its value is harmless, such astool_choiceset toauto.exclusivelists settings the provider refuses together, such asreasoning_effortandthinking_budget_tokens, so that only the first one present is sent.modelsdescribes individual models, by name or*pattern: whether they accept sampling settings such astemperature, their thinking form and budget range, the thinking budget for each reasoning effort, and their service tiers. An entry can also set its ownunsupportedandexclusive.
openai_compatible_llm_client:
type: llm
kind: client
options:
dialect: openai
server: https://my-openai-compatible-server/v1
unsupported:
- seed
- service_tier
models:
my-reasoning-model:
sampling: false
exit: sys:http_clientEach dialect has built-in defaults for its models, so these options are only needed for a provider or model the defaults don't describe. See options.models for every setting.
What Is Translated in a Reply
Text, tool calls, refusals, and the finish reason are returned in the request's API.
| Finish reason | OpenAI caller | Anthropic caller |
|---|---|---|
| Natural end | stop | end_turn |
| Token limit | length | max_tokens |
| Tool call | tool_calls | tool_use |
| Refusal or content filter | content_filter | refusal |
A refusal is returned as message.refusal to an OpenAI caller, and as a text block with stop_reason: "refusal" to an Anthropic caller. Reasoning is returned to an Anthropic caller as a thinking block, and not returned to an OpenAI caller. Citations are not returned.
Only one choice is returned, and logprobs are not returned.
Gemini
The Gemini API is translated the same way, section by section:
| Section | Gemini API |
|---|---|
| System instruction | systemInstruction |
| User text | text parts of a user content |
| Images, documents, audio and video | inlineData parts, or fileData parts for a URL or file reference |
| Assistant text | text parts of a model content |
| Reasoning | text parts with thought set to true |
| Tool definitions | tools[].functionDeclarations |
| Tool calls | functionCall parts |
| Tool results | functionResponse parts, in a user content |
The output token limit is carried as generationConfig.maxOutputTokens, and Gemini finish reasons map to stop for STOP, length for MAX_TOKENS, and content_filter for SAFETY and the other blocked reasons. Gemini reports thoughtsTokenCount as reasoning tokens, and they are counted in the output tokens. A Gemini caller sees the thought parts of a Claude thinking block, and the tool calls of any provider, as functionCall parts.
Typed server tools, such as a built-in web search tool, are rejected when they are sent to a different API, and server tool results and citations are dropped, as for the other APIs.
A reply from a Gemini model is translated into the caller's API as it is streamed by the provider, because a request translated to the Gemini API always streams.
Token Usage
Token counts are passed through from the provider into the request's API:
| Count | OpenAI caller | Anthropic caller |
|---|---|---|
| Input | prompt_tokens | input_tokens |
| Output | completion_tokens | output_tokens |
| Total | total_tokens, input plus output when the provider doesn't report it | Not returned |
| Cache read | prompt_tokens_details.cached_tokens | cache_read_input_tokens |
| Cache write | Not returned | cache_creation_input_tokens |
| Reasoning | completion_tokens_details.reasoning_tokens | Not returned |
The llm.* metrics record the counts the provider reports, whether or not the caller's API can show them.
Streaming
A request that asks for a stream receives one in its own API's event format:
- OpenAI caller: a chunk with the role, content deltas, tool calls as a chunk with the id and name followed by argument fragments, a chunk with the
finish_reason, then[DONE]. When the request setsstream_options.include_usagetotrue, a usage chunk with emptychoicescomes before[DONE]. - Anthropic caller:
message_start, thencontent_block_start,content_block_delta, andcontent_block_stopfor each block, thenmessage_deltawith the stop reason and token usage, thenmessage_stop. Themessage_startevent reports zero tokens.
Errors
When the provider rejects a translated request, its status, error type, and message are kept, in the error format of the request's API. See Unified API.
Next Steps
- Unified API: the APIs Zilla accepts.
- Providers: the clients that call each provider.
- llm · client: the full
llm · clientreference.

