> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.asi1.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.asi1.ai/_mcp/server.

# Tool Calling

> Extend ASI:One models with custom functions and real-world actions through tool selection.

Tool calling in ASI:One allows models to go beyond text generation by invoking external functions with the right parameters. This enables integration with APIs, tools, or your own code to retrieve live data, perform tasks, or trigger actions based on user input.

## Overview

Tool calling connects your own code to ASI:One. When given access to defined tools, the model can choose to call them based on the conversation context. You then execute the corresponding code, return the results, and the model incorporates the output into its final reply.

It works on both `/v1/chat/completions` and `/v1/responses`. The examples on this page use `/v1/chat/completions`; see [On the Responses API](#on-the-responses-api) for what changes on the other endpoint.

A basic tool-calling request:

#### cURL

```bash
curl -X POST https://api.asi1.ai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ASI_ONE_API_KEY" \
  -d '{
    "model": "asi1",
    "messages": [
      {
        "role": "system",
        "content": "You are a weather assistant. When a user asks for the weather in a location, use the get_weather tool with the appropriate latitude and longitude for that location."
      },
      {
        "role": "user",
        "content": "What'\''s the current weather like in Indore right now?"
      }
    ],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "get_weather",
          "description": "Get current temperature for a given location (latitude and longitude).",
          "parameters": {
            "type": "object",
            "properties": {
              "latitude": { "type": "number" },
              "longitude": { "type": "number" }
            },
            "required": ["latitude", "longitude"]
          }
        }
      }
    ],
    "temperature": 0.7,
    "max_tokens": 1024
  }'
```

#### Python

```python
import os

from openai import OpenAI

client = OpenAI(
    api_key=os.getenv("ASI_ONE_API_KEY"),
    base_url="https://api.asi1.ai/v1",
)

completion = client.chat.completions.create(
    model="asi1",
    messages=[
        {
            "role": "system",
            "content": (
                "You are a weather assistant. When a user asks for the weather in "
                "a location, use the get_weather tool with the appropriate "
                "latitude and longitude for that location."
            ),
        },
        {"role": "user", "content": "What's the current weather like in Indore right now?"},
    ],
    tools=[
        {
            "type": "function",
            "function": {
                "name": "get_weather",
                "description": "Get current temperature for a given location (latitude and longitude).",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "latitude": {"type": "number"},
                        "longitude": {"type": "number"},
                    },
                    "required": ["latitude", "longitude"],
                },
            },
        }
    ],
    temperature=0.7,
    max_tokens=1024,
)

print(completion.choices[0].message.tool_calls)
```

### Example response

The model replies with the tool call rather than an answer:

```json
{
    "id": "chatcmpl-c41d7e90-2b6f-4a38-8e15-9f0a3d6c2e7b",
    "choices": [{
        "finish_reason": "tool_calls",
        "index": 0,
        "logprobs": null,
        "message": {
            "content": "",
            "refusal": null,
            "role": "assistant",
            "annotations": null,
            "audio": null,
            "function_call": null,
            "tool_calls": [{
                "id": "chatcmpl-tool-5e2b8c4f1a7d9036",
                "function": {
                    "arguments": "{\"latitude\": 22.7196, \"longitude\": 75.8577}",
                    "name": "get_weather"
                },
                "type": "function"
            }],
            "reasoning_content": "The user wants the current weather in Indore. Indore, India is at about latitude 22.7196, longitude 75.8577, so I'll call get_weather."
        }
    }],
    "created": 1768409382,
    "model": "asi1",
    "object": "chat.completion",
    "usage": {
        "completion_tokens": 67,
        "prompt_tokens": 458,
        "total_tokens": 525,
        "completion_tokens_details": {
            "reasoning_tokens": 41
        }
    }
}
```

### Sample tools

The walkthrough below uses a real `get_weather` tool. Implement it in your own codebase:

```python
import requests

def get_weather(latitude, longitude):
    response = requests.get(f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&current=temperature_2m,wind_speed_10m&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m")
    data = response.json()
    return data['current']['temperature_2m']
```

## Complete tool execution cycle

The full cycle, using the weather example. Every step below builds on this setup:

```python
import os
import json
import requests

BASE_URL = "https://api.asi1.ai/v1"
headers = {
    "Authorization": f"Bearer {os.getenv('ASI_ONE_API_KEY')}",
    "Content-Type": "application/json",
}
```

### Step 1: Initial request with tools

Send the tool definition alongside the user's message:

```python
# Define the get_weather tool
get_weather_tool = {
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get current temperature for a given location (latitude and longitude).",
        "parameters": {
            "type": "object",
            "properties": {
                "latitude": {"type": "number"},
                "longitude": {"type": "number"}
            },
            "required": ["latitude", "longitude"]
        }
    }
}

# User's question
initial_message = {
    "role": "user",
    "content": "What's the current weather like in Indore right now?"
}

# First call to model
payload = {
    "model": "asi1",
    "messages": [initial_message],
    "tools": [get_weather_tool],
    "temperature": 0.7,
    "max_tokens": 1024
}

first_response = requests.post(
    f"{BASE_URL}/chat/completions",
    headers=headers,
    json=payload
)
```

### Step 2: Parse tool calls from the response

The model responds with a tool call to parse:

```python
first_response.raise_for_status()
first_response_json = first_response.json()

tool_calls = first_response_json["choices"][0]["message"].get("tool_calls", [])
messages_history = [
    initial_message,
    first_response_json["choices"][0]["message"]
]
```

### Step 3: Execute tools and format results

Execute the tool and format the result. `get_weather` is the function defined in
[Sample tools](#sample-tools) above:

```python
for tool_call in tool_calls:
    function_name = tool_call["function"]["name"]
    arguments = json.loads(tool_call["function"]["arguments"])

    if function_name == "get_weather":
        latitude = arguments["latitude"]
        longitude = arguments["longitude"]
        temperature = get_weather(latitude, longitude)
        result = {
            "temperature_celsius": temperature,
            "location": f"lat: {latitude}, lon: {longitude}"
        }
    else:
        result = {"error": f"Unknown tool: {function_name}"}

    # Tool result message
    tool_result_message = {
        "role": "tool",
        "tool_call_id": tool_call["id"],
        "content": json.dumps(result)
    }
    messages_history.append(tool_result_message)
```

### Step 4: Send results back to the model

Send the tool result back to the model:

```python
# Final call to model with tool results
final_payload = {
    "model": "asi1",
    "messages": messages_history,
    "temperature": 0.7,
    "max_tokens": 1024
}

final_response = requests.post(
    f"{BASE_URL}/chat/completions",
    headers=headers,
    json=final_payload
)
```

### Step 5: Receive the final answer

The model now answers, incorporating the tool result:

```python
final_response.raise_for_status()
final_response_json = final_response.json()

# Final result
print(final_response_json["choices"][0]["message"]["content"])
```

## Tool result handling

Here are key guidelines to ensure correct behavior and prevent common errors.

---

<details>
  <summary>
    **Preserving Tool Call IDs**
  </summary>

  Each tool call comes with a unique `id` that **must** be preserved when sending results back.

  ```python
  # Correct
  tool_result_message = {
      "role": "tool",
      "tool_call_id": tool_call["id"],  # Use the exact ID from the tool call
      "content": json.dumps(result),
  }

  # Incorrect - Don't make up IDs
  tool_result_message = {
      "role": "tool",
      "tool_call_id": "my_custom_id",  # This will cause an error
      "content": json.dumps(result),
  }
  ```
</details>

<details>
  <summary>
    **Message History Order**
  </summary>

  The message history **must** maintain this exact order:

  * Original user message
  * Assistant message with `tool_calls` (content should be null or empty)
  * Tool result messages (one for each tool_call, identified by `tool_call_id`)
</details>

<details>
  <summary>
    **Content Formatting**
  </summary>

  Tool results **must** be JSON-stringified within the `content` field.

  ```python
  # Correct
  tool_result_message = {"content": json.dumps({"key": "value"})}

  # Incorrect - Don't send raw objects
  tool_result_message = {"content": {"key": "value"}}  # This will cause an error
  ```
</details>

<details>
  <summary>
    **Error Handling**
  </summary>

  If a tool fails, send back a result message indicating the error.

  ```python
  try:
      result = execute_tool(function_name, arguments)
      content_to_send = json.dumps(result)
  except Exception as e:
      error_content = {
          "error": f"Tool execution failed: {str(e)}",
          "status": "failed",
      }
      content_to_send = json.dumps(error_content)

  tool_result_message = {
      "role": "tool",
      "tool_call_id": tool_call["id"],  # Still use the original tool call ID
      "content": content_to_send,
  }
  messages_history.append(tool_result_message)
  ```
</details>

## Tool definition

Functions are specified using the tools parameter in each API request, where each tool is described as a function object.

Each function is defined using a schema that tells the model what the function does and what input arguments it requires. The schema includes the following key fields:

* **`name`** (string) : A unique, descriptive identifier for the tool (e.g., `get_weather_forecast`, `send_email`). Use underscores or camelCase formatting. Avoid spaces or special characters.

* **`description`** (string) : A detailed explanation of what the tool does and when it should be used. Clear, specific descriptions improve the model's ability to use the function correctly.

* **`parameters`** (object) : Defines the input parameters the tool expects.

  * **`type`** (string) : Usually set to `"object"` to represent input parameters.

  * **`properties`** (object) : Lists each input parameter and its details:

  * **`type`** (string): The data type (e.g., `string`, `integer`, `boolean`, `array`).

  * **`description`** (string): A clear explanation of the parameter's purpose and expected format.
    *Example:* `"City and country, e.g., 'Paris, France'"`

  * **`enum`** *(optional)*: An array of allowed values, useful when inputs must be restricted.
    *Example:* `"enum": ["celsius", "fahrenheit"]`

  * **`required`** (array of strings) : Lists the parameter names that must be included when calling the function.

The `get_weather` definition used throughout this page is a minimal example of
that shape. Descriptions are the part worth spending time on: the model chooses
which tool to call, and what to pass it, from your prose alone.

## Additional configurations

ASI:One provides several options to control how and when tools are called, as well as how strictly the model adheres to your function schemas.

### Tool choice

By default, the model determines when and how many tools to use. You can control this behavior with the `tool_choice` parameter:

* **Auto (default):** The model may call zero, one, or multiple functions.

```json
"tool_choice": "auto"
```

* **Required:** The model must call at least one function.

```json
"tool_choice": "required"
```

* **Forced Function:** Force the model to call a specific function.

```json
"tool_choice": {
  "type": "function",
  "function": { "name": "get_weather" }
}
```

* **None:** Prevent the model from calling any functions.

```json
"tool_choice": "none"
```

### Parallel tool calling

By default, the model may call multiple functions in a single turn. To restrict it
to one at a time, send `parallel_tool_calls`, which both endpoints take:

```json
"parallel_tool_calls": false
```

> **Info**
>
> Handle multiple `tool_calls` in one response whichever endpoint you use. A model
> that can gather several pieces of information at once will usually do so, and
> that is normally what you want.

### Strict mode

Setting `strict` to `true` makes the model follow your schema exactly rather than
treating it as a strong suggestion. Enable it unless you have a reason not to.

Two things are required alongside it:

1. `additionalProperties` must be `false` on every object inside `parameters`.
2. Every key in `properties` must be listed in `required`.

Here is the same `get_weather` tool with strict mode on, plus an optional `units`
parameter to show what that second requirement means in practice:

```json
{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "Get current temperature for a given location (latitude and longitude).",
    "strict": true,
    "parameters": {
      "type": "object",
      "properties": {
        "latitude": { "type": "number" },
        "longitude": { "type": "number" },
        "units": {
          "type": ["string", "null"],
          "description": "Units to report the temperature in, celsius or fahrenheit. Null for the default."
        }
      },
      "required": ["latitude", "longitude", "units"],
      "additionalProperties": false
    }
  }
}
```

Strict mode leaves no room for a parameter the model may simply omit. Widen an
optional one to accept `"null"`, list it in `required` like any other, and treat
null as "not supplied" in your own code.

## On the Responses API

Everything above uses `/v1/chat/completions`. Tool calling works the same way on
`/v1/responses`, with three differences:

* **Tools may use the flat shape.** `/v1/responses` accepts OpenAI's Responses
  form, where `name`, `description` and `parameters` sit at the top level of the
  tool object instead of inside a nested `function` object. The nested Chat
  Completions shape is accepted too, so an existing tool definition keeps working.

  ```json
  {
    "type": "function",
    "name": "get_weather",
    "description": "Get current temperature for a given location.",
    "parameters": {
      "type": "object",
      "properties": {
        "latitude": { "type": "number" },
        "longitude": { "type": "number" }
      },
      "required": ["latitude", "longitude"]
    }
  }
  ```

* **Function tools and the hosted `web_search` tool are supported.** Any other
  tool type is rejected with a `400`. See the [Web Search guide](/documentation/build-with-asi-one/web-search) for the hosted tool.

`tool_choice`, `strict` and `parallel_tool_calls` behave identically on both
endpoints.

## Next steps

1. **[Structured Data](/documentation/build-with-asi-one/structured-data)** - Make the model's reply conform to a JSON schema
2. **[Chat Completions API](/documentation/build-with-asi-one/chat-completions#streaming)** - How tool calls arrive when you stream the reply
3. **[Planner Mode](/documentation/build-with-asi-one/planner)** - Let ASI:One plan and run multi-step work against agents and tools
4. **[Using ASI:One with LangChain](/documentation/tutorials/langchain)** - Bind tools with `@tool` instead of raw JSON schemas