Tool calling, JSON mode and vision
Both wires accept their own native shapes for tools, structured output and images, on every model. Where a model's own protocol differs from the wire you called, Sator translates the request in and the response out; where they match, the request passes through untouched. Either way you write the request in the dialect of the endpoint you called.
Tools#
| OpenAI wire | Anthropic wire | |
|---|---|---|
| Definition | tools[].function with name, description, parameters (JSON Schema), optional strict | tools[] with name, description, input_schema |
| Choice | "auto", "none", "required", or {"type": "function", "function": {"name": "…"}} | {"type": "auto"}, {"type": "any"}, {"type": "none"}, or {"type": "tool", "name": "…"} (the strings "auto", "none", "required" also work) |
| Parallel calls | parallel_tool_calls: false to disable | tool_choice.disable_parallel_tool_use: true |
| Results | role: "tool" message with tool_call_id | tool_result block naming tool_use_id |
{
"model": "deepseek-v4-flash",
"messages": [{"role": "user", "content": "What is the weather in Montreal?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
}
}],
"tool_choice": "auto"
}When streaming, tool-call deltas are forwarded exactly as the model emits them.
Structured output#
| OpenAI wire | Anthropic wire |
|---|---|
response_format: {"type": "text"}, {"type": "json_object"}, or {"type": "json_schema", "json_schema": {"name", "schema", "description"}} | output_config.format: {"type": "json_schema", "schema": {…}, "name"} — json_schema is the only format accepted |
Vision and documents#
| OpenAI wire | Anthropic wire |
|---|---|
image_url content parts, base64 data URLs only (data:image/png;base64,…). Remote URLs are not fetched. | image and document blocks with source.type: "base64" and source.media_type, or source.type: "url" |
Images count toward the 5 MB body cap.
Reasoning#
- OpenAI wire:
reasoning_effortis passed through; models that expose their reasoning return it asreasoning_contenton the assistant message, and you may send it back on prior assistant turns. - Anthropic wire:
thinkingblocks are accepted in the conversation;redacted_thinkingblocks are dropped silently, the one deliberate silent drop on either wire.
What is refused#
An unsupported value is a 400 invalid_request_error naming the field — for example an unknown tool_choice string, a response_format type other than the three above, a content part of an unknown type, or a message role the wire does not define. Nothing is silently discarded except redacted_thinking. The full list of statuses is in Errors.