Lab L1.7: Build a Lookup Function

🎯 Assignment: Accept this lab on GitHub Classroom
You’ll get your own repository with starter code, instructions, and automatic grading.

   
Duration 60 minutes
Prerequisites Previous module completed

Objectives

  • Create SWAIG functions with parameters
  • Test functions using swaig-test
  • Make live calls that use functions
  • Handle function results properly

How to Complete This Lab

  1. Accept the Assignment — Click the GitHub Classroom link above
  2. Clone Your Repo — git clone <your-repo-url>
  3. Read the README — Your repo has detailed requirements and grading criteria
  4. Write Your Code — Implement the solution in solution/agent.py
  5. Test Locally — Use swaig-test to verify your agent works
  6. Push to Submit — git push triggers auto-grading

Key Concepts

The following exercises walk through the concepts you’ll need. Your GitHub Classroom repo README has the specific requirements for grading.

Scenario

You’re building an Order Status Agent that helps customers check their order status. The agent needs a function to look up orders.


Exercise 1: Create Order Agent

Create order_agent.py:

#!/usr/bin/env python3
"""Order status agent with lookup function."""

import os
from signalwire_agents import AgentBase, SwaigFunctionResult

agent = AgentBase(
    name="order-agent",
    route="/orders"
)

# Authentication
agent.set_params({
    "swml_basic_auth_user": os.getenv("AUTH_USER", "signalwire"),
    "swml_basic_auth_password": os.getenv("AUTH_PASS", "training123"),
})

# Prompt
agent.prompt_add_section(
    "Role",
    "You are an order status assistant. Help customers check "
    "the status of their orders. Ask for their order number "
    "to look it up."
)

agent.prompt_add_section(
    "Process",
    bullets=[
        "Greet the customer warmly",
        "Ask for their order number",
        "Use the lookup function to find the order",
        "Tell them the status clearly",
        "Ask if there's anything else"
    ]
)

# Voice
agent.add_language(
    "English", "en-US", "rime.spore",
    speech_fillers=["Um", "Uh", "Well"],
    function_fillers=["Looking that up now...", "One moment please..."]
)

if __name__ == "__main__":
    agent.run()

Test:

swaig-test order_agent.py --list-tools

Expected: No tools listed yet.


Exercise 2: Add Lookup Function

Add a function to look up orders:

Add to order_agent.py before if __name__:

# Mock order database
ORDERS = {
    "12345": {"status": "shipped", "date": "Monday", "carrier": "FedEx"},
    "67890": {"status": "processing", "date": "tomorrow", "carrier": "UPS"},
    "11111": {"status": "delivered", "date": "last Friday", "carrier": "USPS"},
}

@agent.tool(
    description="Look up an order by order number",
    parameters={
        "type": "object",
        "properties": {
            "order_number": {
                "type": "string",
                "description": "The order number to look up"
            }
        },
        "required": ["order_number"]
    }
)
def get_order_status(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    """Look up order status."""

    # Clean the input
    order_number = args.get("order_number", "").strip()

    # Look up in mock database
    if order_number in ORDERS:
        order = ORDERS[order_number]
        return SwaigFunctionResult(
            f"Order {order_number} status is {order['status']}. "
            f"It was shipped via {order['carrier']} and "
            f"{'was delivered' if order['status'] == 'delivered' else 'is expected'} "
            f"{order['date']}."
        )
    else:
        return SwaigFunctionResult(
            f"I couldn't find order number {order_number}. "
            "Please check the number and try again."
        )

Exercise 3: Test Function Listing

swaig-test order_agent.py --list-tools

Expected Output:

Available tools:
  get_order_status - Look up an order by order number
    Parameters:
      order_number (string, required)

Exercise 4: Test Function Execution

Test with valid order:

swaig-test order_agent.py --exec get_order_status --order_number "12345"

Expected Output:

Executing function: get_order_status
Arguments: {"order_number": "12345"}
Result: Order 12345 status is shipped. It was shipped via FedEx and is expected Monday.

Test with invalid order:

swaig-test order_agent.py --exec get_order_status --order_number "99999"

Expected: Error message about order not found.


Exercise 5: Examine SWML

swaig-test order_agent.py --dump-swml | grep -A 30 '"SWAIG"'

Find:

  • Function name
  • Description
  • Parameters schema
  • Webhook URL

Exercise 6: Add Second Function

Add a function to list recent orders by email:

# Mock customer database
CUSTOMERS = {
    "john@example.com": ["12345", "67890"],
    "jane@example.com": ["11111"],
}

@agent.tool(
    description="Find orders for a customer by their email address",
    fillers=["Searching for your orders...", "Let me find those..."],
    parameters={
        "type": "object",
        "properties": {
            "email": {
                "type": "string",
                "description": "Customer email address"
            }
        },
        "required": ["email"]
    }
)
def find_orders_by_email(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    """Find orders by customer email."""

    email = args.get("email", "").lower().strip()

    if email in CUSTOMERS:
        orders = CUSTOMERS[email]
        if len(orders) == 1:
            return SwaigFunctionResult(
                f"I found 1 order for {email}: order number {orders[0]}."
            )
        else:
            order_list = ", ".join(orders[:-1]) + f" and {orders[-1]}"
            return SwaigFunctionResult(
                f"I found {len(orders)} orders for {email}: {order_list}."
            )
    else:
        return SwaigFunctionResult(
            f"I couldn't find any orders for {email}. "
            "Please check the email address."
        )

Update the prompt to mention email lookup:

agent.prompt_add_section(
    "Process",
    bullets=[
        "Greet the customer warmly",
        "Ask if they have an order number, or ask for their email",
        "Use the appropriate function to find their order",
        "Tell them the status clearly",
        "Ask if there's anything else"
    ]
)

Exercise 7: Test Both Functions

# List all tools
swaig-test order_agent.py --list-tools

# Test order lookup
swaig-test order_agent.py --exec get_order_status --order_number "12345"

# Test email lookup
swaig-test order_agent.py --exec find_orders_by_email --email "john@example.com"

Exercise 8: Live Call Test

  1. Start the agent:

    python order_agent.py
    
  2. Start ngrok:

    ngrok http 3000
    
  3. Update SignalWire URL (change /agent to /orders)

  4. Call your number and try:

    • “What’s the status of order 12345?”
    • “Can you look up orders for john@example.com?”
    • “Check order 99999” (invalid)

Exercise 9: Add Parameter Validation

Improve the order lookup with validation:

@agent.tool(
    description="Look up an order by order number (5 digits)",
    parameters={
        "type": "object",
        "properties": {
            "order_number": {
                "type": "string",
                "description": "The 5-digit order number",
                "pattern": "^[0-9]{5}$"
            }
        },
        "required": ["order_number"]
    }
)
def get_order_status(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    order_number = args.get("order_number", "").strip()
    # ... existing code ...

Challenge Exercise

Add a third function to cancel an order:

Requirements:

  • Only allow cancellation if status is “processing”
  • Return appropriate message for other statuses
  • Add confirmation in the response
@agent.tool(
    description="Cancel an order if it hasn't shipped yet",
    parameters={
        "type": "object",
        "properties": {
            "order_number": {
                "type": "string",
                "description": "The order number to cancel"
            }
        },
        "required": ["order_number"]
    }
)
def cancel_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    order_number = args.get("order_number", "").strip()
    # Your implementation here
    pass

Complete Agent Code

Click to reveal complete solution
#!/usr/bin/env python3
"""Complete order status agent."""

import os
from signalwire_agents import AgentBase, SwaigFunctionResult

agent = AgentBase(name="order-agent", route="/orders")

agent.set_params({
    "swml_basic_auth_user": os.getenv("AUTH_USER", "signalwire"),
    "swml_basic_auth_password": os.getenv("AUTH_PASS", "training123"),
})

agent.prompt_add_section(
    "Role",
    "You are an order status assistant. Help customers check "
    "and manage their orders."
)

agent.prompt_add_section(
    "Process",
    bullets=[
        "Greet the customer warmly",
        "Ask if they have an order number, or ask for their email",
        "Use the appropriate function to find or check their order",
        "Tell them the status clearly",
        "Ask if there's anything else"
    ]
)

agent.add_language(
    "English", "en-US", "rime.spore",
    speech_fillers=["Um", "Uh", "Well"],
    function_fillers=["Looking that up now...", "One moment please..."]
)

# Mock databases
ORDERS = {
    "12345": {"status": "shipped", "date": "Monday", "carrier": "FedEx"},
    "67890": {"status": "processing", "date": "tomorrow", "carrier": "UPS"},
    "11111": {"status": "delivered", "date": "last Friday", "carrier": "USPS"},
}

CUSTOMERS = {
    "john@example.com": ["12345", "67890"],
    "jane@example.com": ["11111"],
}


@agent.tool(
    description="Look up an order by order number",
    parameters={
        "type": "object",
        "properties": {
            "order_number": {
                "type": "string",
                "description": "The order number to look up"
            }
        },
        "required": ["order_number"]
    }
)
def get_order_status(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    order_number = args.get("order_number", "").strip()
    if order_number in ORDERS:
        order = ORDERS[order_number]
        return SwaigFunctionResult(
            f"Order {order_number} status is {order['status']}. "
            f"Shipped via {order['carrier']}, "
            f"{'delivered' if order['status'] == 'delivered' else 'expected'} "
            f"{order['date']}."
        )
    return SwaigFunctionResult(f"Order {order_number} not found.")


@agent.tool(
    description="Find orders for a customer by email",
    fillers=["Searching for your orders..."],
    parameters={
        "type": "object",
        "properties": {
            "email": {
                "type": "string",
                "description": "Customer email address"
            }
        },
        "required": ["email"]
    }
)
def find_orders_by_email(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    email = args.get("email", "").lower().strip()
    if email in CUSTOMERS:
        orders = CUSTOMERS[email]
        return SwaigFunctionResult(
            f"Found {len(orders)} order(s) for {email}: {', '.join(orders)}."
        )
    return SwaigFunctionResult(f"No orders found for {email}.")


@agent.tool(
    description="Cancel an order if it hasn't shipped",
    parameters={
        "type": "object",
        "properties": {
            "order_number": {
                "type": "string",
                "description": "The order number to cancel"
            }
        },
        "required": ["order_number"]
    }
)
def cancel_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    order_number = args.get("order_number", "").strip()
    if order_number not in ORDERS:
        return SwaigFunctionResult(f"Order {order_number} not found.")

    order = ORDERS[order_number]
    if order["status"] == "processing":
        return SwaigFunctionResult(
            f"Order {order_number} has been cancelled. "
            "You'll receive a confirmation email."
        )
    elif order["status"] == "shipped":
        return SwaigFunctionResult(
            f"Order {order_number} has already shipped and cannot be cancelled. "
            "Would you like information about returns instead?"
        )
    else:
        return SwaigFunctionResult(
            f"Order {order_number} has already been delivered."
        )


if __name__ == "__main__":
    agent.run()

Deliverables

  1. order_agent.py with all three functions
  2. Screenshot of swaig-test –list-tools output
  3. Notes from live call testing

Review Questions

  1. What does the @tool decorator do?
  2. How does the AI know when to call a function?
  3. What must every function return?
  4. How do you test functions without making calls?
  5. What are fillers used for?

Summary

You have successfully:

  1. Created SWAIG functions with parameters
  2. Tested functions using swaig-test CLI
  3. Made live calls that invoke functions
  4. Handled various response scenarios

Next: Module 1.8 - Certification Assessment


Back to top

SignalWire AI Agents Certification Program