SignalWire AI Gateway (SWAIG) lets voice AI agents call backend functions in real time, bending external API calls and business logic directly into live voice conversations without brittle webhook chains. This guide explains how SWAIG works with the Agents SDK, from the @SWAIGFunction decorator and DataMaps to rich request contexts and advanced patterns, so developers can integrate external services into voice AI systems with minimal complexity.
This guide will walk through the basics of creating an AI Culinary Companion by integrating Spoonacular with a voice AI agent. Follow along in the Recipe AI Agent Github Repo
What is SWAIG?
SignalWire AI Gateway (SWAIG) allows voice AI agents to delegate tasks to your backend via HTTP POST. Think of it like CGI (Common Gateway Interface) - where a web server hands off execution to an external program using raw query strings or form-encoded data - but for AI agents.
Traditional AI function integration often looks like the Model Context Protocol (MCP): multiple services stitched together, lots of orchestration, and brittle webhook chains. It works, but it’s imperative and heavy.
The key difference? SWAIG allows you to incorporate external API calls into your AI agent conversation in real time - seamlessly, during natural voice conversations, without awkward pauses or "please hold" moments.
Why SWAIG + Agents SDK Changes Everything
Traditional voice AI architecture requires multiple services, webhook management, and complex orchestration. The SignalWire Agents SDK eliminates this complexity: the agent SDK allows you to set up these functions, set up a web server, and build complex AI agents (with POM), ALL IN A SINGLE PYTHON FILE!
Here's your complete voice AI system:
from signalwire_agents import AgentBase, SWAIGFunction, DataMap
class MyVoiceAI(AgentBase):
def __init__(self):
super().__init__(name="My Agent", port=5000, use_pom=True)
@SWAIGFunction(name="my_function", description="Does something useful")
def my_function(self, parameter: str):
return {"result": "success"}
if __name__ == "__main__":
MyVoiceAI().run() from signalwire_agents import AgentBase, SWAIGFunction, DataMap
class MyVoiceAI(AgentBase):
def __init__(self):
super().__init__(name="My Agent", port=5000, use_pom=True)
@SWAIGFunction(name="my_function", description="Does something useful")
def my_function(self, parameter: str):
return {"result": "success"}
if __name__ == "__main__":
MyVoiceAI().run() from signalwire_agents import AgentBase, SWAIGFunction, DataMap
class MyVoiceAI(AgentBase):
def __init__(self):
super().__init__(name="My Agent", port=5000, use_pom=True)
@SWAIGFunction(name="my_function", description="Does something useful")
def my_function(self, parameter: str):
return {"result": "success"}
if __name__ == "__main__":
MyVoiceAI().run() from signalwire_agents import AgentBase, SWAIGFunction, DataMap
class MyVoiceAI(AgentBase):
def __init__(self):
super().__init__(name="My Agent", port=5000, use_pom=True)
@SWAIGFunction(name="my_function", description="Does something useful")
def my_function(self, parameter: str):
return {"result": "success"}
if __name__ == "__main__":
MyVoiceAI().run()
SWAIG 101: Understanding the Basics
Step 1: The @SWAIGFunction Decorator
The @SWAIGFunction decorator is your gateway to real-time API integration. Here's how our recipe agent example demonstrates the pattern:
@SWAIGFunction(name="search_recipes",
description="Find recipes based on ingredients, cuisine type, or dietary restrictions")
def search_recipes(self, query: str, ingredients: str = None,
cuisine: str = None, max_results: int = 3):
"""SWAIG function that integrates with external APIs"""
@SWAIGFunction(name="search_recipes",
description="Find recipes based on ingredients, cuisine type, or dietary restrictions")
def search_recipes(self, query: str, ingredients: str = None,
cuisine: str = None, max_results: int = 3):
"""SWAIG function that integrates with external APIs"""
@SWAIGFunction(name="search_recipes",
description="Find recipes based on ingredients, cuisine type, or dietary restrictions")
def search_recipes(self, query: str, ingredients: str = None,
cuisine: str = None, max_results: int = 3):
"""SWAIG function that integrates with external APIs"""
@SWAIGFunction(name="search_recipes",
description="Find recipes based on ingredients, cuisine type, or dietary restrictions")
def search_recipes(self, query: str, ingredients: str = None,
cuisine: str = None, max_results: int = 3):
"""SWAIG function that integrates with external APIs"""
Key Elements:
name: How the AI identifies the function
description: Tells the AI when to use this function
Parameters: Automatically parsed from voice input
Type hints: Enable automatic validation
Step 2: DataMaps for Declarative API Integration
The DataMap system handles API calls declaratively. Instead of imperative code with error handling, you describe what you want:
@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, max_results: int = 3):
data_map = DataMap(
url="https://api.spoonacular.com/recipes/complexSearch",
method="GET",
input={
"query": "${query}",
"number": "${max_results}",
"apiKey": "${SPOONACULAR_API_KEY}",
"addRecipeInformation": "true"
},
output={
"recipe_count": "${results.length}",
"recipes": "${results[*].{id: id, title: title, readyInMinutes: readyInMinutes}}"
}
)
return self.swaig_data_map(data_map, query=query, max_results=max_results)@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, max_results: int = 3):
data_map = DataMap(
url="https://api.spoonacular.com/recipes/complexSearch",
method="GET",
input={
"query": "${query}",
"number": "${max_results}",
"apiKey": "${SPOONACULAR_API_KEY}",
"addRecipeInformation": "true"
},
output={
"recipe_count": "${results.length}",
"recipes": "${results[*].{id: id, title: title, readyInMinutes: readyInMinutes}}"
}
)
return self.swaig_data_map(data_map, query=query, max_results=max_results)@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, max_results: int = 3):
data_map = DataMap(
url="https://api.spoonacular.com/recipes/complexSearch",
method="GET",
input={
"query": "${query}",
"number": "${max_results}",
"apiKey": "${SPOONACULAR_API_KEY}",
"addRecipeInformation": "true"
},
output={
"recipe_count": "${results.length}",
"recipes": "${results[*].{id: id, title: title, readyInMinutes: readyInMinutes}}"
}
)
return self.swaig_data_map(data_map, query=query, max_results=max_results)@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, max_results: int = 3):
data_map = DataMap(
url="https://api.spoonacular.com/recipes/complexSearch",
method="GET",
input={
"query": "${query}",
"number": "${max_results}",
"apiKey": "${SPOONACULAR_API_KEY}",
"addRecipeInformation": "true"
},
output={
"recipe_count": "${results.length}",
"recipes": "${results[*].{id: id, title: title, readyInMinutes: readyInMinutes}}"
}
)
return self.swaig_data_map(data_map, query=query, max_results=max_results)
DataMap handles automatically:
HTTP request formatting
Parameter substitution (${variable} syntax)
JSON response parsing
Error handling and retries
Response transformation
Step 3: Understanding SWAIG Request Context
SWAIG requests include rich context: The function name (e.g., "search_movie"), Parsed arguments in the structured argument.parsed field, The full argument schema (argument_desc) to describe expected inputs, Session, caller, and project context so you can make smart decisions without additional lookups
This rich context enables powerful patterns like automatic authentication using caller ID.
Advanced SWAIG Patterns
Custom Logic with SwaigFunctionResult
For complex business logic beyond simple API calls, use custom SWAIG functions with the SwaigFunctionResult class:
@SWAIGFunction(name="create_meal_plan",
description="Generate a personalized meal plan")
def create_meal_plan(self, days: int, dietary_restrictions: list = None):
"""Complex multi-step logic in a SWAIG function"""
user_profile = self._get_user_dietary_profile()
available_ingredients = self._check_pantry_inventory()
seasonal_adjustments = self._calculate_seasonal_preferences()
meal_plan = MealPlanGenerator(
profile=user_profile,
constraints={
'restrictions': dietary_restrictions,
'inventory': available_ingredients
}
).generate(days)
return SwaigFunctionResult(
response=f"I've created a {days}-day meal plan tailored to your preferences...",
action_data={"meal_plan_id": meal_plan.id}
)@SWAIGFunction(name="create_meal_plan",
description="Generate a personalized meal plan")
def create_meal_plan(self, days: int, dietary_restrictions: list = None):
"""Complex multi-step logic in a SWAIG function"""
user_profile = self._get_user_dietary_profile()
available_ingredients = self._check_pantry_inventory()
seasonal_adjustments = self._calculate_seasonal_preferences()
meal_plan = MealPlanGenerator(
profile=user_profile,
constraints={
'restrictions': dietary_restrictions,
'inventory': available_ingredients
}
).generate(days)
return SwaigFunctionResult(
response=f"I've created a {days}-day meal plan tailored to your preferences...",
action_data={"meal_plan_id": meal_plan.id}
)@SWAIGFunction(name="create_meal_plan",
description="Generate a personalized meal plan")
def create_meal_plan(self, days: int, dietary_restrictions: list = None):
"""Complex multi-step logic in a SWAIG function"""
user_profile = self._get_user_dietary_profile()
available_ingredients = self._check_pantry_inventory()
seasonal_adjustments = self._calculate_seasonal_preferences()
meal_plan = MealPlanGenerator(
profile=user_profile,
constraints={
'restrictions': dietary_restrictions,
'inventory': available_ingredients
}
).generate(days)
return SwaigFunctionResult(
response=f"I've created a {days}-day meal plan tailored to your preferences...",
action_data={"meal_plan_id": meal_plan.id}
)@SWAIGFunction(name="create_meal_plan",
description="Generate a personalized meal plan")
def create_meal_plan(self, days: int, dietary_restrictions: list = None):
"""Complex multi-step logic in a SWAIG function"""
user_profile = self._get_user_dietary_profile()
available_ingredients = self._check_pantry_inventory()
seasonal_adjustments = self._calculate_seasonal_preferences()
meal_plan = MealPlanGenerator(
profile=user_profile,
constraints={
'restrictions': dietary_restrictions,
'inventory': available_ingredients
}
).generate(days)
return SwaigFunctionResult(
response=f"I've created a {days}-day meal plan tailored to your preferences...",
action_data={"meal_plan_id": meal_plan.id}
)
SwaigFunctionResult enables:
Natural language responses for the AI to speak
Action data for conversation state management
Complex response orchestration
Error Handling and Graceful Degradation
Production SWAIG functions handle failures elegantly:
@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, **kwargs):
try:
return self.swaig_data_map(data_map, **kwargs)
except APITimeoutError:
fallback_recipes = self._get_cached_popular_recipes()
return SwaigFunctionResult(
response="The recipe service is running slow, but I have some popular suggestions...",
action_data={"fallback_used": True, "recipes": fallback_recipes}
)
except APIError:
return SwaigFunctionResult(
response="I'm having trouble with the recipe database. Would you like me to suggest some classic dishes I know well?"
)@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, **kwargs):
try:
return self.swaig_data_map(data_map, **kwargs)
except APITimeoutError:
fallback_recipes = self._get_cached_popular_recipes()
return SwaigFunctionResult(
response="The recipe service is running slow, but I have some popular suggestions...",
action_data={"fallback_used": True, "recipes": fallback_recipes}
)
except APIError:
return SwaigFunctionResult(
response="I'm having trouble with the recipe database. Would you like me to suggest some classic dishes I know well?"
)@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, **kwargs):
try:
return self.swaig_data_map(data_map, **kwargs)
except APITimeoutError:
fallback_recipes = self._get_cached_popular_recipes()
return SwaigFunctionResult(
response="The recipe service is running slow, but I have some popular suggestions...",
action_data={"fallback_used": True, "recipes": fallback_recipes}
)
except APIError:
return SwaigFunctionResult(
response="I'm having trouble with the recipe database. Would you like me to suggest some classic dishes I know well?"
)@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, **kwargs):
try:
return self.swaig_data_map(data_map, **kwargs)
except APITimeoutError:
fallback_recipes = self._get_cached_popular_recipes()
return SwaigFunctionResult(
response="The recipe service is running slow, but I have some popular suggestions...",
action_data={"fallback_used": True, "recipes": fallback_recipes}
)
except APIError:
return SwaigFunctionResult(
response="I'm having trouble with the recipe database. Would you like me to suggest some classic dishes I know well?"
)
State Management Across Conversations
SWAIG functions can persist data across phone calls using the built-in state management:
@SWAIGFunction(name="save_recipe_rating")
def save_recipe_rating(self, recipe_id: str, rating: int, notes: str = ""):
"""Persistent user preferences"""
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})
user_prefs.setdefault("recipe_history", []).append({
"recipe_id": recipe_id,
"rating": rating,
"timestamp": datetime.now().isoformat()
})
self.state_manager.set_state(self.session_id, "preferences", user_prefs)
return SwaigFunctionResult(
response=f"Thanks! I'll remember you rated this {rating}/5 stars."
)
@SWAIGFunction(name="save_recipe_rating")
def save_recipe_rating(self, recipe_id: str, rating: int, notes: str = ""):
"""Persistent user preferences"""
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})
user_prefs.setdefault("recipe_history", []).append({
"recipe_id": recipe_id,
"rating": rating,
"timestamp": datetime.now().isoformat()
})
self.state_manager.set_state(self.session_id, "preferences", user_prefs)
return SwaigFunctionResult(
response=f"Thanks! I'll remember you rated this {rating}/5 stars."
)
@SWAIGFunction(name="save_recipe_rating")
def save_recipe_rating(self, recipe_id: str, rating: int, notes: str = ""):
"""Persistent user preferences"""
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})
user_prefs.setdefault("recipe_history", []).append({
"recipe_id": recipe_id,
"rating": rating,
"timestamp": datetime.now().isoformat()
})
self.state_manager.set_state(self.session_id, "preferences", user_prefs)
return SwaigFunctionResult(
response=f"Thanks! I'll remember you rated this {rating}/5 stars."
)
@SWAIGFunction(name="save_recipe_rating")
def save_recipe_rating(self, recipe_id: str, rating: int, notes: str = ""):
"""Persistent user preferences"""
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})
user_prefs.setdefault("recipe_history", []).append({
"recipe_id": recipe_id,
"rating": rating,
"timestamp": datetime.now().isoformat()
})
self.state_manager.set_state(self.session_id, "preferences", user_prefs)
return SwaigFunctionResult(
response=f"Thanks! I'll remember you rated this {rating}/5 stars."
)
Universal SWAIG Patterns
The beauty of SWAIG? This pattern works with ANY API. Swap out Spoonacular for:
E-commerce: Product searches and inventory checks
Travel: Flight prices and hotel availability
Finance: Stock quotes and account balances
Healthcare: Appointment scheduling and prescription refills
Real Estate: Property listings and market data
Customer Service: Account lookups and order status (using caller ID for automatic authentication)
Need authenticated APIs? SWAIG handles that too. The rich context includes caller information, so you can automatically authenticate users and access their personal data without asking them to recite account numbers over the phone.
Integrating with the Agents SDK
Setting Up Your Agent Foundation
The AgentBase class provides the foundation for creating AI-powered agents using the SignalWire AI Agent SDK. It extends the SignalWire Markup Language (SWML) Service class, inheriting all its SWML document creation and serving capabilities, while adding AI-specific functionality
class RecipeAgent(AgentBase):
def __init__(self):
super().__init__(
name="Chef Assistant",
host="0.0.0.0",
port=5000,
use_pom=True,
record_call=True,
auto_answer=True
)
self._setup_agent_personality()
self._setup_conversation_contexts()class RecipeAgent(AgentBase):
def __init__(self):
super().__init__(
name="Chef Assistant",
host="0.0.0.0",
port=5000,
use_pom=True,
record_call=True,
auto_answer=True
)
self._setup_agent_personality()
self._setup_conversation_contexts()class RecipeAgent(AgentBase):
def __init__(self):
super().__init__(
name="Chef Assistant",
host="0.0.0.0",
port=5000,
use_pom=True,
record_call=True,
auto_answer=True
)
self._setup_agent_personality()
self._setup_conversation_contexts()class RecipeAgent(AgentBase):
def __init__(self):
super().__init__(
name="Chef Assistant",
host="0.0.0.0",
port=5000,
use_pom=True,
record_call=True,
auto_answer=True
)
self._setup_agent_personality()
self._setup_conversation_contexts()
Configuring AI Behavior with POM
The Prompt Object Model (POM) structures AI behavior configuration:
def _setup_agent_personality(self):
"""Configure AI behavior declaratively"""
self.prompt_add_section("Role",
"You are a master chef with 20 years of experience, "
"passionate about teaching cooking and helping people "
"create delicious meals at home."
)
self.prompt_add_section("Guidelines", [
"Always ask about dietary restrictions first",
"Provide encouraging feedback during cooking",
"Offer ingredient substitutions when needed"
])def _setup_agent_personality(self):
"""Configure AI behavior declaratively"""
self.prompt_add_section("Role",
"You are a master chef with 20 years of experience, "
"passionate about teaching cooking and helping people "
"create delicious meals at home."
)
self.prompt_add_section("Guidelines", [
"Always ask about dietary restrictions first",
"Provide encouraging feedback during cooking",
"Offer ingredient substitutions when needed"
])def _setup_agent_personality(self):
"""Configure AI behavior declaratively"""
self.prompt_add_section("Role",
"You are a master chef with 20 years of experience, "
"passionate about teaching cooking and helping people "
"create delicious meals at home."
)
self.prompt_add_section("Guidelines", [
"Always ask about dietary restrictions first",
"Provide encouraging feedback during cooking",
"Offer ingredient substitutions when needed"
])def _setup_agent_personality(self):
"""Configure AI behavior declaratively"""
self.prompt_add_section("Role",
"You are a master chef with 20 years of experience, "
"passionate about teaching cooking and helping people "
"create delicious meals at home."
)
self.prompt_add_section("Guidelines", [
"Always ask about dietary restrictions first",
"Provide encouraging feedback during cooking",
"Offer ingredient substitutions when needed"
])
Context-Aware Function Availability
Define which SWAIG functions are available in different conversation contexts:
def _setup_conversation_contexts(self):
"""Define specialized conversation contexts"""
self.prompt_add_context("recipe_search", [
"search_recipes", "get_recipe_details", "suggest_alternatives"
])
self.prompt_add_context("cooking_guidance", [
"get_next_step", "set_timer", "handle_cooking_questions"
])
self.prompt_add_context("meal_planning", [
"create_meal_plan", "save_recipe_rating", "get_shopping_list"
])def _setup_conversation_contexts(self):
"""Define specialized conversation contexts"""
self.prompt_add_context("recipe_search", [
"search_recipes", "get_recipe_details", "suggest_alternatives"
])
self.prompt_add_context("cooking_guidance", [
"get_next_step", "set_timer", "handle_cooking_questions"
])
self.prompt_add_context("meal_planning", [
"create_meal_plan", "save_recipe_rating", "get_shopping_list"
])def _setup_conversation_contexts(self):
"""Define specialized conversation contexts"""
self.prompt_add_context("recipe_search", [
"search_recipes", "get_recipe_details", "suggest_alternatives"
])
self.prompt_add_context("cooking_guidance", [
"get_next_step", "set_timer", "handle_cooking_questions"
])
self.prompt_add_context("meal_planning", [
"create_meal_plan", "save_recipe_rating", "get_shopping_list"
])def _setup_conversation_contexts(self):
"""Define specialized conversation contexts"""
self.prompt_add_context("recipe_search", [
"search_recipes", "get_recipe_details", "suggest_alternatives"
])
self.prompt_add_context("cooking_guidance", [
"get_next_step", "set_timer", "handle_cooking_questions"
])
self.prompt_add_context("meal_planning", [
"create_meal_plan", "save_recipe_rating", "get_shopping_list"
])
SWAIG Best Practices
1. Function Design Principles
Single responsibility: Each SWAIG function should do one thing well
Clear descriptions: Help the AI understand when to call your function
Sensible defaults: Use optional parameters with reasonable defaults
Error handling: Always handle API failures gracefully
2. DataMap Optimization
output={
"recipes": "${results[*].{id: id, title: title, cookTime: readyInMinutes}}"
}
output={"raw_response": "${*}"}
output={
"recipes": "${results[*].{id: id, title: title, cookTime: readyInMinutes}}"
}
output={"raw_response": "${*}"}
output={
"recipes": "${results[*].{id: id, title: title, cookTime: readyInMinutes}}"
}
output={"raw_response": "${*}"}
output={
"recipes": "${results[*].{id: id, title: title, cookTime: readyInMinutes}}"
}
output={"raw_response": "${*}"}
3. State Management Strategy
Use session state for conversation-specific data
Use global state for shared/cached data
Implement state cleanup for expired data
Consider state size limits for performance
4. Voice-Optimized Responses
return SwaigFunctionResult(
response="I found 3 great pasta recipes: **Creamy Mushroom Fettuccine** (25 mins), **Pasta Primavera** (20 mins), and **Carbonara** (15 mins). Which sounds good?",
response="Recipe search results: 1. Creamy Mushroom Fettuccine - Prep time 25 minutes, Difficulty intermediate..."
)return SwaigFunctionResult(
response="I found 3 great pasta recipes: **Creamy Mushroom Fettuccine** (25 mins), **Pasta Primavera** (20 mins), and **Carbonara** (15 mins). Which sounds good?",
response="Recipe search results: 1. Creamy Mushroom Fettuccine - Prep time 25 minutes, Difficulty intermediate..."
)return SwaigFunctionResult(
response="I found 3 great pasta recipes: **Creamy Mushroom Fettuccine** (25 mins), **Pasta Primavera** (20 mins), and **Carbonara** (15 mins). Which sounds good?",
response="Recipe search results: 1. Creamy Mushroom Fettuccine - Prep time 25 minutes, Difficulty intermediate..."
)return SwaigFunctionResult(
response="I found 3 great pasta recipes: **Creamy Mushroom Fettuccine** (25 mins), **Pasta Primavera** (20 mins), and **Carbonara** (15 mins). Which sounds good?",
response="Recipe search results: 1. Creamy Mushroom Fettuccine - Prep time 25 minutes, Difficulty intermediate..."
)
Production Deployment Guide
Environment Configuration
Set up your environment variables:
export SPOONACULAR_API_KEY="your_api_key_here"
export SIGNALWIRE_PROJECT_ID="your_project_id"
export SIGNALWIRE_TOKEN="your_token"
export SPOONACULAR_API_KEY="your_api_key_here"
export SIGNALWIRE_PROJECT_ID="your_project_id"
export SIGNALWIRE_TOKEN="your_token"
export SPOONACULAR_API_KEY="your_api_key_here"
export SIGNALWIRE_PROJECT_ID="your_project_id"
export SIGNALWIRE_TOKEN="your_token"
export SPOONACULAR_API_KEY="your_api_key_here"
export SIGNALWIRE_PROJECT_ID="your_project_id"
export SIGNALWIRE_TOKEN="your_token"
Single File Deployment
The AgentBase class automatically handles deployment environments:
if __name__ == "__main__":
RecipeAgent().run() if __name__ == "__main__":
RecipeAgent().run() if __name__ == "__main__":
RecipeAgent().run() if __name__ == "__main__":
RecipeAgent().run()
Automatic environment detection:
Local development: Starts web server
AWS Lambda: Configures as Lambda handler
Docker: Handles containerization
Cloud Functions: Adapts automatically
Testing Your SWAIG Functions
Test individual functions during development:
agent = RecipeAgent()
result = agent.search_recipes("pasta", ingredients="mushrooms", max_results=3)
print(result)
agent = RecipeAgent()
result = agent.search_recipes("pasta", ingredients="mushrooms", max_results=3)
print(result)
agent = RecipeAgent()
result = agent.search_recipes("pasta", ingredients="mushrooms", max_results=3)
print(result)
agent = RecipeAgent()
result = agent.search_recipes("pasta", ingredients="mushrooms", max_results=3)
print(result)
Common SWAIG Patterns and Examples
Pattern 1: Simple API Integration
@SWAIGFunction(name="get_weather", description="Get current weather")
def get_weather(self, location: str):
weather_map = DataMap(
url="https://api.openweathermap.org/data/2.5/weather",
input={"q": "${location}", "appid": "${WEATHER_API_KEY}"},
output={"temp": "${main.temp}", "description": "${weather[0].description}"}
)
return self.swaig_data_map(weather_map, location=location)@SWAIGFunction(name="get_weather", description="Get current weather")
def get_weather(self, location: str):
weather_map = DataMap(
url="https://api.openweathermap.org/data/2.5/weather",
input={"q": "${location}", "appid": "${WEATHER_API_KEY}"},
output={"temp": "${main.temp}", "description": "${weather[0].description}"}
)
return self.swaig_data_map(weather_map, location=location)@SWAIGFunction(name="get_weather", description="Get current weather")
def get_weather(self, location: str):
weather_map = DataMap(
url="https://api.openweathermap.org/data/2.5/weather",
input={"q": "${location}", "appid": "${WEATHER_API_KEY}"},
output={"temp": "${main.temp}", "description": "${weather[0].description}"}
)
return self.swaig_data_map(weather_map, location=location)@SWAIGFunction(name="get_weather", description="Get current weather")
def get_weather(self, location: str):
weather_map = DataMap(
url="https://api.openweathermap.org/data/2.5/weather",
input={"q": "${location}", "appid": "${WEATHER_API_KEY}"},
output={"temp": "${main.temp}", "description": "${weather[0].description}"}
)
return self.swaig_data_map(weather_map, location=location)
Pattern 2: Multi-Step Business Logic
@SWAIGFunction(name="process_order")
def process_order(self, product: str, quantity: int):
if not self._check_inventory(product, quantity):
return SwaigFunctionResult(response="Sorry, insufficient inventory")
price = self._calculate_price(product, quantity)
reservation = self._reserve_items(product, quantity)
return SwaigFunctionResult(
response=f"Reserved {quantity} {product} for ${price}. Proceed with order?",
action_data={"reservation_id": reservation.id}
)@SWAIGFunction(name="process_order")
def process_order(self, product: str, quantity: int):
if not self._check_inventory(product, quantity):
return SwaigFunctionResult(response="Sorry, insufficient inventory")
price = self._calculate_price(product, quantity)
reservation = self._reserve_items(product, quantity)
return SwaigFunctionResult(
response=f"Reserved {quantity} {product} for ${price}. Proceed with order?",
action_data={"reservation_id": reservation.id}
)@SWAIGFunction(name="process_order")
def process_order(self, product: str, quantity: int):
if not self._check_inventory(product, quantity):
return SwaigFunctionResult(response="Sorry, insufficient inventory")
price = self._calculate_price(product, quantity)
reservation = self._reserve_items(product, quantity)
return SwaigFunctionResult(
response=f"Reserved {quantity} {product} for ${price}. Proceed with order?",
action_data={"reservation_id": reservation.id}
)@SWAIGFunction(name="process_order")
def process_order(self, product: str, quantity: int):
if not self._check_inventory(product, quantity):
return SwaigFunctionResult(response="Sorry, insufficient inventory")
price = self._calculate_price(product, quantity)
reservation = self._reserve_items(product, quantity)
return SwaigFunctionResult(
response=f"Reserved {quantity} {product} for ${price}. Proceed with order?",
action_data={"reservation_id": reservation.id}
)
Pattern 3: Authenticated User Data
@SWAIGFunction(name="get_account_balance")
def get_account_balance(self):
caller_id = self.session_id
user = self._authenticate_user(caller_id)
balance_map = DataMap(
url="https://banking-api.com/accounts/balance",
headers={"Authorization": f"Bearer {user.token}"},
input={"account_id": "${account_id}"},
output={"balance": "${current_balance}", "available": "${available_balance}"}
)
return self.swaig_data_map(balance_map, account_id=user.account_id)@SWAIGFunction(name="get_account_balance")
def get_account_balance(self):
caller_id = self.session_id
user = self._authenticate_user(caller_id)
balance_map = DataMap(
url="https://banking-api.com/accounts/balance",
headers={"Authorization": f"Bearer {user.token}"},
input={"account_id": "${account_id}"},
output={"balance": "${current_balance}", "available": "${available_balance}"}
)
return self.swaig_data_map(balance_map, account_id=user.account_id)@SWAIGFunction(name="get_account_balance")
def get_account_balance(self):
caller_id = self.session_id
user = self._authenticate_user(caller_id)
balance_map = DataMap(
url="https://banking-api.com/accounts/balance",
headers={"Authorization": f"Bearer {user.token}"},
input={"account_id": "${account_id}"},
output={"balance": "${current_balance}", "available": "${available_balance}"}
)
return self.swaig_data_map(balance_map, account_id=user.account_id)@SWAIGFunction(name="get_account_balance")
def get_account_balance(self):
caller_id = self.session_id
user = self._authenticate_user(caller_id)
balance_map = DataMap(
url="https://banking-api.com/accounts/balance",
headers={"Authorization": f"Bearer {user.token}"},
input={"account_id": "${account_id}"},
output={"balance": "${current_balance}", "available": "${available_balance}"}
)
return self.swaig_data_map(balance_map, account_id=user.account_id)
Troubleshooting Common Issues
DataMap Parameter Problems
input={"query": "pasta"}
input={"query": "${query}"}
input={"query": "pasta"}
input={"query": "${query}"}
input={"query": "pasta"}
input={"query": "${query}"}
input={"query": "pasta"}
input={"query": "${query}"}
Missing Error Handling
try:
return self.swaig_data_map(data_map, **kwargs)
except Exception as e:
return SwaigFunctionResult(response="Sorry, I'm having technical difficulties.")
try:
return self.swaig_data_map(data_map, **kwargs)
except Exception as e:
return SwaigFunctionResult(response="Sorry, I'm having technical difficulties.")
try:
return self.swaig_data_map(data_map, **kwargs)
except Exception as e:
return SwaigFunctionResult(response="Sorry, I'm having technical difficulties.")
try:
return self.swaig_data_map(data_map, **kwargs)
except Exception as e:
return SwaigFunctionResult(response="Sorry, I'm having technical difficulties.")
State Management Issues
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})
user_prefs = self.state_manager.get_state(self.session_id, "preferences")
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})
user_prefs = self.state_manager.get_state(self.session_id, "preferences")
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})
user_prefs = self.state_manager.get_state(self.session_id, "preferences")
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})
user_prefs = self.state_manager.get_state(self.session_id, "preferences")
Next Steps: Building Your SWAIG Application
1. Start with the Recipe Agent
Clone and explore the complete recipe agent implementation to understand all SWAIG patterns in context.
2. Study the Documentation
3. Explore More Examples
Flo's Flowers demo - AI with SMS/MMS integration
Complete guides showcase - Various SignalWire patterns
4. Build Your First Agent
Follow these steps to get your SWAIG agent running:
Step 1: Set Up Your Python Environment
pip install signalwire-agents
git clone https:
cd signalwire-agents
pip install signalwire-agents
git clone https:
cd signalwire-agents
pip install signalwire-agents
git clone https:
cd signalwire-agents
pip install signalwire-agents
git clone https:
cd signalwire-agents
Step 2: Get Your API Keysfrom the SignalWire Dashboard
Sign up for Spoonacular API (free tier available)
Get your SignalWire credentials
Set environment variables:
export SPOONACULAR_API_KEY="your_spoonacular_key"
export SIGNALWIRE_PROJECT_ID="your_project_id"
export SIGNALWIRE_TOKEN="your_token"
export SPOONACULAR_API_KEY="your_spoonacular_key"
export SIGNALWIRE_PROJECT_ID="your_project_id"
export SIGNALWIRE_TOKEN="your_token"
export SPOONACULAR_API_KEY="your_spoonacular_key"
export SIGNALWIRE_PROJECT_ID="your_project_id"
export SIGNALWIRE_TOKEN="your_token"
export SPOONACULAR_API_KEY="your_spoonacular_key"
export SIGNALWIRE_PROJECT_ID="your_project_id"
export SIGNALWIRE_TOKEN="your_token"
Step 3: Run Your Agent Server
python recipe_agent.py # Starts server on port 5000
python recipe_agent.py # Starts server on port 5000
python recipe_agent.py # Starts server on port 5000
python recipe_agent.py # Starts server on port 5000
Step 4: Expose Your Local Server. Install ngrok and expose your local server to the internet:
Copy the public URL (e.g., https://983f-93-41-16-193.ngro...)
Step 5: Configure Your Phone Number
Buy a phone number in SignalWire Dashboard
Create a new Resource → Script → SWML type
Set to "External URL" and input your ngrok URL
Test by calling your number!
Detailed setup instructions: Handle incoming calls from code
Welcome to SWAIG development—where enterprise voice AI becomes as simple as writing Python functions, and your biggest architectural decision is choosing meaningful function names.
Ready to dive deeper? The recipe agent demonstrates every pattern covered in this guide. Use it as your foundation for building sophisticated voice AI applications with SWAIG and the SignalWire Agents SDK.
Frequently asked questions
What is SignalWire AI Gateway (SWAIG)?
SignalWire AI Gateway (SWAIG) is a system that enables AI voice agents to integrate real-time external API calls into voice conversations, avoiding complex webhook orchestration.
How does the @SWAIGFunction decorator work?
The @SWAIGFunction decorator tells the AI how to identify a function, describes when to use it, and lets the agent call it with structured parameters.
What are DataMaps in SWAIG?
DataMaps let developers describe API requests declaratively, handling parameter substitution, HTTP formatting, response parsing, and error handling automatically.
What context does a SWAIG request include?
A SWAIG request includes the function name, parsed arguments, full argument schema, session info, caller data, and project context, enabling smart decisions without extra lookups.
What are some advanced SWAIG patterns?
Advanced patterns include custom logic via SwaigFunctionResult, graceful error handling, session-wide state management, and authenticated API access, all usable in production voice AI.