# How aisuite Generates Tool Schemas from Python Functions: From Signatures to JSON Schema

> Discover how aisuite generates tool schemas from Python functions, transforming signatures and docstrings into JSON Schema for LLM integration.

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

---

**aisuite generates tool schemas from Python functions by inspecting callables with the `inspect` module and `docstring_parser`, dynamically creating Pydantic models via `create_model`, and converting them to JSON Schema specifications that LLM providers like OpenAI expect.**

The aisuite library (andrewyng/aisuite) abstracts tool-calling across multiple LLM providers by automatically transforming Python functions into structured schemas. Understanding how aisuite generates tool schemas from Python functions reveals the sophisticated interplay between runtime introspection, dynamic model generation, and MCP schema preservation that powers its unified tool interface.

## The Tool Registration Pipeline

When you instantiate `Tools(tools=[my_func])` in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py), the class immediately invokes the private method `Tools._add_tool` to process each callable. This method serves as the central dispatcher that determines whether to extract schema information from an existing MCP definition or infer it from the function's signature.

### MCP Schema Detection vs. Signature Inference

The `_add_tool` method first checks for the presence of the `__mcp_input_schema__` attribute on the function. If found, the code bypasses standard signature inspection and instead calls `Tools._convert_mcp_schema_to_tool_spec` to copy the original JSON Schema directly into the tool specification. Simultaneously, it invokes `Tools._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 construct a Pydantic model for runtime validation.

If no MCP schema is detected, the method falls back to `self.__infer_from_signature(func)`, which uses Python's `inspect.signature` to analyze parameters.

## Converting Python Signatures to JSON Schema

For standard Python functions without MCP metadata, aisuite builds a complete type-aware schema through three distinct phases.

### Runtime Introspection with inspect and docstring_parser

The `__infer_from_signature` method captures the function's parameter names, types, and default values using `inspect.signature`. It then parses the docstring via `docstring_parser` to extract parameter descriptions, linking these explanations to their corresponding arguments for complete documentation.

### Dynamic Pydantic Model Generation

Using the extracted metadata, aisuite calls `create_model` from Pydantic to dynamically generate a model class that mirrors the function signature. Required parameters (those without defaults) become mandatory fields in the Pydantic model, while optional parameters receive default values. This model serves dual purposes: runtime validation of LLM-provided arguments and JSON Schema generation.

### Schema Normalization

The method `_convert_to_tool_spec` transforms the Pydantic model into a provider-compatible specification. It calls `_normalize_json_schema` to ensure proper formatting, producing a dictionary with keys **`name`**, **`description`**, and **`parameters`** containing the complete JSON Schema.

## MCP Schema Preservation and Conversion

When integrating Model Context Protocol (MCP) tools, aisuite preserves the original schema integrity rather than regenerating it.

### Schema-to-Python Type Conversion

In [`aisuite/mcp/schema_converter.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/schema_converter.py), the function `mcp_schema_to_annotations` walks the MCP input schema and converts JSON Schema types (e.g., `string`, `array`) into Python type annotations (`str`, `List[str]`). Fields not listed in the schema's `"required"` array are automatically wrapped with `typing.Optional`. The `create_function_signature` function can then rebuild an `inspect.Signature` from these annotations.

### Validation Model Creation

The `Tools._create_pydantic_model_from_mcp_schema` method consumes these annotations to create a Pydantic model that exactly matches the MCP schema, preserving validation rules such as required fields and constraints without information loss.

## Practical Implementation Examples

### Registering a Standard Python Function

```python
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

tools = Tools([add])
openai_spec = tools.tools()  # Returns [{'type': 'function', 'function': {...}}]

print(openai_spec[0]['function']['parameters'])

# {

#   "type": "object",

#   "properties": {

#     "a": {"type": "integer", "description": ""},

#     "b": {"type": "integer", "description": ""}

#   },

#   "required": ["a", "b"]

# }

```

### Integrating MCP-Wrapped Tools

When a function carries MCP metadata via `__mcp_input_schema__` (typically set by decorators in [`aisuite/mcp/tool_wrapper.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/tool_wrapper.py)), aisuite preserves the original schema:

```python
from aisuite.mcp.tool_wrapper import mcp_tool_wrapper

@mcp_tool_wrapper
def download(url: str):
    """Download a file from the given URL."""
    ...

tools = Tools([download])

# The original MCP JSON schema is used verbatim via 

# Tools._convert_mcp_schema_to_tool_spec

openai_spec = tools.tools()

```

### Executing Tool Calls

Once registered, the `Tools.execute_tool` method validates incoming arguments against the stored Pydantic model before invoking the function:

```python
tool_calls = [{
    "id": "call_1",
    "function": {"name": "add", "arguments": {"a": 3, "b": 5}}
}]
results, messages = tools.execute_tool(tool_calls)
print(results)   # [8]

print(messages)  # [{'role':'tool', 'name':'add', 'content':'8', ...}]

```

## Summary

- aisuite generates tool schemas in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py) through the `Tools._add_tool` method, which supports both standard Python functions and MCP-wrapped tools.
- For standard functions, the library uses `inspect.signature` and `docstring_parser` to extract metadata, then builds Pydantic models dynamically via `create_model`.
- MCP schemas are preserved through `__mcp_input_schema__` detection and processed via [`aisuite/mcp/schema_converter.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/schema_converter.py) to maintain original validation rules.
- The resulting specification contains **`name`**, **`description`**, and **`parameters`** keys formatted as JSON Schema, compatible with OpenAI and other providers.

## Frequently Asked Questions

### How does aisuite handle type annotations when generating tool schemas?

aisuite inspects type annotations using `inspect.signature` and converts them into Pydantic field definitions. Complex types from MCP schemas are translated via `mcp_schema_to_annotations` in [`aisuite/mcp/schema_converter.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/schema_converter.py), ensuring JSON Schema compatibility while preserving Python's type system constraints.

### What happens if my Python function lacks type hints?

The `__infer_from_signature` method in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py) still processes the function using `inspect.signature`, but fields may default to less specific types in the generated Pydantic model. The schema generation remains functional, though explicit type hints provide better validation and clearer documentation for the LLM.

### Can I use existing Pydantic models directly with aisuite tools?

While the internal pipeline specifically uses `create_model` to generate models from function signatures, functions decorated with MCP wrappers that include `__mcp_input_schema__` bypass regeneration. The `Tools._create_pydantic_model_from_mcp_schema` method ensures the runtime validation model matches the original schema exactly.

### Where does schema normalization occur in the pipeline?

The `_convert_to_tool_spec` method calls `_normalize_json_schema` to finalize the dictionary structure before storage. This occurs after Pydantic model creation (for standard functions) or MCP conversion, ensuring the output contains the required **`name`**, **`description`**, and **`parameters`** keys in provider-compatible format.