Skip to content

Defining Tools

Tools are the functions an agent can call. exagent infers the JSON schema automatically from your type hints and docstring — you just write a normal Python function.


The @tool decorator

from exagent import tool

@tool
def search(query: str, max_results: int = 5) -> list:
    """Search the knowledge base and return matching entries."""
    ...

What the decorator does:

Source Used for
Function name Tool name sent to the model
Docstring Tool description the model reads
Type hints JSON schema (types + required fields)
Default values Marks parameters as optional

Supported type hints

Python type JSON schema type
str string
int integer
float number
bool boolean
list / list[T] array
dict object
Optional[T] unwrapped to T (optional parameter)

Override name or description

Pass keyword arguments to @tool to override the inferred values:

@tool(name="lookup_price", description="Get the current price of a product by SKU.")
def price(sku: str) -> float:
    ...

This is useful when the function name is not descriptive enough for the model, or when you want a more detailed description than the docstring.


Registering tools

# One at a time
agent.add_tool(my_tool)

# Several at once
agent.add_tools([tool_a, tool_b, tool_c])

# Plain function — wrapped automatically
agent.add_tool(my_plain_function)

add_tool accepts both Tool instances and plain functions. Plain functions are wrapped with @tool on the fly.


Manual Tool construction

When you need full control over the schema — for example to add an enum constraint or a nested object — construct a Tool directly:

from exagent import Tool

sentiment_tool = Tool(
    name="analyze_sentiment",
    description="Classify the sentiment of a piece of text.",
    parameters={
        "type": "object",
        "properties": {
            "text": {"type": "string", "description": "The text to analyze."},
            "language": {
                "type": "string",
                "enum": ["en", "fr", "de", "es"],
                "description": "ISO language code.",
            },
        },
        "required": ["text"],
    },
    handler=lambda text, language="en": classify(text, language),
)

agent.add_tool(sentiment_tool)

Returning values

Tool handlers can return any JSON-serialisable value. exagent converts the result to a string before feeding it back to the model:

  • str — passed through unchanged
  • anything else — serialised with json.dumps
  • non-serialisable — converted with str()
@tool
def get_user(user_id: str) -> dict:
    """Fetch a user record."""
    return {"id": user_id, "name": "Alice", "role": "admin"}
    # the model receives: '{"id": "u1", "name": "Alice", "role": "admin"}'

Error handling

If a tool handler raises an exception, exagent catches it and sends the error message back to the model as a tool_result with is_error: true. The model can then decide how to respond — usually by informing the user or trying a different approach.

@tool
def read_file(path: str) -> str:
    """Read a file and return its contents."""
    with open(path) as f:
        return f.read()
    # If FileNotFoundError is raised, the model is told: "Error running tool 'read_file': ..."

Raise meaningful exceptions

Use descriptive exception messages. The model reads them and uses them to decide what to do next.


Next

Building Agents →