Guides

Tool calling

Tool calling (function calling) lets the model ask your code to run functions you define, such as looking up the weather, querying a database or running code. The model only decides which tool to call and with what arguments; your code does the actual work.

How it works

  1. Describe the available tools in the request with JSON Schema.
  2. When the model needs a tool, it returns the tool name and arguments instead of an answer.
  3. Your code runs the tool and sends the result back as a new message.
  4. The model uses the result to answer. If it needs another tool, it asks again, looping until it's done.

OpenAI-compatible format

tools_openai.py
import json
from openai import OpenAI

client = OpenAI(api_key="sk-...", base_url="https://api.flyrouter.ai/v1")

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get the current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string", "description": "City name, e.g. London"}},
            "required": ["city"],
        },
    },
}]

messages = [{"role": "user", "content": "What's the weather in Shanghai right now?"}]
resp = client.chat.completions.create(model="gpt-6.1-sol", messages=messages, tools=tools)
msg = resp.choices[0].message

if msg.tool_calls:
    messages.append(msg)
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = {"city": args["city"], "weather": "cloudy", "temperature": 22}  # replace with your implementation
        messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
    resp = client.chat.completions.create(model="gpt-6.1-sol", messages=messages, tools=tools)

print(resp.choices[0].message.content)

Anthropic native format

Claude tools are defined with input_schema. When the model wants a tool it returns a tool_use content block, and you send the result back in a tool_result block:

tools_claude.py
from anthropic import Anthropic

client = Anthropic(api_key="sk-...", base_url="https://api.flyrouter.ai")

tools = [{
    "name": "get_weather",
    "description": "Get the current weather for a city",
    "input_schema": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
    },
}]

messages = [{"role": "user", "content": "What's the weather in Shanghai right now?"}]
resp = client.messages.create(model="claude-sonnet-5-5", max_tokens=4096, tools=tools, messages=messages)

while resp.stop_reason == "tool_use":
    messages.append({"role": "assistant", "content": resp.content})
    results = []
    for block in resp.content:
        if block.type == "tool_use":
            output = '{"weather": "cloudy", "temperature": 22}'  # replace with your implementation
            results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
    messages.append({"role": "user", "content": results})
    resp = client.messages.create(model="claude-sonnet-5-5", max_tokens=4096, tools=tools, messages=messages)

print(next(b.text for b in resp.content if b.type == "text"))

Tips

  • Write clear descriptions: what the tool does, when to use it and what the arguments look like matter more than any parameter.
  • Parallel calls: the model may request several tools at once. Run them all and return every result in a single message.
  • Return failures too: in the OpenAI format, return an error description; in the Anthropic format, set "is_error": true on the tool_result. Don't drop them.
  • Always parse arguments as JSON: use json.loads / JSON.parse rather than matching on the raw string.

Newer Claude models don't support forced tool use

claude-opus-5-5, claude-sonnet-5-5 and claude-fable-5-1 reject tool_choice set to any or a specific tool (400). Use the default auto and say in the prompt which tool to call. To guarantee arguments match the schema exactly, add "strict": true to the tool definition.

FlyRouter

One key, every top AI model