Skip to content

Tutorial 02: Agent with Tools

This tutorial covers:

  • Defining tools with the @tool decorator
  • Giving tools to an agent
  • Watching the agent use tools
  • Understanding tool execution events

Prerequisites: Tutorial 01 (Basic Agent) Difficulty: Beginner

Source

# Copyright (c) 2025, 2026 Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v1.0 as shown at
# https://oss.oracle.com/licenses/upl/
"""
Tutorial 02: Agent with Tools

This tutorial covers:
- Defining tools with the @tool decorator
- Giving tools to an agent
- Watching the agent use tools
- Understanding tool execution events

Prerequisites: Tutorial 01 (Basic Agent)
Difficulty: Beginner
"""

import asyncio
from datetime import datetime

# Import shared config
from config import get_model, print_config

from locus.agent import Agent
from locus.tools import tool


# =============================================================================
# Part 1: Defining a Simple Tool
# =============================================================================

# Tools are just Python functions decorated with @tool
# The docstring becomes the tool description for the LLM


@tool
def add_numbers(a: int, b: int) -> int:
    """Add two numbers together."""
    return a + b


@tool
def multiply_numbers(a: int, b: int) -> int:
    """Multiply two numbers together."""
    return a * b


def example_simple_tools():
    """Create and use simple tools — and confirm the model can describe them."""
    print("=== Part 1: Simple Tools ===\n")

    result = add_numbers(5, 3)
    print(f"Direct call: add_numbers(5, 3) = {result}")

    print(f"\nTool name: {add_numbers.name}")
    print(f"Tool description: {add_numbers.description}")
    print(f"Tool parameters: {add_numbers.parameters}")

    import time as _t

    agent = Agent(
        model=get_model(max_tokens=80),
        system_prompt="Reply in one short sentence.",
    )
    t0 = _t.perf_counter()
    desc = agent.run_sync(
        f"In one sentence, when would an LLM agent use a tool called '{add_numbers.name}' "
        f"that {add_numbers.description}?"
    )
    dt = _t.perf_counter() - t0
    print(
        f"  [model call: {dt:.2f}s · "
        f"{desc.metrics.prompt_tokens}{desc.metrics.completion_tokens} tokens]"
    )
    print(f"  AI commentary: {desc.message.strip()}")
    print()


# =============================================================================
# Part 2: Agent Using Tools
# =============================================================================


def example_agent_with_tools():
    """Give tools to an agent."""
    print("=== Part 2: Agent Using Tools ===\n")

    model = get_model(max_tokens=200)

    # Pass tools to the agent
    agent = Agent(
        model=model,
        tools=[add_numbers, multiply_numbers],
        system_prompt="You are a calculator assistant. Use the provided tools to perform calculations.",
    )

    print(f"Agent has {len(agent.tools)} tools registered")

    # Ask the agent to use a tool
    result = agent.run_sync("What is 15 + 27?")
    print("\nQ: What is 15 + 27?")
    print(f"A: {result.message}")
    print(f"Tool calls made: {result.metrics.tool_calls}")
    print()


# =============================================================================
# Part 3: More Complex Tools
# =============================================================================


@tool
def get_current_time() -> str:
    """Get the current date and time."""
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


@tool
def calculate_age(birth_year: int) -> str:
    """Calculate someone's age given their birth year."""
    current_year = datetime.now().year
    age = current_year - birth_year
    return f"A person born in {birth_year} is {age} years old."


@tool
def format_greeting(name: str, formal: bool = False) -> str:
    """Create a greeting for someone.

    Args:
        name: The person's name
        formal: Whether to use formal greeting (default: False)
    """
    if formal:
        return f"Good day, {name}. It is a pleasure to meet you."
    return f"Hey {name}! Nice to meet you!"


def example_complex_tools():
    """Use more complex tools with optional parameters."""
    print("=== Part 3: Complex Tools ===\n")

    model = get_model(max_tokens=200)

    agent = Agent(
        model=model,
        tools=[get_current_time, calculate_age, format_greeting],
        system_prompt="You are a helpful assistant with access to time and greeting tools.",
    )

    # Test different tools
    prompts = [
        "What time is it right now?",
        "How old would someone born in 1990 be?",
        "Give me a formal greeting for Dr. Smith",
    ]

    for prompt in prompts:
        result = agent.run_sync(prompt)
        print(f"Q: {prompt}")
        print(f"A: {result.message}")
        print()


# =============================================================================
# Part 4: Watching Tool Execution Events
# =============================================================================


async def example_tool_events():
    """Watch events as tools are executed."""
    print("=== Part 4: Tool Execution Events ===\n")

    model = get_model(max_tokens=200)

    agent = Agent(
        model=model,
        tools=[add_numbers, multiply_numbers],
        system_prompt="Use tools to calculate. Always use tools for math.",
    )

    print("Q: What is (5 + 3) * 2?\n")
    print("Events:")

    async for event in agent.run("What is (5 + 3) * 2?"):
        event_type = event.event_type

        if event_type == "tool_start":
            print(f"  TOOL_START: {event.tool_name}({event.arguments})")
        elif event_type == "tool_complete":
            print(f"  TOOL_COMPLETE: {event.tool_name} -> {event.result}")
        elif event_type == "think":
            if event.tool_calls:
                print(f"  THINK: Planning to call {len(event.tool_calls)} tool(s)")
        elif event_type == "terminate":
            print(f"  TERMINATE: {event.reason}")
            if event.final_message:
                print(f"\nFinal Answer: {event.final_message}")

    print()


# =============================================================================
# Part 5: Tools That Return Structured Data
# =============================================================================


@tool
def search_products(query: str, max_results: int = 3) -> list[dict]:
    """Search for products in the catalog.

    Args:
        query: Search query
        max_results: Maximum number of results to return
    """
    # In-memory product catalogue. Real apps swap this for a DB query;
    # the tutorial keeps the data inline so the search logic is the
    # whole story.
    products = [
        {"id": 1, "name": "Laptop", "price": 999.99, "category": "electronics", "in_stock": True},
        {
            "id": 2,
            "name": "Headphones",
            "price": 149.99,
            "category": "electronics",
            "in_stock": True,
        },
        {"id": 3, "name": "Mouse", "price": 49.99, "category": "electronics", "in_stock": True},
        {"id": 4, "name": "Keyboard", "price": 79.99, "category": "electronics", "in_stock": False},
        {"id": 5, "name": "Monitor", "price": 299.99, "category": "electronics", "in_stock": True},
        {"id": 6, "name": "Webcam", "price": 89.99, "category": "electronics", "in_stock": True},
        {
            "id": 7,
            "name": "Standing Desk",
            "price": 449.99,
            "category": "furniture",
            "in_stock": True,
        },
        {
            "id": 8,
            "name": "Office Chair",
            "price": 329.99,
            "category": "furniture",
            "in_stock": False,
        },
    ]

    # Real search — match on name OR category, case-insensitive, with
    # in-stock filtering.
    q = query.lower()
    matches = [p for p in products if q in p["name"].lower() or q in p["category"].lower()]
    return matches[:max_results]


@tool
def get_product_details(product_id: int) -> dict:
    """Get detailed information about a specific product."""
    details = {
        1: {
            "id": 1,
            "name": "Laptop",
            "price": 999.99,
            "specs": '16GB RAM, 512GB SSD, 14" 2.8K display',
        },
        2: {
            "id": 2,
            "name": "Headphones",
            "price": 149.99,
            "specs": "Noise-canceling, 40h battery, USB-C",
        },
        3: {
            "id": 3,
            "name": "Mouse",
            "price": 49.99,
            "specs": "Wireless, 16k DPI, programmable buttons",
        },
        4: {"id": 4, "name": "Keyboard", "price": 79.99, "specs": "Mechanical, hot-swappable, RGB"},
        5: {"id": 5, "name": "Monitor", "price": 299.99, "specs": '27" 4K IPS, 144Hz, USB-C 90W'},
        6: {"id": 6, "name": "Webcam", "price": 89.99, "specs": "1080p60, dual mic, auto-framing"},
        7: {
            "id": 7,
            "name": "Standing Desk",
            "price": 449.99,
            "specs": "Sit-stand, 60×30, programmable presets",
        },
        8: {
            "id": 8,
            "name": "Office Chair",
            "price": 329.99,
            "specs": "Lumbar support, adjustable arms",
        },
    }
    return details.get(product_id, {"error": f"Product {product_id} not found"})


def example_structured_tools():
    """Tools that return complex data structures."""
    print("=== Part 5: Structured Data Tools ===\n")

    model = get_model(max_tokens=300)

    agent = Agent(
        model=model,
        tools=[search_products, get_product_details],
        system_prompt="You are a shopping assistant. Help users find products.",
    )

    result = agent.run_sync("Find me some electronics, then tell me about the laptop")
    print("Q: Find me some electronics, then tell me about the laptop")
    print(f"A: {result.message}")
    print(f"\nTool calls made: {result.metrics.tool_calls}")
    print()


# =============================================================================
# Main
# =============================================================================


def main():
    """Run all tutorial parts."""
    print("=" * 60)
    print("Tutorial 02: Agent with Tools")
    print("=" * 60)
    print()

    print_config()
    print()

    example_simple_tools()
    example_agent_with_tools()
    example_complex_tools()
    asyncio.run(example_tool_events())
    example_structured_tools()

    print("=" * 60)
    print("Next: Tutorial 03 - Agent Memory & Checkpointing")
    print("=" * 60)


if __name__ == "__main__":
    main()