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

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, 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) 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, 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

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), aisuite preserves the original schema:

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:

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 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 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, 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →