Skip to main content
Learn how to create, configure, and use tools with Laddr agents.

Basic Tool Structure

Tools are Python functions decorated with @tool:
from laddr import tool

@tool(
    name="weather_lookup",
    description="Fetches current weather for a given city",
    parameters={
        "type": "object",
        "properties": {
            "city": {
                "type": "string",
                "description": "City name"
            },
            "units": {
                "type": "string",
                "description": "Units (metric/imperial)",
                "default": "metric"
            }
        },
        "required": ["city"]
    }
)
def weather_lookup(city: str, units: str = "metric") -> dict:
    """Tool docstring: fetch current weather."""
    try:
        result = get_weather(city, units)
        return {"status": "success", "data": result}
    except Exception as e:
        return {"status": "error", "error": str(e)}


The @tool Decorator

Syntax

@tool(
    name: str,              # Unique tool identifier
    description: str,       # What the tool does
    parameters: dict,       # JSON Schema for inputs
    trace: bool = True,      # Enable tracing
    trace_mask: list = []   # Fields to redact in traces
)

Parameters

ParameterTypeRequiredDescription
namestrUnique identifier used by agents
descriptionstrSummary of tool’s purpose
parametersdictJSON Schema for tool inputs
traceboolEnable or disable logging (default: True)
trace_masklistRedact sensitive trace fields

JSON Schema Types

String

parameters={
    "type": "object",
    "properties": {
        "query": {
            "type": "string",
            "description": "Search query"
        }
    },
    "required": ["query"]
}

Number

parameters={
    "type": "object",
    "properties": {
        "count": {
            "type": "integer",
            "description": "Number of results",
            "minimum": 1,
            "maximum": 100
        }
    }
}

Boolean

parameters={
    "type": "object",
    "properties": {
        "include_metadata": {
            "type": "boolean",
            "description": "Include metadata in response"
        }
    }
}

Array

parameters={
    "type": "object",
    "properties": {
        "items": {
            "type": "array",
            "items": {"type": "string"},
            "description": "List of items to process"
        }
    }
}

Object (Nested)

parameters={
    "type": "object",
    "properties": {
        "filters": {
            "type": "object",
            "properties": {
                "min_price": {"type": "number"},
                "max_price": {"type": "number"}
            }
        }
    }
}


Tool Patterns

API Wrapper

import requests
from laddr import tool

@tool(
    name="api_call",
    description="Call external API",
    parameters={
        "type": "object",
        "properties": {
            "endpoint": {"type": "string"},
            "method": {"type": "string", "enum": ["GET", "POST"]},
            "data": {"type": "object"}
        },
        "required": ["endpoint"]
    }
)
def api_call(endpoint: str, method: str = "GET", data: dict = None):
    """Make API call."""
    try:
        if method == "GET":
            response = requests.get(endpoint)
        else:
            response = requests.post(endpoint, json=data)
        response.raise_for_status()
        return {"status": "success", "data": response.json()}
    except Exception as e:
        return {"status": "error", "error": str(e)}

Data Transformer

from laddr import tool

@tool(
    name="transform_data",
    description="Transform data format",
    parameters={
        "type": "object",
        "properties": {
            "data": {"type": "array"},
            "format": {"type": "string", "enum": ["json", "csv", "xml"]}
        },
        "required": ["data", "format"]
    }
)
def transform_data(data: list, format: str) -> dict:
    """Transform data to specified format."""
    if format == "json":
        return {"format": "json", "data": data}
    elif format == "csv":
        # Convert to CSV
        return {"format": "csv", "data": convert_to_csv(data)}
    # ...

File Operations

from laddr import tool
import os

@tool(
    name="read_file",
    description="Read file contents",
    parameters={
        "type": "object",
        "properties": {
            "file_path": {"type": "string"}
        },
        "required": ["file_path"]
    }
)
def read_file(file_path: str) -> dict:
    """Read file from filesystem."""
    try:
        if not os.path.exists(file_path):
            return {"status": "error", "error": "File not found"}
        
        with open(file_path, "r") as f:
            content = f.read()
        
        return {"status": "success", "content": content}
    except Exception as e:
        return {"status": "error", "error": str(e)}


Registering Tools with Agents

Single Tool

from laddr import Agent

agent = Agent(
    name="weather_agent",
    tools=[weather_lookup],
    # ... other config
)

Multiple Tools

agent = Agent(
    name="multi_tool_agent",
    tools=[
        weather_lookup,
        api_call,
        transform_data,
        read_file
    ],
    # ... other config
)


Tracing Configuration

Enable/Disable Tracing

@tool(
    name="sensitive_tool",
    description="Tool with sensitive data",
    parameters={...},
    trace=False  # Disable tracing
)
def sensitive_tool(...):
    pass

Mask Sensitive Fields

@tool(
    name="api_with_auth",
    description="API call with authentication",
    parameters={...},
    trace_mask=["api_key", "password"]  # Redact these fields
)
def api_with_auth(api_key: str, password: str, ...):
    # api_key and password will be redacted in traces
    pass


Error Handling

Return Error Status

@tool(name="risky_tool", ...)
def risky_tool(...):
    try:
        result = perform_operation()
        return {"status": "success", "data": result}
    except ValueError as e:
        return {"status": "error", "error": f"Invalid input: {e}"}
    except Exception as e:
        return {"status": "error", "error": str(e)}

Raise Exceptions

@tool(name="critical_tool", ...)
def critical_tool(...):
    if not validate_input(...):
        raise ValueError("Invalid input")
    
    result = perform_operation()
    return {"status": "success", "data": result}

Return error dictionaries for recoverable errors. Raise exceptions for critical failures.

Testing Tools

Manual Testing

# Test tool directly
result = weather_lookup(city="San Francisco", units="metric")
print(result)

# Test with invalid input
result = weather_lookup(city="")
print(result)

Unit Testing

import pytest
from tools.weather_tools import weather_lookup

def test_weather_lookup_success():
    result = weather_lookup(city="San Francisco")
    assert result["status"] == "success"
    assert "data" in result

def test_weather_lookup_invalid_city():
    result = weather_lookup(city="")
    assert result["status"] == "error"

Integration Testing

async def test_tool_with_agent():
    agent = Agent(
        name="test_agent",
        tools=[weather_lookup],
        # ... config
    )
    
    result = await agent.process_task({
        "query": "What's the weather in San Francisco?"
    })
    
    # Verify tool was called
    assert "weather_lookup" in str(result)


Best Practices

1. Clear Descriptions

# ✅ Good
@tool(
    name="search_web",
    description="Search the web for information on a given topic",
    ...
)

# ❌ Bad
@tool(
    name="search",
    description="Searches stuff",
    ...
)

2. Validate Inputs

@tool(name="safe_tool", ...)
def safe_tool(value: int):
    if value < 0:
        return {"status": "error", "error": "Value must be positive"}
    # ... rest of implementation

3. Consistent Return Format

# ✅ Good - Consistent format
return {"status": "success", "data": result}
return {"status": "error", "error": "message"}

# ❌ Bad - Inconsistent
return result
return {"error": "message"}
return {"success": True, "result": result}

4. Handle Errors Gracefully

@tool(name="robust_tool", ...)
def robust_tool(...):
    try:
        result = perform_operation()
        return {"status": "success", "data": result}
    except SpecificError as e:
        # Handle specific error
        return {"status": "error", "error": str(e)}
    except Exception as e:
        # Log unexpected errors
        logger.error(f"Unexpected error: {e}")
        return {"status": "error", "error": "Internal error"}


Advanced Patterns

Async Tools

from laddr import tool
import aiohttp

@tool(name="async_api_call", ...)
async def async_api_call(url: str):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return {"status": "success", "data": await response.json()}

Tool Composition

@tool(name="composite_tool", ...)
def composite_tool(...):
    # Use other tools internally
    result1 = helper_tool1(...)
    result2 = helper_tool2(...)
    return combine_results(result1, result2)


Next Steps