Custom Tools

Custom Tools

Register custom Python functions as tools using the @global_tool decorator with OpenAI-compatible parameter schemas.

Registration

from leafmesh import global_tool @global_tool( name="lookup_account", description="Look up account details by account ID", parameters={ "type": "object", "properties": { "account_id": { "type": "string", "description": "The account ID to look up" } }, "required": ["account_id"] } ) async def lookup_account(account_id: str) -> dict: """Your implementation here""" return {"tier": "enterprise", "status": "active", "account_id": account_id}

Tools that change nothing

2.4.491+. A tool that only reads, such as a price lookup, can say so with changes_nothing=True (default false). @tool takes the same argument.

@global_tool(name="lookup_price", description="Current list price for a SKU", parameters={...}, changes_nothing=True)

When every tool a turn used changes nothing, and the answer holds no personal data or secret, the answer may be reused from the response cache.

Using Custom Tools

Once registered, include the tool name in any agent's tools array:

agents: support_agent: name: "support_agent" model: "gpt-4o" tools: ["lookup_account", "current_time"] prompt: | Look up customer accounts when needed.

Examples

Database Query

@global_tool( name="query_database", description="Execute a read-only SQL query", parameters={ "type": "object", "properties": { "query": {"type": "string", "description": "SQL SELECT query"} }, "required": ["query"] } ) async def query_database(query: str) -> dict: import aiosqlite async with aiosqlite.connect("app.db") as db: cursor = await db.execute(query) rows = await cursor.fetchall() columns = [desc[0] for desc in cursor.description] return {"columns": columns, "rows": rows, "count": len(rows)}

HTTP API Call

@global_tool( name="fetch_weather", description="Get current weather for a location", parameters={ "type": "object", "properties": { "city": {"type": "string", "description": "City name"} }, "required": ["city"] } ) async def fetch_weather(city: str) -> dict: import aiohttp url = f"https://api.weather.example.com/current?city={city}" async with aiohttp.ClientSession() as session: async with session.get(url) as resp: return await resp.json()

Statistics

@global_tool( name="statistics", description="Calculate statistics for a list of numbers", parameters={ "type": "object", "properties": { "values": { "type": "array", "items": {"type": "number"}, "description": "Numeric values to analyze" } }, "required": ["values"] } ) async def statistics(values: list) -> dict: n = len(values) if n == 0: return {"error": "Empty list"} mean = sum(values) / n variance = sum((x - mean) ** 2 for x in values) / n return {"count": n, "mean": round(mean, 4), "std_dev": round(variance ** 0.5, 4)}

Parameter Schema

The parameters field uses OpenAI-compatible JSON Schema:

parameters={ "type": "object", "properties": { "param_name": { "type": "string|number|boolean|array|object", "description": "What this parameter does" } }, "required": ["param_name"] }

Tool Categories

Categories let you organize tools by domain and grant agents access to entire groups at once, instead of listing every tool individually.

Built-in Categories

LeafMesh organizes its built-in tools into these categories:

CategoryTools
mathcalculator, random_number
timecurrent_time, time_difference
texttext_analyzer, text_formatter
datajson_formatter, data_converter
webweb_request
reasoningchain_of_thought, metacognitive_reflection
memoryrecall_memory
systemsystem_info

Zapier, MCP, Composio and n8n are not generic tools. From 2.4.485 the generic zapier_action, mcp_call, composio_action and n8n_workflow tools are removed; an agent listing one in tools: is refused at load, and the error names the block to use instead. Declare them with the agent's zapier:, mcp:, composio: or n8n: block, each picking a vault record (see Declarative Connectors and MCP).

Assigning Categories to Agents

Use tool_categories in YAML to give an agent access to all tools in one or more categories:

agents: data_agent: name: "data_agent" model: "gpt-4o" tool_categories: ["math", "data"] # Access to all math + data tools prompt: | You are a data processing agent.

This is equivalent to writing tools: ["calculator", "random_number", "json_formatter", "data_converter"] but easier to maintain. You can combine both approaches -- tools listed in tools and tools from tool_categories are merged together:

agents: analyst: name: "analyst" model: "gpt-4o" tools: ["web_request"] # Specific tool tool_categories: ["math", "text"] # Entire categories prompt: | You are an analyst with web access, math, and text tools.

Defining Custom Categories

When you register a custom tool with @global_tool, set the category parameter to place it in a category. If the category does not exist, it is created automatically:

from leafmesh import global_tool @global_tool( name="query_orders", description="Query recent orders from the database", category="ecommerce", # Custom category parameters={ "type": "object", "properties": { "customer_id": {"type": "string", "description": "Customer ID"} }, "required": ["customer_id"] } ) async def query_orders(customer_id: str) -> dict: # Your implementation return {"orders": []} @global_tool( name="check_inventory", description="Check product inventory levels", category="ecommerce", # Same category parameters={ "type": "object", "properties": { "product_id": {"type": "string", "description": "Product ID"} }, "required": ["product_id"] } ) async def check_inventory(product_id: str) -> dict: return {"in_stock": True, "quantity": 42}

Now any agent with tool_categories: ["ecommerce"] gets access to both query_orders and check_inventory:

agents: shop_assistant: name: "shop_assistant" model: "gpt-4o" tool_categories: ["ecommerce"] prompt: | You are a shop assistant. Help customers with orders and inventory.

Parallel Tool Execution

By default, when an LLM generates multiple tool calls in a single response, LeafMesh executes them concurrently. This significantly speeds up agents that need to gather data from multiple sources at once.

agents: fast_agent: name: "fast_agent" model: "gpt-4o" tools: ["web_request", "query_database", "calculator"] allow_parallel_tool_calls: true # Default: true

To force sequential execution (e.g., when one tool depends on another's result):

agents: sequential_agent: name: "sequential_agent" model: "gpt-4o" tools: ["web_request", "query_database"] allow_parallel_tool_calls: false # Tools run one at a time

When parallel execution is enabled, independent tool calls run concurrently, bounded per batch.

Tool Execution Limits

Prevent runaway tool loops with max_tool_calls_per_message. When the limit is reached, the LLM receives the accumulated tool results and must produce a final text response without further tool calls.

agents: # Conservative: stop after 2 tool calls careful_agent: name: "careful_agent" model: "gpt-4o" tools: ["web_request", "calculator"] max_tool_calls_per_message: 2 # Generous: allow up to 15 tool calls research_agent: name: "research_agent" model: "gpt-4o" tools: ["web_request", "query_database", "calculator"] max_tool_calls_per_message: 15
SettingDefaultRangeDescription
max_tool_calls_per_message50-20Maximum tool call rounds per message
tool_call_timeout30.0Any positive floatTimeout per individual tool execution (seconds)

Setting max_tool_calls_per_message: 0 effectively disables tool calling for that agent, even if tools are listed.

Tool Choice Modes

Control how the LLM uses its available tools with the tool_choice setting:

agents: # LLM decides whether and which tools to call (default) auto_agent: name: "auto_agent" tools: ["calculator", "web_request"] tool_choice: "auto" # LLM must call at least one tool every turn always_tool_agent: name: "always_tool_agent" tools: ["query_database"] tool_choice: "required" # Force calls to a specific tool by name forced_agent: name: "forced_agent" tools: ["calculator", "web_request"] tool_choice: "calculator" # Disable tool calling — agent responds with text only text_only_agent: name: "text_only_agent" tools: ["calculator"] tool_choice: "none"
ModeBehavior
"auto"LLM decides when to use tools (default)
"required"LLM must use at least one tool per turn
"none"Tool calling disabled, even if tools are listed
"<tool_name>"Force the LLM to call a specific tool

Complete Configuration Example

An agent combining all tool configuration options:

agents: full_featured_agent: name: "full_featured_agent" model: "gpt-4o" tools: ["web_request"] tool_categories: ["math", "data", "ecommerce"] tool_choice: "auto" max_tool_calls_per_message: 10 tool_call_timeout: 45.0 allow_parallel_tool_calls: true prompt: | You are a versatile agent with access to web, math, data processing, and e-commerce tools.

Next Steps


LeafMesh --- Extend agents with custom capabilities

Last updated: 11/10/2026