Defining Functions

View as Markdown

SWAIG Functions

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.
SWAIG Function Flow

Quick Start Example

Here’s a complete agent with a SWAIG function:

1from signalwire_agents import AgentBase, SwaigFunctionResult
2
3class OrderAgent(AgentBase):
4 def __init__(self):
5 super().__init__(name="order-agent")
6 self.add_language("English", "en-US", "rime.spore")
7
8 self.prompt_add_section(
9 "Role",
10 "You are an order status assistant. Help customers check their orders."
11 )
12
13 # Define a function the AI can call
14 self.define_tool(
15 name="check_order",
16 description="Look up order status by order number",
17 parameters={
18 "type": "object",
19 "properties": {
20 "order_number": {
21 "type": "string",
22 "description": "The order number to look up"
23 }
24 },
25 "required": ["order_number"]
26 },
27 handler=self.check_order
28 )
29
30 def check_order(self, args, raw_data):
31 order_number = args.get("order_number")
32
33 # Your business logic here - database lookup, API call, etc.
34 orders = {
35 "12345": "Shipped Monday, arriving Thursday",
36 "67890": "Processing, ships tomorrow"
37 }
38
39 status = orders.get(order_number, "Order not found")
40 return SwaigFunctionResult(f"Order {order_number}: {status}")
41
42if __name__ == "__main__":
43 agent = OrderAgent()
44 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

1from signalwire_agents import AgentBase, SwaigFunctionResult
2
3
4class MyAgent(AgentBase):
5 def __init__(self):
6 super().__init__(name="my-agent")
7 self.add_language("English", "en-US", "rime.spore")
8
9 # Define a function
10 self.define_tool(
11 name="get_weather",
12 description="Get current weather for a city",
13 parameters={
14 "type": "object",
15 "properties": {
16 "city": {
17 "type": "string",
18 "description": "City name"
19 }
20 },
21 "required": ["city"]
22 },
23 handler=self.get_weather
24 )
25
26 def get_weather(self, args, raw_data):
27 city = args.get("city")
28 # Your logic here
29 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:

1def my_handler(self, args, raw_data):
2 """
3 Args:
4 args: Dictionary of parsed function arguments
5 {"city": "New York", "units": "fahrenheit"}
6
7 raw_data: Full request data including:
8 - call_id: Unique call identifier
9 - caller_id_num: Caller's phone number
10 - caller_id_name: Caller's name
11 - called_id_num: Number that was called
12 - And more...
13
14 Returns:
15 SwaigFunctionResult with response text and optional actions
16 """
17 return SwaigFunctionResult("Response text")

Accessing Call Data

1def check_account(self, args, raw_data):
2 # Get caller information
3 caller_number = raw_data.get("caller_id_num", "")
4 call_id = raw_data.get("call_id", "")
5
6 # Get function arguments
7 account_id = args.get("account_id")
8
9 # Use both for your logic
10 return SwaigFunctionResult(
11 f"Account {account_id} for caller {caller_number} is active"
12 )

Multiple Functions

Register as many functions as your agent needs:

1class CustomerServiceAgent(AgentBase):
2 def __init__(self):
3 super().__init__(name="customer-service")
4 self.add_language("English", "en-US", "rime.spore")
5
6 # Order lookup
7 self.define_tool(
8 name="check_order",
9 description="Look up order status by order number",
10 parameters={
11 "type": "object",
12 "properties": {
13 "order_number": {
14 "type": "string",
15 "description": "The order number"
16 }
17 },
18 "required": ["order_number"]
19 },
20 handler=self.check_order
21 )
22
23 # Account balance
24 self.define_tool(
25 name="get_balance",
26 description="Get account balance for a customer",
27 parameters={
28 "type": "object",
29 "properties": {
30 "account_id": {
31 "type": "string",
32 "description": "Customer account ID"
33 }
34 },
35 "required": ["account_id"]
36 },
37 handler=self.get_balance
38 )
39
40 # Store hours
41 self.define_tool(
42 name="get_store_hours",
43 description="Get store hours for a location",
44 parameters={
45 "type": "object",
46 "properties": {
47 "location": {
48 "type": "string",
49 "description": "Store location or city"
50 }
51 },
52 "required": ["location"]
53 },
54 handler=self.get_store_hours
55 )
56
57 def check_order(self, args, raw_data):
58 order_number = args.get("order_number")
59 return SwaigFunctionResult(f"Order {order_number} is in transit")
60
61 def get_balance(self, args, raw_data):
62 account_id = args.get("account_id")
63 return SwaigFunctionResult(f"Account {account_id} balance: $150.00")
64
65 def get_store_hours(self, args, raw_data):
66 location = args.get("location")
67 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:

1self.define_tool(
2 name="search_inventory",
3 description="Search product inventory",
4 parameters={
5 "type": "object",
6 "properties": {
7 "product": {"type": "string", "description": "Product to search"}
8 },
9 "required": ["product"]
10 },
11 handler=self.search_inventory,
12 fillers={
13 "en-US": [
14 "Let me check our inventory...",
15 "Searching our stock now...",
16 "One moment while I look that up..."
17 ],
18 "es-MX": [
19 "Dejame revisar nuestro inventario...",
20 "Buscando en nuestro stock..."
21 ]
22 }
23)

The @tool Decorator

Alternative syntax using decorators:

1from signalwire_agents import AgentBase, SwaigFunctionResult
2
3
4class MyAgent(AgentBase):
5 def __init__(self):
6 super().__init__(name="my-agent")
7 self.add_language("English", "en-US", "rime.spore")
8
9 @AgentBase.tool(
10 name="get_time",
11 description="Get the current time",
12 parameters={
13 "type": "object",
14 "properties": {
15 "timezone": {
16 "type": "string",
17 "description": "Timezone (e.g., 'EST', 'PST')"
18 }
19 }
20 }
21 )
22 def get_time(self, args, raw_data):
23 timezone = args.get("timezone", "UTC")
24 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:

1def __init__(self, enable_admin=False):
2 super().__init__(name="my-agent")
3
4 # Always available
5 self.define_tool(name="get_info", ...)
6
7 # Only for admin mode
8 if enable_admin:
9 self.define_tool(name="admin_reset", ...)

Dynamic functions from configuration:

1def __init__(self, functions_config):
2 super().__init__(name="my-agent")
3
4 for func in functions_config:
5 self.define_tool(
6 name=func["name"],
7 description=func["description"],
8 parameters=func["params"],
9 handler=getattr(self, func["handler_name"])
10 )

Handlers defined outside the class:

1def external_handler(agent, args, raw_data):
2 return SwaigFunctionResult("Handled externally")
3
4class MyAgent(AgentBase):
5 def __init__(self):
6 super().__init__(name="my-agent")
7 self.define_tool(
8 name="external_func",
9 description="Uses external handler",
10 parameters={...},
11 handler=lambda args, raw: external_handler(self, args, raw)
12 )

When to Use @tool Decorator

Static, self-documenting functions:

1class CustomerServiceAgent(AgentBase):
2 def __init__(self):
3 super().__init__(name="customer-service")
4 self.add_language("English", "en-US", "rime.spore")
5
6 @AgentBase.tool(
7 name="check_order",
8 description="Look up order status",
9 parameters={...}
10 )
11 def check_order(self, args, raw_data):
12 # Handler right here with its definition
13 return SwaigFunctionResult("...")
14
15 @AgentBase.tool(
16 name="get_balance",
17 description="Get account balance",
18 parameters={...}
19 )
20 def get_balance(self, args, raw_data):
21 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:

1class HybridAgent(AgentBase):
2 def __init__(self, extra_functions=None):
3 super().__init__(name="hybrid")
4 self.add_language("English", "en-US", "rime.spore")
5
6 # Dynamic functions via define_tool
7 if extra_functions:
8 for func in extra_functions:
9 self.define_tool(**func)
10
11 # Static function via decorator
12 @AgentBase.tool(
13 name="get_help",
14 description="Get help information",
15 parameters={"type": "object", "properties": {}}
16 )
17 def get_help(self, args, raw_data):
18 return SwaigFunctionResult("How can I help you?")

External Webhook Functions

Route function calls to an external webhook:

1self.define_tool(
2 name="external_lookup",
3 description="Look up data from external service",
4 parameters={
5 "type": "object",
6 "properties": {
7 "query": {"type": "string", "description": "Search query"}
8 },
9 "required": ["query"]
10 },
11 handler=None, # No local handler
12 webhook_url="https://external-service.com/api/lookup"
13)

Function Security

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

1# Secure function (default)
2self.define_tool(
3 name="secure_function",
4 description="Requires token validation",
5 parameters={"type": "object", "properties": {}},
6 handler=self.secure_handler,
7 secure=True # Default
8)
9
10# Insecure function (testing only)
11self.define_tool(
12 name="test_function",
13 description="No token validation (testing only)",
14 parameters={"type": "object", "properties": {}},
15 handler=self.test_handler,
16 secure=False # Disable for testing
17)

Writing Good Descriptions

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

1# Good - specific and clear
2description="Look up order status by order number. Returns shipping status and estimated delivery date."
3
4# Bad - too vague
5description="Get order info"
6
7# Good - mentions what triggers it
8description="Check if a product is in stock. Use when customer asks about availability."
9
10# Good - explains constraints
11description="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

1#!/usr/bin/env python3
2# restaurant_agent.py - Restaurant order assistant
3from signalwire_agents import AgentBase, SwaigFunctionResult
4
5
6class RestaurantAgent(AgentBase):
7 MENU = {
8 "burger": {"price": 12.99, "description": "Angus beef burger with fries"},
9 "pizza": {"price": 14.99, "description": "12-inch cheese pizza"},
10 "salad": {"price": 9.99, "description": "Garden salad with dressing"}
11 }
12
13 def __init__(self):
14 super().__init__(name="restaurant-agent")
15 self.add_language("English", "en-US", "rime.spore")
16
17 self.prompt_add_section(
18 "Role",
19 "You are a friendly restaurant order assistant."
20 )
21
22 self.define_tool(
23 name="get_menu_item",
24 description="Get details about a menu item including price and description",
25 parameters={
26 "type": "object",
27 "properties": {
28 "item_name": {
29 "type": "string",
30 "description": "Name of the menu item"
31 }
32 },
33 "required": ["item_name"]
34 },
35 handler=self.get_menu_item,
36 fillers={
37 "en-US": ["Let me check the menu..."]
38 }
39 )
40
41 self.define_tool(
42 name="place_order",
43 description="Place an order for menu items",
44 parameters={
45 "type": "object",
46 "properties": {
47 "items": {
48 "type": "array",
49 "items": {"type": "string"},
50 "description": "List of menu items to order"
51 },
52 "special_requests": {
53 "type": "string",
54 "description": "Any special requests or modifications"
55 }
56 },
57 "required": ["items"]
58 },
59 handler=self.place_order,
60 fillers={
61 "en-US": ["Placing your order now..."]
62 }
63 )
64
65 def get_menu_item(self, args, raw_data):
66 item_name = args.get("item_name", "").lower()
67 item = self.MENU.get(item_name)
68
69 if item:
70 return SwaigFunctionResult(
71 f"{item_name.title()}: {item['description']}. Price: ${item['price']}"
72 )
73 return SwaigFunctionResult(f"Sorry, {item_name} is not on our menu.")
74
75 def place_order(self, args, raw_data):
76 items = args.get("items", [])
77 special = args.get("special_requests", "")
78
79 total = sum(
80 self.MENU.get(item.lower(), {}).get("price", 0)
81 for item in items
82 )
83
84 if total > 0:
85 msg = f"Order placed: {', '.join(items)}. Total: ${total:.2f}"
86 if special:
87 msg += f" Special requests: {special}"
88 return SwaigFunctionResult(msg)
89
90 return SwaigFunctionResult("Could not place order. Please check item names.")
91
92
93if __name__ == "__main__":
94 agent = RestaurantAgent()
95 agent.run()