Skip to main content

Tools & Function Calling

Tools let your voice agent perform actions during a conversation — check a database, call an API, transfer a call, or anything else you can expose via a webhook or an in-process handler.

Defining Tools

Each tool is a dictionary with the following fields:
FieldTypeRequiredDescription
namestrYesUnique identifier for the tool.
descriptionstrYesNatural language description of what the tool does. The AI uses this to decide when to call it.
parametersdictYesJSON Schema defining the tool’s input parameters.
webhook_urlstrOne of webhook_url or handlerURL that Patter POSTs to when the AI invokes this tool.
handlerCallableOne of webhook_url or handlerAsync or sync callable `(arguments, context) -> strdict`. Runs in-process instead of making an HTTP call.
Every tool must have either a webhook_url or a handler. Providing neither raises a ValueError.
agent = phone.agent(
    system_prompt="You are a restaurant reservation assistant.",
    tools=[
        {
            "name": "check_availability",
            "description": "Check if a table is available for a given date, time, and party size.",
            "parameters": {
                "type": "object",
                "properties": {
                    "date": {
                        "type": "string",
                        "description": "Reservation date in YYYY-MM-DD format",
                    },
                    "time": {
                        "type": "string",
                        "description": "Reservation time in HH:MM format",
                    },
                    "party_size": {
                        "type": "integer",
                        "description": "Number of guests",
                    },
                },
                "required": ["date", "time", "party_size"],
            },
            "webhook_url": "https://api.example.com/reservations/check",
        },
        {
            "name": "make_reservation",
            "description": "Book a table reservation.",
            "parameters": {
                "type": "object",
                "properties": {
                    "date": {"type": "string"},
                    "time": {"type": "string"},
                    "party_size": {"type": "integer"},
                    "customer_name": {"type": "string"},
                    "phone": {"type": "string"},
                },
                "required": ["date", "time", "party_size", "customer_name"],
            },
            "webhook_url": "https://api.example.com/reservations/create",
        },
    ],
)

Webhook Request Format

When the AI decides to call a tool, Patter sends an HTTP POST to the webhook_url with the following JSON body:
{
  "tool": "check_availability",
  "arguments": {
    "date": "2025-03-15",
    "time": "19:00",
    "party_size": 4
  },
  "call_id": "call_abc123",
  "caller": "+15550001234",
  "callee": "+15550009876",
  "attempt": 1
}
FieldTypeDescription
toolstrThe name of the tool being invoked.
argumentsdictThe arguments extracted by the AI, matching the JSON Schema.
call_idstrUnique identifier for the current call.
callerstrThe caller’s phone number.
calleestrThe callee’s phone number.
attemptintAttempt number (1, 2, or 3).

Webhook Response

Your webhook must return valid JSON. The response is passed back to the AI as the tool result:
{
  "available": true,
  "tables": [
    {"id": "T5", "seats": 4, "location": "patio"},
    {"id": "T12", "seats": 6, "location": "main"}
  ]
}

Response Requirements

ConstraintValue
Content typeapplication/json
Max response size1 MB
TimeoutStandard HTTP timeout

Retry Behavior

If your webhook fails (non-2xx status code or network error), Patter retries automatically:
  • Max retries: 2 (3 total attempts)
  • Retry delay: 0.5 seconds between attempts
  • Final failure: If all 3 attempts fail, the AI receives an error message and continues the conversation gracefully.

System Tools

Patter automatically injects two system tools into every agent. You do not need to define these — they are always available.

transfer_call

Transfers the current call to another phone number. The AI decides when to trigger this based on the conversation context.
ParameterTypeDescription
numberstrPhone number to transfer to (E.164 format).
The AI might say: “Let me transfer you to our billing department.” and then invoke transfer_call with the appropriate number.

end_call

Ends the current call programmatically.
ParameterTypeDescription
reasonstrReason for ending the call (logged in the transcript).

Using Patter.tool()

The Patter.tool() static method provides a convenient way to create tool definitions:
agent = phone.agent(
    system_prompt="You are a helpful assistant.",
    tools=[
        Patter.tool(
            name="get_order_status",
            description="Look up an order by ID.",
            parameters={
                "type": "object",
                "properties": {
                    "order_id": {"type": "string", "description": "Order ID"},
                },
                "required": ["order_id"],
            },
            webhook_url="https://api.example.com/orders/status",
        ),
    ],
)

In-Process Handlers

Instead of webhook URLs, you can pass a Python callable that runs in-process. This is useful for tools that query local databases, call internal APIs, or perform any logic without an external HTTP endpoint.
async def check_inventory(arguments, context):
    product_id = arguments["product_id"]
    # Your custom logic here
    stock = await db.get_stock(product_id)
    return {"product_id": product_id, "in_stock": stock > 0, "quantity": stock}

agent = phone.agent(
    system_prompt="You are a product specialist.",
    tools=[
        Patter.tool(
            name="check_inventory",
            description="Check if a product is in stock.",
            parameters={
                "type": "object",
                "properties": {
                    "product_id": {"type": "string", "description": "Product ID"},
                },
                "required": ["product_id"],
            },
            handler=check_inventory,
        ),
    ],
)
The handler receives two arguments:
  • arguments — A dict of the arguments extracted by the AI
  • context — A dict with call metadata (call_id, caller, callee)
The handler must return a str or dict. Dict values are serialized to JSON before being passed to the AI.

Tool Design Tips

The AI uses the description field to decide when to call a tool. Be specific:
# Good
"description": "Look up the customer's order status by order ID. Returns tracking number and estimated delivery date."

# Bad
"description": "Check order."
Parameter names and descriptions help the AI extract the right values from the conversation:
"properties": {
    "order_id": {
        "type": "string",
        "description": "The order ID, usually starts with 'ORD-' followed by 6 digits",
    },
}
The AI processes the full JSON response. Large responses add latency. Return only what the AI needs to continue the conversation.

Complete Example

import os
import asyncio
from dotenv import load_dotenv
from patter import Patter

load_dotenv()

phone = Patter(
    twilio_sid=os.environ["TWILIO_SID"],
    twilio_token=os.environ["TWILIO_TOKEN"],
    openai_key=os.environ["OPENAI_KEY"],
    phone_number=os.environ["PHONE_NUMBER"],
    webhook_url=os.environ["WEBHOOK_URL"],
)

agent = phone.agent(
    system_prompt="""You are a customer service agent for an e-commerce store.
Help customers check order status and process returns.
Always ask for the order ID before looking anything up.""",
    tools=[
        {
            "name": "get_order_status",
            "description": "Look up an order by ID. Returns order status, items, and tracking info.",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "Order ID (e.g., ORD-123456)",
                    },
                },
                "required": ["order_id"],
            },
            "webhook_url": "https://api.example.com/orders/status",
        },
        {
            "name": "initiate_return",
            "description": "Start a return process for a specific order item.",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {"type": "string"},
                    "item_id": {"type": "string"},
                    "reason": {
                        "type": "string",
                        "enum": ["defective", "wrong_item", "changed_mind", "other"],
                    },
                },
                "required": ["order_id", "item_id", "reason"],
            },
            "webhook_url": "https://api.example.com/returns/initiate",
        },
    ],
    first_message="Hi! I'm here to help with your order. Do you have an order ID I can look up?",
)

async def main():
    await phone.serve(agent, port=8000)

asyncio.run(main())