# How to Handle Tool Calls with aisuite: A Complete Guide to Tool Registration and Execution

> Learn to handle tool calls with aisuite. This guide covers registering Python functions as LLM tools, exporting specs, and executing calls with validation and tracing.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: how-to-guide
- Published: 2026-06-16

---

**aisuite provides a unified framework to register Python functions as LLM tools, export OpenAI-compatible specifications, and execute tool calls with automatic validation, policy enforcement, and tracing via the `Tools` class in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py).**

The `aisuite` library by Andrew Ng offers a robust abstraction for integrating external utilities with large language models. To handle tool calls effectively, you work with the `Tools` class which manages the complete lifecycle from registration to execution. The framework automatically handles argument validation, schema conversion, and safety policies while providing detailed tracing for debugging.

## Stage 1: Registering Tools with `Tools._add_tool`

The foundation of the tool system lies in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py), where the `Tools` class provides the `_add_tool` method (lines 83-100) to register Python callables. When you register a function, the framework inspects `__mcp_input_schema__` for MCP compatibility or infers types via `__infer_from_signature`, then builds a **tool spec** compatible with OpenAI's function-calling format.

During registration, the system performs three critical operations:

- **Schema Detection** – If the function has a `__mcp_input_schema__` attribute, the framework preserves the original JSON-Schema via `_convert_mcp_schema_to_tool_spec`.
- **Pydantic Model Creation** – The system generates a Pydantic model for argument validation using either `_create_pydantic_model_from_mcp_schema` or signature inference.
- **Metadata Storage** – Any `__aisuite_tool_metadata__` attached to the function is preserved for later tracing.

```python
from aisuite.utils.tools import Tools
from aisuite.toolkits.shell import run_shell

tools = Tools()
tools._add_tool(run_shell)  # Registers the shell tool automatically

```

### MCP Schema Support

For tools implementing the Model Context Protocol (MCP), `aisuite` preserves complex JSON-Schemas verbatim to avoid losing information on nested objects or arrays. The framework still generates a temporary Pydantic model via `_create_pydantic_model_from_mcp_schema` (implemented in [`aisuite/mcp/schema_converter.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/schema_converter.py)) to reuse the existing validation pipeline while maintaining the original schema specification.

## Stage 2: Exporting OpenAI-Compatible Tool Specifications

Once registered, you must export the tool specifications to send to the LLM. The `tools()` method (line 111 in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py)) returns a list of function definitions formatted for OpenAI's API via the `__convert_to_openai_format` helper (lines 315-320).

Each specification contains:

- **name** – The Python function name
- **description** – Extracted from the docstring
- **parameters** – A JSON-Schema derived from the Pydantic model, including types, required fields, and defaults

```python
openai_spec = tools.tools()  # Default format is "openai"

# Returns: [{"type": "function", "function": {"name": "run_shell", ...}}]

```

## Stage 3: Executing Tool Calls with `execute_tool`

When the LLM returns a tool call, you invoke `execute_tool` (line 332 in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py)) to handle the complete execution flow. This method validates arguments, enforces policies, and manages the response formatting.

The execution flow follows these steps:

1. **Argument Parsing** – Converts JSON strings to dictionaries (lines 71-73)
2. **Validation** – Instantiates the Pydantic model with `param_model(**arguments)`, raising `ValidationError` for mismatched payloads (lines 78-84)
3. **Policy Enforcement** – If a `ToolPolicy` is provided, `_evaluate_tool_policy` determines whether to allow the call (lines 91-124)
4. **Function Invocation** – Calls the underlying Python function with validated arguments
5. **Result Handling** – Returns both the raw results and formatted tool messages for the LLM (lines 174-182)

```python

# Simulated LLM response

mock_response = {
    "tool_calls": [
        {
            "id": "call_1",
            "function": {
                "name": "run_shell",
                "arguments": '{"command":"echo hello"}'
            }
        }
    ]
}

results, messages = tools.execute_tool(mock_response["tool_calls"])

# results: [{'stdout': 'hello\n', 'stderr': '', 'code': 0}]

# messages: [{'role': 'tool', 'name': 'run_shell', ...}]

```

### Implementing Safety Policies

The framework supports optional `ToolPolicy` objects that intercept calls before execution. Define a policy function that receives a `ToolPolicyContext` and returns a `ToolPolicyDecision` to allow or deny operations. The framework evaluates this policy in `_evaluate_tool_policy` (lines 91-124) before invoking the underlying function.

```python
from aisuite.agents import ToolPolicyContext, ToolPolicyDecision

def block_dangerous_commands(context: ToolPolicyContext) -> ToolPolicyDecision:
    command = context.arguments.get("command", "")
    if "rm " in command:
        return ToolPolicyDecision(allowed=False, reason="Dangerous command blocked")
    return ToolPolicyDecision(allowed=True)

results, msgs = tools.execute_tool(
    mock_response["tool_calls"],
    tool_policy=block_dangerous_commands
)

```

## Stage 4: Tracing and Artifact Management

Every tool execution emits trace events through `_emit_tool_trace_event` (lines 53-78), marking when tools start, complete, or fail. If an active run context contains an `artifact_store` (managed via [`aisuite/agents/context.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/context.py)), both input arguments and return values pass through `_artifactized_trace_value` (lines 80-88 and 133-140), creating persistent records for debugging and reproducibility.

This integration enables automatic tracking of tool interactions without manual logging, supporting complex workflows in [`aisuite/toolkits/git.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/toolkits/git.py) and [`aisuite/toolkits/files.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/toolkits/files.py) alongside custom utilities.

## Summary

- **Register tools** using `Tools._add_tool` in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py) to automatically generate validation schemas and preserve `__aisuite_tool_metadata__`.
- **Export specifications** via `Tools.tools()` to generate OpenAI-compatible JSON schemas derived from Pydantic models for LLM prompts.
- **Execute safely** with `Tools.execute_tool`, which handles JSON parsing, Pydantic validation, optional `ToolPolicy` enforcement via `_evaluate_tool_policy`, and result formatting.
- **Trace automatically** through built-in event emission and artifact storage when using the active run context from [`aisuite/agents/context.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/context.py).

## Frequently Asked Questions

### How does aisuite validate tool arguments?

The framework uses Pydantic models generated during tool registration. When `execute_tool` processes a call, it validates arguments against `param_model(**arguments)` (lines 78-84 in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py)), raising a `ValidationError` if the payload doesn't match the expected schema derived from the function signature or MCP schema.

### Can I use existing MCP tools with aisuite?

Yes. The `Tools` class detects `__mcp_input_schema__` attributes on functions and preserves the full JSON-Schema via `_convert_mcp_schema_to_tool_spec`. It creates temporary Pydantic models using `_create_pydantic_model_from_mcp_schema` (defined in [`aisuite/mcp/schema_converter.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/schema_converter.py)) to maintain compatibility with the validation pipeline while retaining complex nested structures.

### What safety mechanisms are available for tool execution?

The `ToolPolicy` system allows you to intercept calls before execution. Define a policy function that accepts `ToolPolicyContext` and returns `ToolPolicyDecision` to allow or deny specific operations. The framework evaluates this policy in `_evaluate_tool_policy` (lines 91-124) before invoking the underlying function, enabling you to block dangerous commands or restrict access to sensitive tools like those in [`aisuite/toolkits/shell.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/toolkits/shell.py).

### Where does aisuite store tool execution history?

When an `artifact_store` is present in the active run context, the framework automatically artifactizes both inputs and outputs through `_artifactized_trace_value`. These artifacts attach to trace events emitted by `_emit_tool_trace_event` (lines 53-78), providing persistent records in [`aisuite/agents/context.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/context.py) for debugging, audit trails, and visualization in the UI.