SWAIG Functions

View as MarkdownOpen in Claude

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

Quick Start Example

Here’s a complete agent with a SWAIG function:

1from signalwire import AgentBase, FunctionResult
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 FunctionResult(f"Order {order_number}: {status}")
41
42if __name__ == "__main__":
43 agent = OrderAgent()
44 agent.run()

Function Types

TypeDescription
Handler FunctionsDefined with define_tool(). Your 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 FunctionResult.connect()
SMS sendingFunctionResult.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.

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

define_tool() Across Languages

The define_tool() method is available in all SDK languages. The parameters JSON Schema is identical everywhere; only the calling syntax differs:

LanguageSyntax
Pythonagent.define_tool(name="x", description="d", parameters={...}, handler=self.fn)
TypeScriptagent.defineTool({ name: 'x', description: 'd', parameters: {...}, handler: (args) => {...} })

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: the parsed function arguments and the full request data. The handler returns a FunctionResult with response text and optional actions.

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 FunctionResult with response text and optional actions
16 """
17 return FunctionResult("Response text")

The handler signature across languages:

LanguageSignature
Pythondef handler(self, args, raw_data): returns FunctionResult
TypeScript(args: Record<string, any>, raw?: Record<string, any>) => returns FunctionResult

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 FunctionResult(
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 FunctionResult(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 FunctionResult(f"Account {account_id} balance: $150.00")
64
65 def get_store_hours(self, args, raw_data):
66 location = args.get("location")
67 return FunctionResult(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

The @tool decorator is a Python-specific convenience. Other languages achieve the same result using define_tool() / defineTool() as shown in the examples above.

Alternative syntax using decorators:

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

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

Type-Inferred Functions

Type inference from function signatures is a Python-specific feature. Other languages require explicit JSON Schema parameter definitions via define_tool() / defineTool().

When using the @tool decorator (or define_tool()) without explicit parameters, the SDK automatically infers the JSON Schema from Python type hints. This eliminates boilerplate for straightforward functions.

1from typing import Optional, Literal
2from signalwire import AgentBase, FunctionResult
3
4class TypedAgent(AgentBase):
5 def __init__(self):
6 super().__init__(name="typed-agent")
7 self.add_language("English", "en-US", "rime.spore")
8
9 @AgentBase.tool(name="check_order")
10 def check_order(self, order_number: str, include_tracking: bool = False):
11 """Look up order status by order number.
12
13 Args:
14 order_number: The order number to look up
15 include_tracking: Include tracking URL in the response
16 """
17 status = "Shipped Monday, arriving Thursday"
18 result = f"Order {order_number}: {status}"
19 if include_tracking:
20 result += " Tracking: https://track.example.com/12345"
21 return FunctionResult(result)
22
23 @AgentBase.tool(name="set_priority")
24 def set_priority(self, ticket_id: str, level: Literal["low", "medium", "high"]):
25 """Set the priority level for a support ticket.
26
27 Args:
28 ticket_id: The support ticket ID
29 level: Priority level to set
30 """
31 return FunctionResult(f"Ticket {ticket_id} set to {level} priority")

How type inference works:

Python TypeJSON Schema Type
str"string"
int"integer"
float"number"
bool"boolean"
list / List[str]"array" (with items)
dict"object"
Optional[X]Type of X, not required
Literal["a", "b"]"string" with enum

Rules:

  • Parameters without defaults and not Optional are marked as required
  • The function’s docstring summary becomes the tool description
  • Google-style Args: docstring sections provide per-parameter descriptions
  • A raw_data parameter is recognized and passed through (not included in schema)
  • If no type hints are present, the SDK falls back to the traditional (args, raw_data) convention

Accessing raw_data with typed functions:

1@AgentBase.tool(name="caller_info")
2def caller_info(self, raw_data: dict):
3 """Get information about the current caller."""
4 caller = raw_data.get("caller_id_num", "unknown")
5 return FunctionResult(f"Your number is {caller}")

When to use type inference vs explicit parameters:

ScenarioRecommendation
Simple string/int/bool paramsType inference
Nested objects or complex schemasExplicit parameters
Enum constraintsLiteral type or explicit
Shared parameter schemasExplicit parameters

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 FunctionResult("How can I help you?")

Registering Pre-Built SWAIG Functions

The register_swaig_function() method registers a function definition object directly (used with DataMap and external definitions). This is a lower-level method than define_tool():

LanguageSyntax
Pythonagent.register_swaig_function(dm.to_swaig_function())
TypeScriptagent.registerSwaigFunction(dm.toSwaigFunction())

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

The following example is shown in Python. All concepts demonstrated (multiple define_tool() calls, fillers, handlers) work identically in other SDK languages using the syntax shown in the Quick Start and define_tool() tables above.

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