Docs menu

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 wireAnthropic wire
Definitiontools[].function with name, description, parameters (JSON Schema), optional stricttools[] 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 callsparallel_tool_calls: false to disabletool_choice.disable_parallel_tool_use: true
Resultsrole: "tool" message with tool_call_idtool_result block naming tool_use_id
json
{
  "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 wireAnthropic 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 wireAnthropic 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_effort is passed through; models that expose their reasoning return it as reasoning_content on the assistant message, and you may send it back on prior assistant turns.
  • Anthropic wire: thinking blocks are accepted in the conversation; redacted_thinking blocks 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.