# Tool calling

> Let models call your functions. Define tools, run the call loop, send results back, force a tool, and handle parallel calls in OpenAI and Anthropic formats.

Source: https://oneaiapikey.com/docs/tool-calling

Tool calling (function calling) lets the model ask your code to run a function, for example a database lookup or an API call, and then use the result in its answer. The model never runs anything itself: it returns the function name and JSON arguments, your code runs it, and you send the result back.

Every model with tool support in the [models table](https://oneaiapikey.com/docs/models#current-models) accepts tools in the OpenAI format. Claude models also accept them in the Anthropic format.

## The loop

1. Send the conversation plus a list of `tools`, each with a name, a description and a JSON Schema for its arguments.
2. If the reply has `finish_reason: "tool_calls"`, run each entry in `message.tool_calls`.
3. Append the assistant message, then one `tool` message per call with the matching `tool_call_id` and the result as a string.
4. Send the request again. Repeat until the model answers without tool calls.

Python:

```python
import json, os
from openai import OpenAI

client = OpenAI(base_url="https://api.oneaiapikey.com/v1", api_key=os.environ["RELAYKIT_API_KEY"])

def get_order_status(order_id: str) -> dict:
    return {"order_id": order_id, "status": "shipped", "eta": "2 days"}  # your real lookup

tools = [{
    "type": "function",
    "function": {
        "name": "get_order_status",
        "description": "Look up the shipping status of an order by its id.",
        "parameters": {
            "type": "object",
            "properties": {"order_id": {"type": "string", "description": "Order id, e.g. A-1042"}},
            "required": ["order_id"],
        },
    },
}]
messages = [{"role": "user", "content": "Where is my order A-1042?"}]

while True:
    reply = client.chat.completions.create(model="gpt-5.6-terra", messages=messages, tools=tools)
    msg = reply.choices[0].message
    messages.append(msg)                      # keep the assistant turn, tool_calls included
    if not msg.tool_calls:
        break
    for call in msg.tool_calls:               # may be several in one turn
        args = json.loads(call.function.arguments)
        result = get_order_status(**args)
        messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})

print(msg.content)
```

Node:

```js
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.oneaiapikey.com/v1", apiKey: process.env.RELAYKIT_API_KEY });

const getOrderStatus = ({ order_id }) => ({ order_id, status: "shipped", eta: "2 days" }); // your real lookup

const tools = [{
  type: "function",
  function: {
    name: "get_order_status",
    description: "Look up the shipping status of an order by its id.",
    parameters: {
      type: "object",
      properties: { order_id: { type: "string", description: "Order id, e.g. A-1042" } },
      required: ["order_id"],
    },
  },
}];
const messages = [{ role: "user", content: "Where is my order A-1042?" }];

let msg;
while (true) {
  const reply = await client.chat.completions.create({ model: "gpt-5.6-terra", messages, tools });
  msg = reply.choices[0].message;
  messages.push(msg); // keep the assistant turn, tool_calls included
  if (!msg.tool_calls?.length) break;
  for (const call of msg.tool_calls) {
    const result = getOrderStatus(JSON.parse(call.function.arguments));
    messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) });
  }
}
console.log(msg.content);
```

Every round trip is a separate, separately billed request, and each one re-sends the tool definitions and the growing history. Keep tool descriptions short and cap the number of rounds in production code.

## Forcing or blocking tools

tool_choice:

```python
# Always call this one tool
tool_choice={"type": "function", "function": {"name": "get_order_status"}}

# Must call some tool / may call one (default) / must not call any
tool_choice="required"   # or "auto", "none"
```

A forced call is also the most portable way to get [structured output](https://oneaiapikey.com/docs/structured-output): define one tool whose parameters are the shape you want, and force it.

## Parallel calls

Models may return several tool calls in one turn (for example, two order lookups). Run them all, in any order, and send one `tool` message per call before the next request. To get at most one call per turn, set `parallel_tool_calls: false`.

## Anthropic format

On [`/v1/messages`](https://oneaiapikey.com/docs/anthropic-messages) the shapes are Anthropic’s: tools have an `input_schema`, the model replies with `tool_use` content blocks and `stop_reason: "tool_use"`, and you answer with `tool_result` blocks inside a `user` message.

Anthropic tool use:

```python
import os, json
import anthropic

client = anthropic.Anthropic(base_url="https://api.oneaiapikey.com", api_key=os.environ["RELAYKIT_API_KEY"])

tools = [{
    "name": "get_order_status",
    "description": "Look up the shipping status of an order by its id.",
    "input_schema": {
        "type": "object",
        "properties": {"order_id": {"type": "string"}},
        "required": ["order_id"],
    },
}]
messages = [{"role": "user", "content": "Where is my order A-1042?"}]

first = client.messages.create(model="claude-sonnet-5-5", max_tokens=1024, tools=tools, messages=messages)
if first.stop_reason == "tool_use":
    call = next(b for b in first.content if b.type == "tool_use")
    result = {"order_id": call.input["order_id"], "status": "shipped"}
    messages += [
        {"role": "assistant", "content": first.content},
        {"role": "user", "content": [
            {"type": "tool_result", "tool_use_id": call.id, "content": json.dumps(result)},
        ]},
    ]
    final = client.messages.create(model="claude-sonnet-5-5", max_tokens=1024, tools=tools, messages=messages)
    print(final.content[0].text)
```

When you call a Claude model in the OpenAI format, the gateway converts tools, `tool_choice`, `parallel_tool_calls` and the tool messages in both directions, so the OpenAI loop above works unchanged.

## Tips

- Write descriptions for the model, not for people: say when to use the tool and what each argument means.
- Validate arguments before you act on them. The JSON is usually well-formed, but the values can be wrong.
- Return errors as tool results (`{"error": "order not found"}`) instead of failing the request, so the model can recover.
- When streaming, the arguments arrive in pieces. See [Streaming](https://oneaiapikey.com/docs/streaming#tool-calls-in-a-stream).
- Some reasoning models restrict tools in chat completions depending on the reasoning setting. If a model rejects your tools, the error message says why; try a lower `reasoning_effort` or the [Responses API](https://oneaiapikey.com/docs/responses).

> **Tools that change things:** If a tool sends email, spends money or deletes data, ask the user to confirm before your code runs it. The model decides which tool to call from text it reads, and that text may include instructions planted in documents or web pages.

## Related

- [Structured output](https://oneaiapikey.com/docs/structured-output)
- [Chat completions](https://oneaiapikey.com/docs/chat-completions#roles): the `tool` role.
- [MCP servers](https://oneaiapikey.com/docs/mcp): connect MCP tools in Claude Code, Codex CLI, Cursor and other coding tools.
