Skip to main content

AI Tools Executor

An executor layer between AI agents and tools. The agent gets only 3 meta-tools — tools are discovered on-demand and invoked via Python function call syntax, not JSON.

The Problem

Every agent framework dumps all tool schemas into the LLM context on every turn. With 50 tools at ~500 tokens each, that's 25,000 tokens wasted — on tools that will never be used.

The Solution

The agent sees exactly 3 meta-tools:

Meta-Tool Purpose
search_tools(query) Discover available tools
execute(calls) Run one or more tool calls
describe_tool(name) Get detailed docs for a specific tool

Everything else — validation, execution, result formatting — happens behind the scenes.

Installation

pip install ai-tools-executor

Or with uv:

uv add ai-tools-executor

Quick Start

1. Register Tools

from ai_tools_executor import tool

@tool(description="Fetch real-time stock price.", category="finance", tags=["stock", "price"])
def get_stock_price(symbol: str) -> dict:
    """Fetch real-time stock price for a ticker symbol.

    Args:
        symbol: Stock ticker (e.g. 'GOOG', 'AAPL').

    Examples:
        get_stock_price(symbol="GOOG")
    """
    return {"symbol": symbol, "price": 182.63, "currency": "USD"}


@tool(description="Search the web for information.", category="search", tags=["web", "google"])
def search_web(query: str, max_results: int = 5) -> list[dict]:
    """Search the web using a text query.

    Args:
        query: Natural language search query.
        max_results: Number of results to return (1-20).
    """
    return [{"title": "...", "url": "...", "snippet": "..."}]

2. Use the Executor

from ai_tools_executor import ToolExecutor

executor = ToolExecutor()

# Meta-tool 1: Search for tools by intent
print(executor.search_tools("stock price lookup"))
# def get_stock_price(symbol: str) -> dict:
#     """Fetch real-time stock price."""

# Meta-tool 2: Execute tool calls using Python syntax
results = executor.execute("get_stock_price(symbol='GOOG')")
r = results[0]
r.ok        # True
r.tool      # "get_stock_price"
r.result    # {"symbol": "GOOG", "price": 182.63, "currency": "USD"}

# Execute multiple calls at once
results = executor.execute(
    "[get_stock_price(symbol='GOOG'), search_web(query='market trends')]"
)

# Serialise for transport when needed
r.to_dict()  # plain dict
r.to_json()  # JSON string

# Meta-tool 3: Get detailed docs when needed
print(executor.describe_tool("search_web"))

Key Features

Function Call Syntax (not JSON)

# Traditional agent tool calling (JSON — verbose, error-prone)
{"tool": "get_stock_price", "params": {"symbol": "GOOG"}}

# This package (Python syntax — native, token-efficient)
get_stock_price(symbol="GOOG")
JSON Tool Calling Function Call Syntax
Tokens ~20 per call ~7 per call (65% less)
LLM fluency Synthetic format Native — trained on billions of function calls
Validation Custom JSON schema validator ast.parse() — same as IDEs/linters
Error rate Higher (JSON syntax errors) Lower (function calls are natural to LLMs)

Safe AST Parsing

No code is ever executed. The parser uses Python's ast module to extract function names and arguments — exactly like an IDE or linter:

import ast
tree = ast.parse('get_stock_price(symbol="GOOG")', mode="eval")
# Extracts: function_name="get_stock_price", kwargs={"symbol": "GOOG"}
# Validates against registry, then calls the real function

Partial Failure on Multi-Call

When some calls succeed and others fail, you get both:

results = executor.execute(
    "[get_stock_price(symbol='GOOG'), bad_tool(x=1)]"
)
results[0].ok      # True  — stock price succeeded
results[0].result  # {"symbol": "GOOG", "price": 182.63, ...}
results[1].ok      # False — bad_tool failed
results[1].error   # "ToolNotFoundError: Tool 'bad_tool' not found ..."

Structured Error Messages

Errors follow a consistent format so the agent can self-correct:

ValidationError: Missing required parameter(s) for 'get_stock_price': symbol
  Input:    get_stock_price()
  Error:    Missing required parameter(s) for 'get_stock_price': symbol
  Expected: def get_stock_price(symbol: str) -> dict:
            """Fetch real-time stock price."""
  Hint:     Required: symbol

Pluggable Search

Swap search strategies via the SearchStrategy ABC:

from ai_tools_executor import ToolExecutor, SearchStrategy

class SemanticSearch(SearchStrategy):
    def search(self, query, tools, *, max_results=5):
        # Your embedding-based search here
        ...

executor = ToolExecutor(search_strategy=SemanticSearch())

Async Execution

Run independent tool calls concurrently:

results = await executor.execute_async(
    "[get_stock_price(symbol='GOOG'), get_weather(city='London')]"
)

Hot-Reload

Register and unregister tools at runtime:

from ai_tools_executor import get_default_registry

registry = get_default_registry()
registry.unregister("old_tool")
# Register new tools with @tool — no restart needed

Architecture

AI Agent (LLM)
  │  Only sees: search_tools + execute + describe_tool
  │
  ├── search_tools(query) ──► Tool Search ──► Registry ──► Ranked results
  │
  ├── execute(calls) ──► AST Parser ──► Validator ──► Tool Function ──► Results
  │
  └── describe_tool(name) ──► Registry ──► Full docstring

For the complete architecture document, see docs/architecture.md.

Project Structure

src/ai_tools_executor/
├── __init__.py      # Public API
├── decorator.py     # @tool decorator, ToolInfo, ParameterInfo
├── exceptions.py    # Structured error hierarchy
├── executor.py      # ToolExecutor (3 meta-tools)
├── models.py        # ToolCallResult, CallStatus (frozen dataclasses)
├── parser.py        # AST call parser + Layer 1 validation
├── registry.py      # Thread-safe ToolRegistry
├── meta_tools.py    # Meta-tool schema + handle_tool_call handler
└── search.py        # Pluggable search strategies

Development

# Clone and install
git clone https://github.com/surajairi/ai-tools-executor.git
cd ai-tools-executor
uv sync

# Run tests
uv run pytest tests/ -v

# Lint
uv run ruff check src/ tests/

Requirements

  • Python ≥ 3.10
  • No external dependencies (stdlib only)

License

MIT

Release files for ai-tools-executor 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ai-tools-executor 0.3.0
File Size Uploaded
ai_tools_executor-0.3.0.tar.gz 23.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ai-tools-executor 0.3.0
File Interpreter ABI Platform
ai_tools_executor-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 44.3 kB

Release files / ai_tools_executor-0.3.0.tar.gz

Download URL ai_tools_executor-0.3.0.tar.gz
Size 23.6 kB
Tags Source
SHA-256 checksum
How to use checksums
3f03e96e069d1ef6b841b628a8e97d615d83838f6ccd62de800d3c6c8852ef13
BLAKE2b-256 checksum
How to use checksums
75821382b05ca63927258b7be3932be82bcb6223ffbcb2f0b76d3b73cd97080e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.8

Release files / ai_tools_executor-0.3.0-py3-none-any.whl

Download URL ai_tools_executor-0.3.0-py3-none-any.whl
Size 20.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9f3ba865ddc9ee9959473cf2ff91ccf7a66ba3f09fe99a65d453447607a57005
BLAKE2b-256 checksum
How to use checksums
4585e7c6068a82fceedb2850386845096139ae2fdc9f5a13cf705f0ce70f0997
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.8

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page