Tool Use

Give your agents superpowers! 💪

Tools let your agents interact with the outside world.

In Lasagna AI, tools are simply Python callables (i.e. functions or callable objects) that you pass to the AI model. The framework handles the complex orchestration of:

# This page will use the following imports:

from lasagna import Model, EventCallback, AgentRun
from lasagna import (
    recursive_extract_messages,
    override_system_prompt,
    flat_messages,
)
from lasagna import known_models
from lasagna.tui import tui_input_loop

import os

import sympy as sp  # type: ignore

from dotenv import load_dotenv

We need to set up our “binder” (see the quickstart guide for what this is).

load_dotenv()

if os.environ.get('OPENAI_API_KEY'):
    print('Using OpenAI')
    binder = known_models.openai_gpt_5_mini_binder

elif os.environ.get('ANTHROPIC_API_KEY'):
    print('Using Anthropic')
    binder = known_models.anthropic_claude_sonnet_4_binder

else:
    assert False, "Neither OPENAI_API_KEY nor ANTHROPIC_API_KEY is set! We need at least one to do this demo."
Using OpenAI

Create Your First Tool

Let’s make a tool! Remember, tools are just Python callables.

AI models (at the time of writing) are bad at math. Here’s a simple math tool to give our AI the ability to evaluate complex math expressions accurately.

def evaluate_math_expression(expression: str) -> float:
    """
    This tool evaluates a math expression and returns the result.
    Pass math expression as a string, for example:
     - "3 * 6 + 1"
     - "cos(2 * pi / 3) + log(8)"
     - "(4.5/2) + (6.3/1.2)"
     - ... etc

    :param: expression: str: the math expression to evaluate
    """
    expr = sp.sympify(expression)
    result = float(expr.evalf())
    return result
Docstring Format

The format of the docstring is important! We’ll cover this in a later section!

Pass the Tool to the AI Model

It is your agent’s job to decide which tools the AI has access to. When your agent does model.run(...), it passes zero or more tools to the AI model. Here’s a quick demo!

async def math_agent(
    model: Model,
    event_callback: EventCallback,
    prev_runs: list[AgentRun],
) -> AgentRun:
    messages = recursive_extract_messages(prev_runs, from_tools=False, from_extraction=False)
    messages = override_system_prompt(messages, 'You are a math assistant. Your name is Bob. Answer all prompts briefly.')

    new_messages = await model.run(
        event_callback,
        messages,
        tools=[
            evaluate_math_expression,   # <-- 🔨 the tool is passed here!
        ],
    )

    return flat_messages('math_agent', new_messages)
await tui_input_loop(binder(math_agent))   # type: ignore[top-level-await]
>  Hi!
Hi — I'm Bob. How can I help with math today?
>  Who are you?
I'm Bob, an AI math assistant. I can help solve problems, explain concepts, check work, and show steps. What math do you need help with?
>  What is pi to the pi?
evaluate_math_expression({"expression":"pi**pi"})

 -> 36.46215960720791

π^π = π^π ≈ 36.46215960720791.
>  exit

Colors

In the tui_input_loop(...) output above, notice:

  • Green is your input.
  • Black is the AI output.
  • Red is a tool invocation.
  • Blue is a tool result.

Tool Features

Definition Flexibilities

Tools can be either functions or callable objects (classes with __call__ method).

Also, tools can either be sync or async. Lasagna is natively async, so there’s a preference for async tools; but, if you pass a synchronous tool then Lasagna will run it in a thread pool (no worries).

Docstrings

Your tool’s docstring is critically important. It is used to:

  • Describe to the AI what the tool does and when to use it.
  • Define the tool’s input parameters. Each parameter has a name, type, and description!

It is formatted like this (for a tool with n input parameters):

"""
{tool_description}

:param: {param_1_name}: {param_1_type}: {param_1_description}
:param: {param_2_name}: {param_2_type}: {param_2_description}
  ...
:param: {param_n_name}: {param_n_type}: {param_n_description}
"""
Authoring Tools is Prompt Engineering!

You should spend LOTS of time and energy writing your tool’s docstring. The AI depends on you describing exactly how your tool works, when to use it, and what the parameters represent.

You’ll likely want to iterate and test different tool docstrings to see which perform best.

The following types are supported as parameters of tools:

  • str
  • float
  • int
  • bool
  • enum {A} {B} ... {Z} (i.e. enum types list the enum string values as a space-separated list following the word “enum”)

Parameters can be optional by putting the string “(optional)” at the start of the parameter’s description in the docstring.

Lasagna will Validate Your Tool’s Parameters

Lasagna will check that your tool’s docstring’s parameter definitions match the callable’s actual parameters (names and types). It will also ensure that all “(optional)” parameters have default values in the callable’s signature.

If any docstring-to-signature mismatch is found, you’ll get a runtime exception when you first attempt to pass the tool to a model.

Parallel Execution

If the AI model asks for more than one tool call, then Lasagna will call those tools in parallel! This provides a speed boost, but keep this in mind so that you manage state correctly (i.e. no race conditions). If you are a well-behaved functional-style programmer who never modifies state, you’ll be fine.

Tool Recipes

Many of the recipes show examples of tool use. See:

Layered Agents

Layered agents are a founding idea behind the Lasagna AI library. With Lasagna, we can call agents like we call functions in procedural programming. Consider good-ol’ functions:

  • You define a function.
  • You can invoke it.
  • Other functions can also invoke it.
  • It can invoke other functions.
  • Each function has its own well-defined input/output and behavior.

In Lasagna, agents are the same!

  • You define an agent (as a function or callable object).
  • You can invoke it.
  • Other agents can also invoke it.
  • It can invoke other agents.
  • Each agent has its own well-defined input/output and behavior.

Just like you compose a program by layering functions from low-level to high-level, you do the same with Lasagna and AI Agents!

🎉🎉🎉 This is why it’s called Lasagna! Because it has layers! 🤓🤓🤓

Agents as Tools

A similar founding idea was that you should be able to layer agents by passing agents as tools to other agents. So, you can!

See the Agents as Tools recipe for a working example.

Agent Routing

Creating an agent whose purpose is to route to one (or many) downstream agents is also a key concept for any “layered” system.

See the Agent Routing recipe for a working example.

Next Steps

Now that you understand tools, you can explore: