# AtmoRouter — Tool calling & JSON

Source: https://atmorouter.dev/docs/tool-calling

Tool calling is passed through to the selected model. AtmoRouter does not execute your functions. Your application validates arguments, runs its own function, and returns the result in the next message.

## OpenAI function-calling round trip

```
import json, os
from openai import OpenAI

client = OpenAI(api_key=os.environ["ATMOROUTER_API_KEY"],
                base_url="https://atmorouter.dev/v1")
messages = [{"role": "user", "content": "What is the weather in Paris?"}]
tools = [{"type": "function", "function": {
    "name": "get_weather", "description": "Get weather for a city",
    "parameters": {"type": "object", "properties": {
        "city": {"type": "string"}}, "required": ["city"]}
}}]
first = client.chat.completions.create(model="YOUR_MODEL_ID", messages=messages,
                                      tools=tools, max_tokens=256)
message = first.choices[0].message
messages.append(message.model_dump(exclude_none=True))
for call in message.tool_calls or []:
    if call.function.name != "get_weather":
        raise ValueError("Unexpected tool")
    args = json.loads(call.function.arguments)
    if not isinstance(args.get("city"), str):
        raise ValueError("Invalid city")
    # Replace this demo result with your weather service.
    result = {"city": args["city"], "temperature_c": 18, "demo": True}
    messages.append({"role": "tool", "tool_call_id": call.id,
                     "content": json.dumps(result)})
if message.tool_calls:
    final = client.chat.completions.create(model="YOUR_MODEL_ID", messages=messages,
                                          tools=tools, max_tokens=256)
    print(final.choices[0].message.content)
else:
    print(message.content)
```

Some models return a normal answer instead of tool calls. Handle both. Streamed tool names and arguments can be split across chunks: collect by tool-call index, wait for completion, then parse JSON and validate it. Never evaluate model-generated code.

## JSON and schema output

```
response = client.chat.completions.create(
    model="YOUR_MODEL_ID",
    messages=[{"role":"user", "content":"Return a JSON object with a city field."}],
    response_format={"type":"json_object"},
    max_tokens=128,
)
value = json.loads(response.choices[0].message.content)
if not isinstance(value.get("city"), str):
    raise ValueError("Unexpected result schema")
```

`response_format`, strict schemas, tool choice, vision input, and reasoning controls are model-dependent. A forwarded parameter is not a gateway guarantee. Anthropic tools use the native `tools`, `tool_use`, and `tool_result` shapes instead of OpenAI tool messages.
