***
id: 3d31b9ba-e380-4a20-9791-a96494b8ea32
title: Defining Functions
sidebar-title: Defining Functions
slug: /python/guides/defining-functions
max-toc-depth: 3
----------------
# 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
## Quick Start Example
Here's a complete agent with a SWAIG function:
```python
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
| Type | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **Handler Functions** | Defined with `define_tool()`. Python handler runs on your server with full control over logic, database access, and API calls. |
| **DataMap Functions** | Serverless API integration that runs on SignalWire's servers. No webhook endpoint needed - direct REST API calls. |
| **Native Functions** | Built into SignalWire. No custom code required - handles transfer, recording, etc. |
## Chapter Contents
| Section | Description |
| ----------------------------------------------------------------------- | -------------------------------------------- |
| [Defining Functions](/docs/agents-sdk/python/guides/defining-functions) | Creating SWAIG functions with define\_tool() |
| [Parameters](/docs/agents-sdk/python/guides/parameters) | Defining and validating function parameters |
| [Results & Actions](/docs/agents-sdk/python/guides/result-actions) | Returning results and triggering actions |
| [DataMap](/docs/agents-sdk/python/guides/data-map) | Serverless API integration |
| [Native Functions](/docs/agents-sdk/python/guides/native-functions) | Built-in SignalWire functions |
## When to Use SWAIG Functions
| Use Case | Approach |
| ----------------------- | ------------------------------------------------ |
| Database lookups | Handler function |
| Complex business logic | Handler function |
| Simple REST API calls | DataMap |
| Pattern-based responses | DataMap expressions |
| Call transfers | Native function or SwaigFunctionResult.connect() |
| SMS sending | SwaigFunctionResult.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
```python
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:**
| Parameter | Description |
| ------------- | ---------------------------------------------------- |
| `name` | Unique function name (lowercase, underscores) |
| `description` | What the function does (helps AI decide when to use) |
| `parameters` | JSON Schema defining accepted arguments |
| `handler` | Python function to call |
**Optional Parameters:**
| Parameter | Description |
| ------------- | ---------------------------------------------- |
| `secure` | Require token validation (default: True) |
| `fillers` | Language-specific filler phrases |
| `webhook_url` | External webhook URL (overrides local handler) |
| `required` | List of required parameter names |
## Handler Function Signature
All handlers receive two arguments:
```python
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
```python
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:
```python
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:
```python
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:
```python
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
| Aspect | define\_tool() | @tool Decorator |
| ------------------------- | -------------------------------- | ----------------------- |
| **Where defined** | Inside `__init__` | At method definition |
| **Dynamic registration** | Easy | Requires workarounds |
| **Conditional functions** | Straightforward | More complex |
| **Code organization** | Definition separate from handler | Self-documenting |
| **Inheritance** | Easier to override | Works but less flexible |
### When to Use define\_tool()
**Conditional function registration:**
```python
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:**
```python
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:**
```python
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:**
```python
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:
```python
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:
```python
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:
```python
# 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:
```python
# 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:
```bash
# 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
```python
#!/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()
```