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

# Tool calling and function execution

> Enable LLMs to call external functions with structured parameters. Build AI agents that interact with APIs, databases, and external services.

**Use Cases**

* Connecting models to live data (databases, calendars, internal APIs) without prompt hacks.
* Agents that take actions on behalf of users: create tickets, send emails, run queries.
* Multi-step workflows where the model decides which tools to invoke and in what order.
* Replacing brittle regex parsing with structured function calls for data extraction.

***

## Quick Start

Enable AI models to call external functions with structured parameters.

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://router.toqan.ai/eu/v3/router/responses \
  -H "Authorization: Bearer $TOQAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4",
    "input": "What is the weather in NYC?",
    "tools": [{
      "type": "function",
      "name": "get_weather",
      "description": "Get current weather for a location",
      "parameters": {
        "type": "object",
        "properties": {
          "location": { "type": "string", "description": "City and state" },
          "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
        },
        "required": ["location"]
      }
    }],
    "tool_choice": "auto"
  }'
```

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.TOQAN_API_KEY,
  baseURL: "https://router.toqan.ai/eu/v3/router",
});

async function getWeather(location: string, unit = "celsius") {
  // Your implementation here - fetch from a weather API, etc.
  return { location, temperature: 22, unit, conditions: "sunny" };
}

const tools = [
  {
    type: "function" as const,
    name: "get_weather",
    description: "Get current weather for a location",
    parameters: {
      type: "object",
      properties: {
        location: { type: "string", description: "City and state" },
        unit: { type: "string", enum: ["celsius", "fahrenheit"] },
      },
      required: ["location"],
    },
  },
];

const response = await client.responses.create({
  model: "openai/gpt-5.4",
  input: "What's the weather in NYC?",
  tools,
  tool_choice: "auto",
});

const toolCall = response.output.find((item) => item.type === "function_call");
if (toolCall && toolCall.type === "function_call") {
  const args = JSON.parse(toolCall.arguments);
  const result = await getWeather(args.location, args.unit);
  const callId = toolCall.call_id;

  // Pattern: previous_response_id - the router maintains conversation state server-side
  const finalResponse = await client.responses.create({
    model: "openai/gpt-5.4",
    previous_response_id: response.id,
    input: [{
      type: "function_call_output",
      call_id: callId,
      output: JSON.stringify(result),
    }],
  });

  console.log(finalResponse.output_text);
}
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from openai import OpenAI
import json
import os

client = OpenAI(
    api_key=os.environ.get("TOQAN_API_KEY"),
    base_url="https://router.toqan.ai/eu/v3/router",
)

def get_weather(location: str, unit: str = "celsius") -> dict:
    # Your implementation here - fetch from a weather API, etc.
    return {"location": location, "temperature": 22, "unit": unit, "conditions": "sunny"}

tools = [
    {
        "type": "function",
        "name": "get_weather",
        "description": "Get current weather for a location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {"type": "string", "description": "City and state"},
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
            },
            "required": ["location"],
        },
    }
]

response = client.responses.create(
    model="openai/gpt-5.4",
    input="What's the weather in NYC?",
    tools=tools,
    tool_choice="auto",
)

tool_call = next((item for item in response.output if item.type == "function_call"), None)
if tool_call:
    args = json.loads(tool_call.arguments)
    result = get_weather(args["location"], args.get("unit", "celsius"))

    # Pattern: previous_response_id - the router maintains conversation state server-side
    final_response = client.responses.create(
        model="openai/gpt-5.4",
        previous_response_id=response.id,
        input=[{
            "type": "function_call_output",
            "call_id": tool_call.call_id,
            "output": json.dumps(result),
        }],
    )

    print(final_response.output_text)
```

```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://router.toqan.ai/eu/v3/router/chat/completions \
  -H "Authorization: Bearer $TOQAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4",
    "messages": [{"role": "user", "content": "What is the weather in NYC?"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get current weather for a location",
        "parameters": {
          "type": "object",
          "properties": {
            "location": { "type": "string", "description": "City and state" },
            "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
          },
          "required": ["location"]
        }
      }
    }],
    "tool_choice": "auto"
  }'
```

```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.TOQAN_API_KEY,
  baseURL: "https://router.toqan.ai/eu/v3/router",
});

const tools = [
  {
    type: "function" as const,
    function: {
      name: "get_weather",
      description: "Get current weather for a location",
      parameters: {
        type: "object",
        properties: {
          location: { type: "string", description: "City and state" },
          unit: { type: "string", enum: ["celsius", "fahrenheit"] },
        },
        required: ["location"],
      },
    },
  },
];

async function getWeather(location: string, unit = "celsius") {
  // Your implementation here - fetch from a weather API, etc.
  return { location, temperature: 22, unit, conditions: "sunny" };
}

const response = await client.chat.completions.create({
  model: "openai/gpt-5.4",
  messages: [{ role: "user", content: "What's the weather in NYC?" }],
  tools,
  tool_choice: "auto",
});

if (response.choices[0].message.tool_calls) {
  const toolCall = response.choices[0].message.tool_calls[0];
  const args = JSON.parse(toolCall.function.arguments);
  const result = await getWeather(args.location, args.unit);

  const finalResponse = await client.chat.completions.create({
    model: "openai/gpt-5.4",
    messages: [
      { role: "user", content: "What's the weather in NYC?" },
      response.choices[0].message,
      {
        role: "tool",
        tool_call_id: toolCall.id,
        content: JSON.stringify(result),
      },
    ],
  });

  console.log(finalResponse.choices[0].message.content);
}
```

```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
from openai import OpenAI
import json
import os

client = OpenAI(
    api_key=os.environ.get("TOQAN_API_KEY"),
    base_url="https://router.toqan.ai/eu/v3/router",
)

def get_weather(location: str, unit: str = "celsius") -> dict:
    # Your implementation here - fetch from a weather API, etc.
    return {"location": location, "temperature": 22, "unit": unit, "conditions": "sunny"}

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {"type": "string", "description": "City and state"},
                    "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                },
                "required": ["location"],
            },
        },
    }
]

messages = [{"role": "user", "content": "What's the weather in NYC?"}]

response = client.chat.completions.create(
    model="openai/gpt-5.4",
    messages=messages,
    tools=tools,
    tool_choice="auto",
)

if response.choices[0].message.tool_calls:
    tool_call = response.choices[0].message.tool_calls[0]
    args = json.loads(tool_call.function.arguments)
    result = get_weather(args["location"], args.get("unit", "celsius"))

    messages.append(response.choices[0].message)
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": json.dumps(result),
    })

    final_response = client.chat.completions.create(
        model="openai/gpt-5.4",
        messages=messages,
    )

    print(final_response.choices[0].message.content)
```

## Configuration

### Tool Definition

**Responses API** (`/v3/router/responses`): flat shape:

| Parameter | Type | Required | Description |
| - | - | - | - |
| `type` | `"function"` | Yes | Tool type |
| `name` | string | Yes | Function identifier |
| `description` | string | Yes | What the function does |
| `parameters` | object | Yes | JSON Schema for parameters |

**Chat Completions** (`/v3/router/chat/completions`): nested `function` wrapper:

| Parameter | Type | Required | Description |
| - | - | - | - |
| `type` | `"function"` | Yes | Tool type |
| `function.name` | string | Yes | Function identifier |
| `function.description` | string | Yes | What the function does |
| `function.parameters` | object | Yes | JSON Schema for parameters |

### Tool Choice Options

| Value | Behavior |
| - | - |
| `"auto"` | Model decides when to use tools |
| `"none"` | Disable tool usage |
| `"required"` | Force tool usage |
| `{type: "function", function: {name: "tool_name"}}` | Force specific tool |

### Tool Message Format

> [!NOTE]
>
> This format applies to the Chat Completions endpoint (`/v3/router/chat/completions`). On the Responses API, tool results use `type: "function_call_output"`, `call_id`, and `output` instead.

When providing tool results back to the model, use the `tool` role:

| Parameter | Type | Required | Description |
| - | - | - | - |
| `role` | `"tool"` | Yes | Message role for tool results |
| `tool_call_id` | string \| null | Yes | ID of the tool call being responded to |
| `content` | string | Yes | JSON-stringified result of the tool execution |

> [!NOTE]
>
> The `tool_call_id` can be `null` in certain scenarios, such as when tool results are being provided without a corresponding tool call from the model, or when working with providers that don't require tool call IDs.

## Code examples

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://router.toqan.ai/eu/v3/router/responses \
  -H "Authorization: Bearer $TOQAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4",
    "input": "What is the weather like in San Francisco?",
    "tools": [
      {
        "type": "function",
        "name": "get_weather",
        "description": "Get the current weather for a location",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "The city and state, e.g. San Francisco, CA"
            },
            "unit": {
              "type": "string",
              "enum": ["celsius", "fahrenheit"],
              "description": "The temperature unit"
            }
          },
          "required": ["location"]
        }
      }
    ],
    "tool_choice": "auto"
  }'
```

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.TOQAN_API_KEY,
  baseURL: "https://router.toqan.ai/eu/v3/router",
});

const tools = [
  {
    type: "function" as const,
    name: "get_weather",
    description: "Get the current weather for a location",
    parameters: {
      type: "object",
      properties: {
        location: {
          type: "string",
          description: "The city and state, e.g. San Francisco, CA",
        },
        unit: {
          type: "string",
          enum: ["celsius", "fahrenheit"],
          description: "The temperature unit",
        },
      },
      required: ["location"],
    },
  },
];

const response = await client.responses.create({
  model: "openai/gpt-5.4",
  input: "What's the weather like in San Francisco?",
  tools,
  tool_choice: "auto",
});

const toolCall = response.output.find((item) => item.type === "function_call");
if (toolCall && toolCall.type === "function_call") {
  const args = JSON.parse(toolCall.arguments);

  const weatherResult = {
    temperature: 72,
    unit: "fahrenheit",
    description: "Sunny with light clouds",
  };

  // Pattern: spread response.output - the full conversation history is sent client-side
  const finalResponse = await client.responses.create({
    model: "openai/gpt-5.4",
    input: [
      ...response.output,
      {
        type: "function_call_output",
        call_id: toolCall.call_id,
        output: JSON.stringify(weatherResult),
      },
    ],
  });

  console.log(finalResponse.output_text);
}
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from openai import OpenAI
import json
import os

client = OpenAI(
    api_key=os.environ.get("TOQAN_API_KEY"),
    base_url="https://router.toqan.ai/eu/v3/router",
)

tools = [
    {
        "type": "function",
        "name": "get_weather",
        "description": "Get the current weather for a location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit",
                },
            },
            "required": ["location"],
        },
    }
]

response = client.responses.create(
    model="openai/gpt-5.4",
    input="What's the weather like in San Francisco?",
    tools=tools,
    tool_choice="auto",
)

tool_call = next((item for item in response.output if item.type == "function_call"), None)
if tool_call:
    arguments = json.loads(tool_call.arguments)

    weather_result = {
        "temperature": 72,
        "unit": "fahrenheit",
        "description": "Sunny with light clouds",
    }

    final_response = client.responses.create(
        model="openai/gpt-5.4",
        input=[
            *response.output,
            {
                "type": "function_call_output",
                "call_id": tool_call.call_id,
                "output": json.dumps(weather_result),
            },
        ],
    )

    print(final_response.output_text)
```

```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://router.toqan.ai/eu/v3/router/chat/completions \
  -H "Authorization: Bearer $TOQAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4",
    "messages": [
      {
        "role": "user",
        "content": "What is the weather like in San Francisco?"
      }
    ],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "get_weather",
          "description": "Get the current weather for a location",
          "parameters": {
            "type": "object",
            "properties": {
              "location": {
                "type": "string",
                "description": "The city and state, e.g. San Francisco, CA"
              },
              "unit": {
                "type": "string",
                "enum": ["celsius", "fahrenheit"],
                "description": "The temperature unit"
              }
            },
            "required": ["location"]
          }
        }
      }
    ],
    "tool_choice": "auto"
  }'
```

```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.TOQAN_API_KEY,
  baseURL: "https://router.toqan.ai/eu/v3/router",
});

const tools = [
  {
    type: "function" as const,
    function: {
      name: "get_weather",
      description: "Get the current weather for a location",
      parameters: {
        type: "object",
        properties: {
          location: {
            type: "string",
            description: "The city and state, e.g. San Francisco, CA",
          },
          unit: {
            type: "string",
            enum: ["celsius", "fahrenheit"],
            description: "The temperature unit",
          },
        },
        required: ["location"],
      },
    },
  },
];

const messages = [
  { role: "user" as const, content: "What's the weather like in San Francisco?" },
];

const response = await client.chat.completions.create({
  model: "openai/gpt-5.4",
  messages,
  tools,
  tool_choice: "auto",
});

if (response.choices[0].message.tool_calls) {
  const toolCall = response.choices[0].message.tool_calls[0];
  const args = JSON.parse(toolCall.function.arguments);

  const weatherResult = {
    temperature: 72,
    unit: "fahrenheit",
    description: "Sunny with light clouds",
  };

  messages.push(response.choices[0].message);
  messages.push({
    role: "tool" as const,
    tool_call_id: toolCall.id,
    content: JSON.stringify(weatherResult),
  });

  const finalResponse = await client.chat.completions.create({
    model: "openai/gpt-5.4",
    messages,
  });

  console.log(finalResponse.choices[0].message.content);
}
```

```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
from openai import OpenAI
import json
import os

client = OpenAI(
    api_key=os.environ.get("TOQAN_API_KEY"),
    base_url="https://router.toqan.ai/eu/v3/router",
)

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "The temperature unit",
                    },
                },
                "required": ["location"],
            },
        },
    }
]

messages = [{"role": "user", "content": "What's the weather like in San Francisco?"}]

response = client.chat.completions.create(
    model="openai/gpt-5.4",
    messages=messages,
    tools=tools,
    tool_choice="auto",
)

if response.choices[0].message.tool_calls:
    tool_call = response.choices[0].message.tool_calls[0]
    arguments = json.loads(tool_call.function.arguments)

    weather_result = {
        "temperature": 72,
        "unit": "fahrenheit",
        "description": "Sunny with light clouds",
    }

    messages.append(response.choices[0].message)
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": json.dumps(weather_result),
    })

    final_response = client.chat.completions.create(
        model="openai/gpt-5.4",
        messages=messages,
    )

    print(final_response.choices[0].message.content)
```

## Function Execution Patterns

### Basic Tool Handler

A registry that maps tool names to handler functions, replacing per-call switch statements with a single dispatch path.

```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
class ToolHandler {
  constructor() {
    this.tools = new Map();
  }

  register(name, func, schema) {
    this.tools.set(name, { func, schema });
  }

  async execute(toolCall) {
    const tool = this.tools.get(toolCall.function.name);
    if (!tool) throw new Error(`Unknown tool: ${toolCall.function.name}`);

    const args = JSON.parse(toolCall.function.arguments);
    const result = await tool.func(args);

    return {
      role: "tool",
      tool_call_id: toolCall.id,
      content: JSON.stringify(result),
    };
  }
}

// Stubs - replace with your actual implementations
const weatherSchema = { type: "object", properties: { location: { type: "string" } }, required: ["location"] };
const searchSchema = { type: "object", properties: { query: { type: "string" } }, required: ["query"] };
const getWeatherAPI = async ({ location }) => ({ temperature: 72, condition: "sunny", location });
const searchWebAPI = async ({ query }) => ({ results: [`Result for: ${query}`] });
const response = { choices: [{ message: { tool_calls: [] } }] };

const handler = new ToolHandler();
handler.register("get_weather", getWeatherAPI, weatherSchema);
handler.register("search_web", searchWebAPI, searchSchema);

// Execute tool calls
const toolResults = await Promise.all(
  response.choices[0].message.tool_calls.map((call) => handler.execute(call)),
);
```

```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
import json
import asyncio

async def get_weather_api(args: dict) -> dict: return {"temperature": 72, "condition": "sunny", "location": args["location"]}  # replace with your weather API
async def search_web_api(args: dict) -> dict: return {"results": [f"Result for: {args['query']}"]}  # replace with your search API

class ToolHandler:
    def __init__(self):
        self._tools = {}

    def register(self, name: str, func):
        self._tools[name] = func

    async def execute(self, tool_call) -> dict:
        func = self._tools.get(tool_call.function.name)
        if not func:
            raise ValueError(f"Unknown tool: {tool_call.function.name}")
        args = json.loads(tool_call.function.arguments)
        result = await func(args)
        return {"role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result)}

handler = ToolHandler()
handler.register("get_weather", get_weather_api)
handler.register("search_web", search_web_api)

async def execute_all_tools(message):
    return await asyncio.gather(*[handler.execute(call) for call in message.tool_calls])
```

### Parallel Tool Execution

When the model returns multiple tool calls in one turn, execute them concurrently to reduce latency.

```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
async function getWeatherAsync(args: Record<string, unknown>) { return { temperature: 72, condition: "sunny" }; } // replace with your weather API
async function searchProductsAsync(args: Record<string, unknown>) { return { products: [] as unknown[] }; } // replace with your product search
async function checkInventoryAsync(args: Record<string, unknown>) { return { inStock: true }; } // replace with your inventory API

// Replace with your actual response and messages array
const response = { choices: [{ message: { role: "assistant" as const, content: null as string | null, tool_calls: [] as Array<{ function: { name: string; arguments: string }; id: string }> } }] }; // e.g., client.chat.completions.create(...)
let messages: Array<{ role: string; tool_call_id?: string; content: string | null }> = [];

const dispatch: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
  get_weather: getWeatherAsync,
  search_products: searchProductsAsync,
  check_inventory: checkInventoryAsync,
};

async function executeToolsParallel(
  toolCalls: Array<{ function: { name: string; arguments: string }; id: string }>,
) {
  return Promise.all(
    toolCalls.map(async (call) => {
      const args = JSON.parse(call.function.arguments) as Record<string, unknown>;
      const fn = dispatch[call.function.name] ?? (() => Promise.resolve({ error: `Unknown: ${call.function.name}` }));
      const result = await fn(args);
      return { role: "tool" as const, tool_call_id: call.id, content: JSON.stringify(result) };
    }),
  );
}

if (response.choices[0].message.tool_calls) {
  messages.push(response.choices[0].message);
  const toolResults = await executeToolsParallel(response.choices[0].message.tool_calls);
  messages = [...messages, ...toolResults];
}
```

```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
import asyncio
import json

async def get_weather_async(args): return {"temperature": 72, "condition": "sunny"}  # replace with your weather API
async def search_products_async(args): return {"products": []}  # replace with your product search
async def check_inventory_async(args): return {"in_stock": True}  # replace with your inventory API

# Replace these with your actual API call and conversation history
response = None  # e.g. client.chat.completions.create(model=..., messages=messages, tools=[...])
messages: list = []

async def execute_tools_parallel(tool_calls):
    async def execute_single_tool(tool_call):
        function_name = tool_call.function.name
        arguments = json.loads(tool_call.function.arguments)

        # Route to appropriate function
        if function_name == "get_weather":
            result = await get_weather_async(arguments)
        elif function_name == "search_products":
            result = await search_products_async(arguments)
        elif function_name == "check_inventory":
            result = await check_inventory_async(arguments)
        else:
            result = {"error": f"Unknown function: {function_name}"}

        return {
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result)
        }

    # Execute all tools concurrently
    results = await asyncio.gather(
        *[execute_single_tool(call) for call in tool_calls]
    )

    return results

# Usage
if response.choices[0].message.tool_calls:
    messages.append(response.choices[0].message)
    tool_results = await execute_tools_parallel(
        response.choices[0].message.tool_calls
    )

    # Add to conversation
    messages.extend(tool_results)
```

## Advanced Use Cases

### Database Integration

Expose SQL access as a tool so the model can query data directly. Always sanitize queries before execution.

```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
const sanitizeSql = (q: string) => q; // replace with your SQL sanitizer
const getDbConnection = () => null as unknown as { execute: (q: string) => Promise<{ fetchAll: () => unknown[] }> }; // replace with your DB client

const db = getDbConnection();

const tools = [
  {
    type: "function" as const,
    function: {
      name: "query_database",
      description: "Query the customer database",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", description: "SQL query to execute" },
          limit: { type: "integer", description: "Maximum number of results" },
        },
        required: ["query"],
      },
    },
  },
];

async function queryDatabase(args: { query: string; limit?: number }) {
  const query = sanitizeSql(args.query);
  const limit = args.limit ?? 10;
  const results = await db.execute(`${query} LIMIT ${limit}`); // limit is integer-typed in the schema, safe to interpolate directly
  return { results: results.fetchAll() };
}
```

```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
sanitize_sql = lambda q: q  # replace with your SQL sanitizer

def get_db_connection():
    pass  # replace with your database client, e.g. from myapp.db import get_db_connection

db = get_db_connection()

tools = [
    {
        "type": "function",
        "function": {
            "name": "query_database",
            "description": "Query the customer database",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "SQL query to execute"
                    },
                    "limit": {
                        "type": "integer",
                        "description": "Maximum number of results"
                    }
                },
                "required": ["query"]
            }
        }
    }
]

async def query_database(args):
    query = sanitize_sql(args["query"])
    limit = args.get("limit", 10)

    results = await db.execute(f"{query} LIMIT {limit}")
    return {"results": results.fetchall()}
```

### API Integration

Expose external service actions (email, calendar, notifications) as tools the model can invoke during a conversation.

```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
const apiTools = [
  {
    type: "function",
    function: {
      name: "send_email",
      description: "Send an email to a recipient",
      parameters: {
        type: "object",
        properties: {
          to: { type: "string", description: "Email address" },
          subject: { type: "string", description: "Email subject" },
          body: { type: "string", description: "Email content" },
        },
        required: ["to", "subject", "body"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_calendar_event",
      description: "Create a calendar event",
      parameters: {
        type: "object",
        properties: {
          title: { type: "string" },
          start_time: { type: "string", format: "date-time" },
          duration: { type: "integer", description: "Duration in minutes" },
          attendees: { type: "array", items: { type: "string" } },
        },
        required: ["title", "start_time"],
      },
    },
  },
];

const emailAPI = { send: async (args: Record<string, unknown>) => ({ messageId: "msg_001" }) }; // replace with your email client
const calendarAPI = { createEvent: async (args: Record<string, unknown>) => ({ eventId: "evt_001" }) }; // replace with your calendar client

const executeApiTool = async (toolCall) => {
  const { name } = toolCall.function;
  const args = JSON.parse(toolCall.function.arguments);

  switch (name) {
    case "send_email":
      return await emailAPI.send(args);
    case "create_calendar_event":
      return await calendarAPI.createEvent(args);
    default:
      throw new Error(`Unknown API tool: ${name}`);
  }
};
```

```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
import json

# Replace with your actual API clients
class EmailAPI:
    async def send(self, args: dict) -> dict: return {"message_id": "msg_001"}

class CalendarAPI:
    async def create_event(self, args: dict) -> dict: return {"event_id": "evt_001"}

email_api = EmailAPI()
calendar_api = CalendarAPI()

api_tools = [
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": "Send an email to a recipient",
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {"type": "string", "description": "Email address"},
                    "subject": {"type": "string", "description": "Email subject"},
                    "body": {"type": "string", "description": "Email content"},
                },
                "required": ["to", "subject", "body"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "create_calendar_event",
            "description": "Create a calendar event",
            "parameters": {
                "type": "object",
                "properties": {
                    "title": {"type": "string"},
                    "start_time": {"type": "string", "format": "date-time"},
                    "duration": {"type": "integer", "description": "Duration in minutes"},
                    "attendees": {"type": "array", "items": {"type": "string"}},
                },
                "required": ["title", "start_time"],
            },
        },
    },
]

async def execute_api_tool(tool_call) -> dict:
    args = json.loads(tool_call.function.arguments)
    match tool_call.function.name:
        case "send_email":
            return await email_api.send(args)
        case "create_calendar_event":
            return await calendar_api.create_event(args)
        case _:
            raise ValueError(f"Unknown API tool: {tool_call.function.name}")
```

### Multi-Step Workflows

Run the model in a loop until it produces a final text response, enabling multi-step agent behavior without streaming.

```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { OpenAI } from "openai";
import type { ChatCompletionMessageParam, ChatCompletionTool } from "openai/resources";

const client = new OpenAI({
  apiKey: process.env.TOQAN_API_KEY,
  baseURL: "https://router.toqan.ai/eu/v3/router",
});

class WorkflowEngine {
  private tools: Record<string, { func: (args: Record<string, unknown>) => Promise<unknown>; schema: ChatCompletionTool }> = {};

  register(name: string, func: (args: Record<string, unknown>) => Promise<unknown>, schema: ChatCompletionTool) {
    this.tools[name] = { func, schema };
  }

  async executeWorkflow(initialPrompt: string, maxSteps = 10): Promise<string> {
    const conversation: ChatCompletionMessageParam[] = [{ role: "user", content: initialPrompt }];

    for (let step = 0; step < maxSteps; step++) {
      const response = await client.chat.completions.create({
        model: "openai/gpt-5.4",
        messages: conversation,
        tools: Object.values(this.tools).map((t) => t.schema),
        tool_choice: "auto",
      });

      conversation.push(response.choices[0].message);

      if (!response.choices[0].message.tool_calls) {
        return response.choices[0].message.content ?? "";
      }

      for (const call of response.choices[0].message.tool_calls) {
        conversation.push(await this.executeTool(call));
      }
    }
    return "Workflow exceeded maximum steps";
  }

  private async executeTool(toolCall: { function: { name: string; arguments: string }; id: string }): Promise<ChatCompletionMessageParam> {
    const args = JSON.parse(toolCall.function.arguments) as Record<string, unknown>;
    const tool = this.tools[toolCall.function.name];
    const result = tool ? await tool.func(args) : { error: `Unknown tool: ${toolCall.function.name}` };
    return { role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(result) };
  }
}
```

```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
from openai import AsyncOpenAI
import os
import json

client = AsyncOpenAI(
    api_key=os.environ.get("TOQAN_API_KEY"),
    base_url="https://router.toqan.ai/eu/v3/router",
)

class WorkflowEngine:
    def __init__(self):
        self.tools = {}
        self.conversation = []

    def register_tool(self, name, func, schema):
        self.tools[name] = {"func": func, "schema": schema}

    async def execute_workflow(self, initial_prompt, max_steps=10):
        self.conversation = [{"role": "user", "content": initial_prompt}]

        for step in range(max_steps):
            response = await client.chat.completions.create(
                model="openai/gpt-5.4",
                messages=self.conversation,
                tools=[v["schema"] for v in self.tools.values()],
                tool_choice="auto"
            )

            self.conversation.append(response.choices[0].message)

            # Check if tools need to be executed
            if response.choices[0].message.tool_calls:
                for tool_call in response.choices[0].message.tool_calls:
                    result = await self.execute_tool(tool_call)
                    self.conversation.append(result)
            else:
                # No tools called, workflow complete
                return response.choices[0].message.content

        return "Workflow exceeded maximum steps"

    async def execute_tool(self, tool_call):
        tool_name = tool_call.function.name
        arguments = json.loads(tool_call.function.arguments)

        if tool_name in self.tools:
            result = await self.tools[tool_name]["func"](arguments)
        else:
            result = {"error": f"Unknown tool: {tool_name}"}

        return {
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result)
        }
```

## Error Handling

Return structured error objects in tool outputs so the model can report failures or retry with corrected arguments.

```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
const validateArgs = (name: string, args: Record<string, unknown>) => ({ valid: true, errors: [] as string[] }); // replace with your validator
const executeFunction = async (name: string, args: Record<string, unknown>): Promise<unknown> => ({}); // replace with your dispatcher

const safeToolExecution = async (toolCall) => {
  try {
    const args = JSON.parse(toolCall.function.arguments);

    // Validate arguments
    const validation = validateArgs(toolCall.function.name, args);
    if (!validation.valid) {
      return {
        role: "tool",
        tool_call_id: toolCall.id,
        content: JSON.stringify({
          error: "Invalid arguments",
          details: validation.errors,
        }),
      };
    }

    // Execute with timeout
    const result = await Promise.race([
      executeFunction(toolCall.function.name, args),
      new Promise((_, reject) =>
        setTimeout(() => reject(new Error("Tool execution timeout")), 30000),
      ),
    ]);

    return {
      role: "tool",
      tool_call_id: toolCall.id,
      content: JSON.stringify(result),
    };
  } catch (error) {
    console.error(`Tool execution failed: ${error.message}`);

    return {
      role: "tool",
      tool_call_id: toolCall.id,
      content: JSON.stringify({
        error: "Tool execution failed",
        message: error.message,
      }),
    };
  }
};
```

```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
import json
import asyncio

def validate_args(name: str, args: dict) -> dict: return {"valid": True, "errors": []}  # replace with your validator
async def execute_function(name: str, args: dict): return {}  # replace with your dispatcher

async def safe_tool_execution(tool_call) -> dict:
    try:
        args = json.loads(tool_call.function.arguments)

        validation = validate_args(tool_call.function.name, args)
        if not validation["valid"]:
            return {
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps({"error": "Invalid arguments", "details": validation["errors"]}),
            }

        result = await asyncio.wait_for(
            execute_function(tool_call.function.name, args),
            timeout=30,
        )
        return {"role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result)}
    except Exception as e:
        return {
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps({"error": "Tool execution failed", "message": str(e)}),
        }
```

## Best Practices

### Tool design

* Use clear, descriptive function names.
* Provide detailed parameter descriptions.
* Include examples in descriptions.
* Make tools idempotent when possible.

### Schema design

```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "object",
  "properties": {
    "location": {
      "type": "string",
      "description": "City and state (e.g., 'San Francisco, CA')",
      "examples": ["New York, NY", "London, UK"]
    },
    "units": {
      "type": "string",
      "enum": ["metric", "imperial"],
      "description": "Temperature unit system",
      "default": "metric"
    }
  },
  "required": ["location"]
}
```

### Security considerations

* Never expose destructive operations directly.
* Validate all inputs thoroughly.
* Use allowlists for sensitive operations.
* Implement proper authentication.
* Log all tool executions.

## Troubleshooting

**Tool not being called**

* Check tool descriptions are clear.
* Verify parameter schemas are correct.
* Ensure tool\_choice is set appropriately.
* Try more explicit prompts.

**Invalid arguments**

* Validate JSON Schema thoroughly.
* Add parameter examples.
* Check required fields are marked.
* Simplify complex parameter structures.

**Execution failures**

* Implement proper error handling.
* Add timeout protection.
* Validate inputs before execution.
* Return structured error messages.

## Limitations

| Limitation | Impact | Workaround |
| - | - | - |
| **Tool limit** | Max \~20 tools per request | Group related functions |
| **Parameter size** | Large schemas may fail | Simplify parameter structure |
| **Execution time** | Tools block response | Use async patterns |
| **Error propagation** | Failures can break workflow | Implement error recovery |
| **Model differences** | Varying tool calling quality | Test across models |

## Performance Optimization

### Tool caching

Cache results from read-only tools to avoid redundant external calls within a session.

```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
class CachedToolExecutor {
  constructor() {
    this.cache = new Map();
    this.cacheTTL = 300000; // 5 minutes
  }

  getCacheKey(toolCall) {
    return `${toolCall.function.name}:${toolCall.function.arguments}`;
  }

  async executeFunction(toolCall) {
    throw new Error("executeFunction must be implemented in a subclass");
  }

  async execute(toolCall) {
    const key = this.getCacheKey(toolCall);
    const cached = this.cache.get(key);

    if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
      return cached.result;
    }

    const result = await this.executeFunction(toolCall);
    this.cache.set(key, { result, timestamp: Date.now() });

    return result;
  }
}
```

```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
import time
import json

class CachedToolExecutor:
    def __init__(self, cache_ttl: int = 300):
        self._cache: dict = {}
        self._cache_ttl = cache_ttl

    def _cache_key(self, tool_call) -> str:
        return f"{tool_call.function.name}:{tool_call.function.arguments}"

    async def execute_function(self, tool_call) -> dict:
        raise NotImplementedError("Implement in subclass")

    async def execute(self, tool_call) -> dict:
        key = self._cache_key(tool_call)
        entry = self._cache.get(key)
        if entry and (time.time() - entry["timestamp"]) < self._cache_ttl:
            return entry["result"]
        result = await self.execute_function(tool_call)
        self._cache[key] = {"result": result, "timestamp": time.time()}
        return result
```

### Batch operations

Accept arrays of inputs in a single tool to reduce the number of model turns required for bulk operations.

```typescript TypeScript (Responses) theme={"theme":{"light":"github-light","dark":"github-dark"}}
const getWeather = (location: string) => ({ temperature: 72, condition: "sunny", location }); // replace with your weather API

const getWeatherBatch = (locations: string[]) =>
  Object.fromEntries(locations.map((loc) => [loc, getWeather(loc)]));

const getWeatherBatchTool = {
  type: "function" as const,
  name: "get_weather_batch",
  description: "Get weather for multiple locations in a single call",
  parameters: {
    type: "object",
    properties: {
      locations: { type: "array", items: { type: "string" }, description: "List of city names" },
    },
    required: ["locations"],
  },
};
```

```python Python (Responses) theme={"theme":{"light":"github-light","dark":"github-dark"}}
# Pattern fragment: add client setup and get_weather stub before using
# Instead of multiple individual calls
def get_weather_batch(locations):
    return {loc: get_weather(loc) for loc in locations}

# Tool that accepts multiple inputs
get_weather_batch_tool = {
    "type": "function",
    "name": "get_weather_batch",
    "description": "Get weather for multiple locations in a single call",
    "parameters": {
        "type": "object",
        "properties": {
            "locations": {
                "type": "array",
                "items": {"type": "string"}
            }
        },
        "required": ["locations"]
    }
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.