Skip to main content

An executor layer between AI agents and tools — 3 meta-tools, function-call syntax, AST parsing.

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ai_tools_executor-0.3.0.tar.gz (23.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ai_tools_executor-0.3.0-py3-none-any.whl (20.7 kB view details)

Uploaded Python 3

File details

Details for the file ai_tools_executor-0.3.0.tar.gz.

File metadata

  • Download URL: ai_tools_executor-0.3.0.tar.gz
  • Upload date:
  • Size: 23.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.8

File hashes

Hashes for ai_tools_executor-0.3.0.tar.gz
Algorithm Hash digest
SHA256 3f03e96e069d1ef6b841b628a8e97d615d83838f6ccd62de800d3c6c8852ef13
MD5 eed980dd0bd04b6a4bff21102cf58677
BLAKE2b-256 75821382b05ca63927258b7be3932be82bcb6223ffbcb2f0b76d3b73cd97080e

See more details on using hashes here.

File details

Details for the file ai_tools_executor-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for ai_tools_executor-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9f3ba865ddc9ee9959473cf2ff91ccf7a66ba3f09fe99a65d453447607a57005
MD5 752e40fc444ea2d7272ed6541ba026a9
BLAKE2b-256 4585e7c6068a82fceedb2850386845096139ae2fdc9f5a13cf705f0ce70f0997

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page