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
└── 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.12
  • 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.2.0.tar.gz (20.4 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.2.0-py3-none-any.whl (17.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for ai_tools_executor-0.2.0.tar.gz
Algorithm Hash digest
SHA256 8e5462ac6f7b46c93ea1164e08676a7e0bc1a3ae9e36daa32264d09a907581fb
MD5 c324c3afffcc68a8eb7e896b1a570189
BLAKE2b-256 bb6826cf3222ffe2904ef9a6dd03441c900c3404285a94416ab9856c7839e127

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for ai_tools_executor-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b605fd4a83b66a84a7e46816ce4fe8dd387876b66cbb70d1c9dd9c8d85cc9f65
MD5 986908fa6754e15218d283c14d35d266
BLAKE2b-256 09d2fa6ccc6fe1573bbb2b92d78f54ddf7ae52b07a4b22929868e90ddf985ac9

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