Skip to content

Latest commit

 

History

History
288 lines (220 loc) · 7.1 KB

File metadata and controls

288 lines (220 loc) · 7.1 KB

Tools Guide

Tools (also called functions) allow your voice assistant to perform actions and retrieve information. When the model determines it needs external data or functionality, it calls your tool functions.

Basic Tool Definition

Use the @client.tool decorator to define tools:

from easy_sonic import Sonic

client = Sonic()

@client.tool
def get_weather(city: str) -> str:
    """Get the current weather for a city.

    Args:
        city: The name of the city.
    """
    # Your implementation here
    return f"Sunny and 72°F in {city}"

await client.chat("You are a weather assistant.")

How It Works

  1. The decorator extracts information from your function:

    • Name: From the function name
    • Description: From the docstring
    • Parameters: From type hints and docstring
  2. This generates a JSON schema that tells the model what the tool does

  3. When the model needs to use the tool, it calls your function with the appropriate arguments

  4. The result is sent back to the model to continue the conversation

Type Hints

Type hints are used to generate the parameter schema:

@client.tool
def search_products(
    query: str,              # Required string
    max_results: int = 10,   # Optional integer with default
    in_stock: bool = True,   # Optional boolean with default
) -> list:
    """Search for products."""
    ...

Supported types:

  • str → JSON string
  • int → JSON integer
  • float → JSON number
  • bool → JSON boolean
  • list → JSON array
  • dict → JSON object

Docstrings

Sonic extracts descriptions from docstrings. Both Google and reStructuredText styles work:

Google Style:

@client.tool
def book_flight(origin: str, destination: str, date: str) -> dict:
    """Book a flight between two cities.

    Args:
        origin: Departure city or airport code.
        destination: Arrival city or airport code.
        date: Travel date in YYYY-MM-DD format.
    """
    ...

reStructuredText Style:

@client.tool
def book_flight(origin: str, destination: str, date: str) -> dict:
    """Book a flight between two cities.

    :param origin: Departure city or airport code.
    :param destination: Arrival city or airport code.
    :param date: Travel date in YYYY-MM-DD format.
    """
    ...

Async Tools

Tools can be asynchronous for I/O operations:

@client.tool
async def fetch_stock_price(symbol: str) -> dict:
    """Get the current stock price.

    Args:
        symbol: Stock ticker symbol (e.g., AAPL).
    """
    async with aiohttp.ClientSession() as session:
        async with session.get(f"https://api.example.com/stocks/{symbol}") as response:
            return await response.json()

Custom Tool Names

Override the auto-generated name:

@client.tool(name="get_current_time")
def time_now() -> str:
    """Get the current time."""
    from datetime import datetime
    return datetime.now().strftime("%H:%M:%S")

Custom Descriptions

Override the docstring description:

@client.tool(description="Retrieves real-time weather data for any city worldwide")
def get_weather(city: str) -> dict:
    """Old description that will be overridden."""
    ...

Multiple Tools

Register multiple tools on the same client:

client = Sonic()

@client.tool
def get_weather(city: str) -> str:
    """Get weather for a city."""
    ...

@client.tool
def get_forecast(city: str, days: int = 3) -> list:
    """Get weather forecast."""
    ...

@client.tool
def set_reminder(message: str, time: str) -> dict:
    """Set a reminder."""
    ...

# All tools are available in the conversation
await client.chat("You are a helpful assistant with weather and reminder capabilities.")

Tool Callbacks

Monitor tool usage with callbacks:

def on_tool_call(name: str, args: dict) -> None:
    print(f"🔧 Calling {name} with {args}")

def on_tool_result(name: str, result) -> None:
    print(f"✅ {name} returned: {result}")

await client.chat(
    system_prompt="You are helpful.",
    on_tool_call=on_tool_call,
    on_tool_result=on_tool_result,
)

Error Handling

If a tool raises an exception, the error is sent to the model as the result:

@client.tool
def risky_operation(param: str) -> str:
    """A tool that might fail."""
    if not param:
        raise ValueError("Parameter cannot be empty")
    return "Success"

The model receives the error and can explain it to the user or try a different approach.

Practical Examples

Database Query Tool

@client.tool
async def query_customers(
    name: str = None,
    email: str = None,
    limit: int = 10
) -> list:
    """Search for customers in the database.

    Args:
        name: Filter by customer name (partial match).
        email: Filter by email address.
        limit: Maximum number of results.
    """
    async with get_db_connection() as conn:
        query = "SELECT * FROM customers WHERE 1=1"
        params = []

        if name:
            query += " AND name ILIKE %s"
            params.append(f"%{name}%")
        if email:
            query += " AND email = %s"
            params.append(email)

        query += f" LIMIT {limit}"
        return await conn.fetch(query, *params)

API Integration Tool

import httpx

@client.tool
async def send_email(to: str, subject: str, body: str) -> dict:
    """Send an email to a recipient.

    Args:
        to: Recipient email address.
        subject: Email subject line.
        body: Email body content.
    """
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://api.email-service.com/send",
            json={"to": to, "subject": subject, "body": body},
            headers={"Authorization": f"Bearer {API_KEY}"}
        )
        return response.json()

Calculator Tool

import math

@client.tool
def calculate(expression: str) -> dict:
    """Evaluate a mathematical expression.

    Args:
        expression: A math expression like "2 + 2" or "sqrt(16)".
    """
    # Safe evaluation with limited functions
    safe_dict = {
        "abs": abs, "round": round, "min": min, "max": max,
        "sqrt": math.sqrt, "sin": math.sin, "cos": math.cos,
        "pi": math.pi, "e": math.e,
    }

    try:
        result = eval(expression, {"__builtins__": {}}, safe_dict)
        return {"expression": expression, "result": result}
    except Exception as e:
        return {"expression": expression, "error": str(e)}

Best Practices

  1. Clear descriptions - Write detailed docstrings so the model knows when to use each tool
  2. Type everything - Use type hints for all parameters and return values
  3. Handle errors gracefully - Return informative error messages instead of crashing
  4. Keep tools focused - One tool should do one thing well
  5. Validate inputs - Check parameters before performing operations
  6. Use async for I/O - Network calls and database queries should be async

Next Steps