> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ruapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Function calling (tool use)

> How a model calls your functions through RuAPI: a complete Python example with the OpenAI SDK

Function calling (a.k.a. *tool use*) is when the model, instead of replying with text, returns a **structured request** to call one of your functions: its name and its arguments. **You** run the actual code on your side and feed the result back. The model then turns that result into a normal, human-readable answer.

It sounds modest, but this is the mechanism every agent is built on. A model can't query your database, fetch the weather, or hit a third-party API on its own — but it can ask you to do it and reason about whatever you hand back.

The flow is always the same:

1. You describe the available functions (their schema) and send a normal request with `tools=`.
2. The model replies not with text but with a `tool_calls` array — what to call and with which arguments.
3. You run the function yourself and send a **second** request, appending the model's reply and the function result to the history.
4. The model returns the final text.

## Complete Python example

Let's use the classic `get_weather(city)`. In real life it would hit a weather service; here it returns a stub so the flow stays clear.

```python theme={null}
import json
from openai import OpenAI

client = OpenAI(
    api_key="sk-YOUR_KEY",
    base_url="https://www.ruapi.ai/v1",
)

# This is your real code. The model does NOT run it — it only asks you to.
def get_weather(city: str) -> str:
    # a real weather API call would go here
    return f"It's currently +18°C and partly cloudy in {city}"

# Function description for the model: what it does and what arguments it takes
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Returns the current weather in the given 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 London right now?"}]

# Step 1. First request — hand the model the question and the tool list
response = client.chat.completions.create(
    model="claude-opus-4-8",  # exact names are at www.ruapi.ai
    messages=messages,
    tools=tools,
)

message = response.choices[0].message

# Step 2. Check whether the model wants to call a function
if message.tool_calls:
    # IMPORTANT: first append the model's own reply (the one with tool_calls)
    messages.append(message)

    # Step 3. Handle each call (there may be more than one)
    for tool_call in message.tool_calls:
        # Arguments arrive as a JSON STRING — you have to parse it
        args = json.loads(tool_call.function.arguments)

        if tool_call.function.name == "get_weather":
            result = get_weather(args["city"])
        else:
            result = "Unknown function"

        # Send the call result back as a separate role="tool" message
        messages.append(
            {
                "role": "tool",
                "tool_call_id": tool_call.id,  # ties the result to this call
                "content": result,
            }
        )

    # Step 4. Second request — now the model sees the result and gives the final answer
    final = client.chat.completions.create(
        model="claude-opus-4-8",
        messages=messages,
        tools=tools,
    )
    print(final.choices[0].message.content)
else:
    # The model chose to answer directly, without calling a function
    print(message.content)
```

<Note>
  Tool use is only supported on **capable models** — the [Claude](/en/claude-api) and
  [GPT](/en/gpt-api) families, for example. Older or lightweight models may ignore
  `tools=` entirely. To see which model supports what, check the capability badges on
  the **Pricing** page at [www.ruapi.ai](https://www.ruapi.ai).
</Note>

## Gotchas

<AccordionGroup>
  <Accordion title="The model can ask for several calls at once">
    A single reply's `message.tool_calls` is an **array** and may hold more than one
    call — the weather in two cities, say. Loop over it and add a separate
    `role="tool"` message with its own `tool_call_id` for **every** `tool_call`.
    Leave even one call unanswered and the next request will fail.
  </Accordion>

  <Accordion title="Assistant reply first, then the function result">
    The order in `messages` is strict: append the **assistant's own reply** (the
    `message` object carrying `tool_calls`) first, and only **after** it the
    `role="tool"` messages with the results. Send a result without the preceding
    assistant reply and the API rejects the history as inconsistent.
  </Accordion>

  <Accordion title="Arguments arrive as a string, not an object">
    `tool_call.function.arguments` is a **JSON string**, not a ready-made dict. Parse
    it before use with `json.loads(...)`. Indexing the raw string by key will raise.
  </Accordion>
</AccordionGroup>

## What's next

* [Quickstart](/en/quickstart) — sign-up, key, and first request
* [Errors and response codes](/en/errors) — what to do when a request fails
