Skip to main content

SWAIG Functions

Summary: SWAIG (SignalWire AI Gateway) functions let your AI agent call custom code to look up data, make API calls, and take actions during conversations.

What You'll Learn

This chapter covers everything about SWAIG functions:

  1. Defining Functions - Creating functions the AI can call
  2. Parameters - Accepting arguments from the AI
  3. Results & Actions - Returning data and triggering actions
  4. DataMap - Serverless API integration without webhooks
  5. Native Functions - Built-in SignalWire functions

How SWAIG Functions Work

┌─────────────────────────────────────────────────────────────────────────────┐
│ SWAIG Function Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Caller speaks │
│ "What's my order status for order 12345?" │
│ │ │
│ ▼ │
│ 2. AI decides to call function │
│ ┌─────────────────────────────────────────────┐ │
│ │ AI: "I'll look that up using check_order" │ │
│ │ Function: check_order │ │
│ │ Args: {"order_number": "12345"} │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. SignalWire calls your webhook │
│ POST https://your-server.com/swaig │
│ {"function": "check_order", "args": {...}} │
│ │ │
│ ▼ │
│ 4. Your handler returns result │
│ ┌─────────────────────────────────────────────┐ │
│ │ SwaigFunctionResult( │ │
│ │ "Order 12345 shipped Monday, │ │
│ │ arriving Thursday" │ │
│ │ ) │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 5. AI speaks result to caller │
│ "Your order 12345 shipped Monday and │
│ will arrive Thursday." │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

Quick Start Example

Here's a complete agent with a SWAIG function:

from signalwire_agents import AgentBase, SwaigFunctionResult

class OrderAgent(AgentBase):
def __init__(self):
super().__init__(name="order-agent")
self.add_language("English", "en-US", "rime.spore")

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

# Define a function the AI can call
self.define_tool(
name="check_order",
description="Look up order status by order number",
parameters={
"type": "object",
"properties": {
"order_number": {
"type": "string",
"description": "The order number to look up"
}
},
"required": ["order_number"]
},
handler=self.check_order
)

def check_order(self, args, raw_data):
order_number = args.get("order_number")

# Your business logic here - database lookup, API call, etc.
orders = {
"12345": "Shipped Monday, arriving Thursday",
"67890": "Processing, ships tomorrow"
}

status = orders.get(order_number, "Order not found")
return SwaigFunctionResult(f"Order {order_number}: {status}")

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

Function Types

TypeDescription
Handler FunctionsDefined with define_tool(). Python handler runs on your server with full control over logic, database access, and API calls.
DataMap FunctionsServerless API integration that runs on SignalWire's servers. No webhook endpoint needed - direct REST API calls.
Native FunctionsBuilt into SignalWire. No custom code required - handles transfer, recording, etc.

Chapter Contents

SectionDescription
Defining FunctionsCreating SWAIG functions with define_tool()
ParametersDefining and validating function parameters
Results & ActionsReturning results and triggering actions
DataMapServerless API integration
Native FunctionsBuilt-in SignalWire functions

When to Use SWAIG Functions

Use CaseApproach
Database lookupsHandler function
Complex business logicHandler function
Simple REST API callsDataMap
Pattern-based responsesDataMap expressions
Call transfersNative function or SwaigFunctionResult.connect()
SMS sendingSwaigFunctionResult.send_sms()

Key Concepts

Handler Functions: Python code that runs on your server when the AI decides to call a function. You have full access to databases, APIs, and any Python library.

SwaigFunctionResult: The return type for all SWAIG functions. Contains the response text the AI will speak and optional actions to execute.

Parameters: JSON Schema definitions that tell the AI what arguments your function accepts. The AI will extract these from the conversation.

Actions: Side effects like call transfers, SMS sending, or context changes that execute after the function completes.

DataMap: A way to define functions that call REST APIs without running any code on your server - the API calls happen directly on SignalWire's infrastructure.

Let's start by learning how to define functions.

Basic Function Definition

from signalwire_agents import AgentBase, SwaigFunctionResult


class MyAgent(AgentBase):
def __init__(self):
super().__init__(name="my-agent")
self.add_language("English", "en-US", "rime.spore")

# Define a function
self.define_tool(
name="get_weather",
description="Get current weather for a city",
parameters={
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name"
}
},
"required": ["city"]
},
handler=self.get_weather
)

def get_weather(self, args, raw_data):
city = args.get("city")
# Your logic here
return SwaigFunctionResult(f"The weather in {city} is sunny, 72 degrees")

The define_tool() Method

Required Parameters:

ParameterDescription
nameUnique function name (lowercase, underscores)
descriptionWhat the function does (helps AI decide when to use)
parametersJSON Schema defining accepted arguments
handlerPython function to call

Optional Parameters:

ParameterDescription
secureRequire token validation (default: True)
fillersLanguage-specific filler phrases
webhook_urlExternal webhook URL (overrides local handler)
requiredList of required parameter names

Handler Function Signature

All handlers receive two arguments:

def my_handler(self, args, raw_data):
"""
Args:
args: Dictionary of parsed function arguments
{"city": "New York", "units": "fahrenheit"}

raw_data: Full request data including:
- call_id: Unique call identifier
- caller_id_num: Caller's phone number
- caller_id_name: Caller's name
- called_id_num: Number that was called
- And more...

Returns:
SwaigFunctionResult with response text and optional actions
"""
return SwaigFunctionResult("Response text")

Accessing Call Data

def check_account(self, args, raw_data):
# Get caller information
caller_number = raw_data.get("caller_id_num", "")
call_id = raw_data.get("call_id", "")

# Get function arguments
account_id = args.get("account_id")

# Use both for your logic
return SwaigFunctionResult(
f"Account {account_id} for caller {caller_number} is active"
)

Multiple Functions

Register as many functions as your agent needs:

class CustomerServiceAgent(AgentBase):
def __init__(self):
super().__init__(name="customer-service")
self.add_language("English", "en-US", "rime.spore")

# Order lookup
self.define_tool(
name="check_order",
description="Look up order status by order number",
parameters={
"type": "object",
"properties": {
"order_number": {
"type": "string",
"description": "The order number"
}
},
"required": ["order_number"]
},
handler=self.check_order
)

# Account balance
self.define_tool(
name="get_balance",
description="Get account balance for a customer",
parameters={
"type": "object",
"properties": {
"account_id": {
"type": "string",
"description": "Customer account ID"
}
},
"required": ["account_id"]
},
handler=self.get_balance
)

# Store hours
self.define_tool(
name="get_store_hours",
description="Get store hours for a location",
parameters={
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "Store location or city"
}
},
"required": ["location"]
},
handler=self.get_store_hours
)

def check_order(self, args, raw_data):
order_number = args.get("order_number")
return SwaigFunctionResult(f"Order {order_number} is in transit")

def get_balance(self, args, raw_data):
account_id = args.get("account_id")
return SwaigFunctionResult(f"Account {account_id} balance: $150.00")

def get_store_hours(self, args, raw_data):
location = args.get("location")
return SwaigFunctionResult(f"{location} store: Mon-Fri 9AM-9PM, Sat-Sun 10AM-6PM")

Function Fillers

Add per-function filler phrases for when the function is executing:

self.define_tool(
name="search_inventory",
description="Search product inventory",
parameters={
"type": "object",
"properties": {
"product": {"type": "string", "description": "Product to search"}
},
"required": ["product"]
},
handler=self.search_inventory,
fillers={
"en-US": [
"Let me check our inventory...",
"Searching our stock now...",
"One moment while I look that up..."
],
"es-MX": [
"Dejame revisar nuestro inventario...",
"Buscando en nuestro stock..."
]
}
)

The @tool Decorator

Alternative syntax using decorators:

from signalwire_agents import AgentBase, SwaigFunctionResult


class MyAgent(AgentBase):
def __init__(self):
super().__init__(name="my-agent")
self.add_language("English", "en-US", "rime.spore")

@AgentBase.tool(
name="get_time",
description="Get the current time",
parameters={
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "Timezone (e.g., 'EST', 'PST')"
}
}
}
)
def get_time(self, args, raw_data):
timezone = args.get("timezone", "UTC")
return SwaigFunctionResult(f"The current time in {timezone} is 3:45 PM")

define_tool() vs @tool: Choosing an Approach

Both methods register SWAIG functions with the same result. Choose based on your coding style and requirements.

Comparison

Aspectdefine_tool()@tool Decorator
Where definedInside __init__At method definition
Dynamic registrationEasyRequires workarounds
Conditional functionsStraightforwardMore complex
Code organizationDefinition separate from handlerSelf-documenting
InheritanceEasier to overrideWorks but less flexible

When to Use define_tool()

Conditional function registration:

def __init__(self, enable_admin=False):
super().__init__(name="my-agent")

# Always available
self.define_tool(name="get_info", ...)

# Only for admin mode
if enable_admin:
self.define_tool(name="admin_reset", ...)

Dynamic functions from configuration:

def __init__(self, functions_config):
super().__init__(name="my-agent")

for func in functions_config:
self.define_tool(
name=func["name"],
description=func["description"],
parameters=func["params"],
handler=getattr(self, func["handler_name"])
)

Handlers defined outside the class:

def external_handler(agent, args, raw_data):
return SwaigFunctionResult("Handled externally")

class MyAgent(AgentBase):
def __init__(self):
super().__init__(name="my-agent")
self.define_tool(
name="external_func",
description="Uses external handler",
parameters={...},
handler=lambda args, raw: external_handler(self, args, raw)
)

When to Use @tool Decorator

Static, self-documenting functions:

class CustomerServiceAgent(AgentBase):
def __init__(self):
super().__init__(name="customer-service")
self.add_language("English", "en-US", "rime.spore")

@AgentBase.tool(
name="check_order",
description="Look up order status",
parameters={...}
)
def check_order(self, args, raw_data):
# Handler right here with its definition
return SwaigFunctionResult("...")

@AgentBase.tool(
name="get_balance",
description="Get account balance",
parameters={...}
)
def get_balance(self, args, raw_data):
return SwaigFunctionResult("...")

The decorator keeps the function metadata with the implementation, making it easier to see what a function does at a glance.

Mixing Both Approaches

You can use both in the same agent:

class HybridAgent(AgentBase):
def __init__(self, extra_functions=None):
super().__init__(name="hybrid")
self.add_language("English", "en-US", "rime.spore")

# Dynamic functions via define_tool
if extra_functions:
for func in extra_functions:
self.define_tool(**func)

# Static function via decorator
@AgentBase.tool(
name="get_help",
description="Get help information",
parameters={"type": "object", "properties": {}}
)
def get_help(self, args, raw_data):
return SwaigFunctionResult("How can I help you?")

External Webhook Functions

Route function calls to an external webhook:

self.define_tool(
name="external_lookup",
description="Look up data from external service",
parameters={
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"}
},
"required": ["query"]
},
handler=None, # No local handler
webhook_url="https://external-service.com/api/lookup"
)

Function Security

By default, functions require token validation. Disable for testing:

# Secure function (default)
self.define_tool(
name="secure_function",
description="Requires token validation",
parameters={"type": "object", "properties": {}},
handler=self.secure_handler,
secure=True # Default
)

# Insecure function (testing only)
self.define_tool(
name="test_function",
description="No token validation (testing only)",
parameters={"type": "object", "properties": {}},
handler=self.test_handler,
secure=False # Disable for testing
)

Writing Good Descriptions

The description helps the AI decide when to use your function:

# Good - specific and clear
description="Look up order status by order number. Returns shipping status and estimated delivery date."

# Bad - too vague
description="Get order info"

# Good - mentions what triggers it
description="Check if a product is in stock. Use when customer asks about availability."

# Good - explains constraints
description="Transfer call to human support. Only use if customer explicitly requests to speak with a person."

Testing Functions

Use swaig-test to test your functions:

# List all functions
swaig-test my_agent.py --list-tools

# Test a specific function
swaig-test my_agent.py --exec check_order --order_number 12345

# See the generated SWML
swaig-test my_agent.py --dump-swml

Complete Example

#!/usr/bin/env python3
# restaurant_agent.py - Restaurant order assistant
from signalwire_agents import AgentBase, SwaigFunctionResult


class RestaurantAgent(AgentBase):
MENU = {
"burger": {"price": 12.99, "description": "Angus beef burger with fries"},
"pizza": {"price": 14.99, "description": "12-inch cheese pizza"},
"salad": {"price": 9.99, "description": "Garden salad with dressing"}
}

def __init__(self):
super().__init__(name="restaurant-agent")
self.add_language("English", "en-US", "rime.spore")

self.prompt_add_section(
"Role",
"You are a friendly restaurant order assistant."
)

self.define_tool(
name="get_menu_item",
description="Get details about a menu item including price and description",
parameters={
"type": "object",
"properties": {
"item_name": {
"type": "string",
"description": "Name of the menu item"
}
},
"required": ["item_name"]
},
handler=self.get_menu_item,
fillers={
"en-US": ["Let me check the menu..."]
}
)

self.define_tool(
name="place_order",
description="Place an order for menu items",
parameters={
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {"type": "string"},
"description": "List of menu items to order"
},
"special_requests": {
"type": "string",
"description": "Any special requests or modifications"
}
},
"required": ["items"]
},
handler=self.place_order,
fillers={
"en-US": ["Placing your order now..."]
}
)

def get_menu_item(self, args, raw_data):
item_name = args.get("item_name", "").lower()
item = self.MENU.get(item_name)

if item:
return SwaigFunctionResult(
f"{item_name.title()}: {item['description']}. Price: ${item['price']}"
)
return SwaigFunctionResult(f"Sorry, {item_name} is not on our menu.")

def place_order(self, args, raw_data):
items = args.get("items", [])
special = args.get("special_requests", "")

total = sum(
self.MENU.get(item.lower(), {}).get("price", 0)
for item in items
)

if total > 0:
msg = f"Order placed: {', '.join(items)}. Total: ${total:.2f}"
if special:
msg += f" Special requests: {special}"
return SwaigFunctionResult(msg)

return SwaigFunctionResult("Could not place order. Please check item names.")


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