Bottom Linear Gradient  Lines image

Learn

Resources

Article

18 min

read

SWAIG 101: Your Complete Guide to Voice AI Function Integration

Master SignalWire's AI Gateway with the Agents SDK - from basics to production deployment

Jon Gray Headshot

Jon Gray

Solutions Architect

In this article

Share

Angular Gradient Image

Build it free.

Create a space and ship your first call flow in minutes.

Subscribe

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:

copy

01

02

03

04

05

06

07

08

09

10

11

12

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()  # Complete production voice AI server

copy

01

02

03

04

05

06

07

08

09

10

11

12

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()  # Complete production voice AI server

copy

01

02

03

04

05

06

07

08

09

10

11

12

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()  # Complete production voice AI server

copy

01

02

03

04

05

06

07

08

09

10

11

12

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()  # Complete production voice AI server



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:

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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"""

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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"""

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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"""

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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:

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, max_results: int = 3):
    # Using the excellent Spoonacular Food API (shoutout to their comprehensive recipe database!)
    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)

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, max_results: int = 3):
    # Using the excellent Spoonacular Food API (shoutout to their comprehensive recipe database!)
    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)

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, max_results: int = 3):
    # Using the excellent Spoonacular Food API (shoutout to their comprehensive recipe database!)
    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)

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, max_results: int = 3):
    # Using the excellent Spoonacular Food API (shoutout to their comprehensive recipe database!)
    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:

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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"""
    
    # Multi-step business logic
    user_profile = self._get_user_dietary_profile()
    available_ingredients = self._check_pantry_inventory()
    seasonal_adjustments = self._calculate_seasonal_preferences()
    
    # Generate comprehensive meal plan
    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}
    )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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"""
    
    # Multi-step business logic
    user_profile = self._get_user_dietary_profile()
    available_ingredients = self._check_pantry_inventory()
    seasonal_adjustments = self._calculate_seasonal_preferences()
    
    # Generate comprehensive meal plan
    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}
    )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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"""
    
    # Multi-step business logic
    user_profile = self._get_user_dietary_profile()
    available_ingredients = self._check_pantry_inventory()
    seasonal_adjustments = self._calculate_seasonal_preferences()
    
    # Generate comprehensive meal plan
    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}
    )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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"""
    
    # Multi-step business logic
    user_profile = self._get_user_dietary_profile()
    available_ingredients = self._check_pantry_inventory()
    seasonal_adjustments = self._calculate_seasonal_preferences()
    
    # Generate comprehensive meal plan
    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:

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, **kwargs):
    try:
        return self.swaig_data_map(data_map, **kwargs)
        
    except APITimeoutError:
        # Fallback to cached data
        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?"
        )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, **kwargs):
    try:
        return self.swaig_data_map(data_map, **kwargs)
        
    except APITimeoutError:
        # Fallback to cached data
        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?"
        )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, **kwargs):
    try:
        return self.swaig_data_map(data_map, **kwargs)
        
    except APITimeoutError:
        # Fallback to cached data
        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?"
        )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="search_recipes")
def search_recipes(self, query: str, **kwargs):
    try:
        return self.swaig_data_map(data_map, **kwargs)
        
    except APITimeoutError:
        # Fallback to cached data
        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:

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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."
    )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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."
    )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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."
    )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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

copy

01

02

03

04

05

06

07

08

09

10

11

12

class RecipeAgent(AgentBase):
    def __init__(self):
        super().__init__(
            name="Chef Assistant",
            host="0.0.0.0",
            port=5000,
            use_pom=True,  # Enable Prompt Object Model
            record_call=True,  # Built-in call recording
            auto_answer=True   # Automatic call handling
        )
        
        self._setup_agent_personality()
        self._setup_conversation_contexts()

copy

01

02

03

04

05

06

07

08

09

10

11

12

class RecipeAgent(AgentBase):
    def __init__(self):
        super().__init__(
            name="Chef Assistant",
            host="0.0.0.0",
            port=5000,
            use_pom=True,  # Enable Prompt Object Model
            record_call=True,  # Built-in call recording
            auto_answer=True   # Automatic call handling
        )
        
        self._setup_agent_personality()
        self._setup_conversation_contexts()

copy

01

02

03

04

05

06

07

08

09

10

11

12

class RecipeAgent(AgentBase):
    def __init__(self):
        super().__init__(
            name="Chef Assistant",
            host="0.0.0.0",
            port=5000,
            use_pom=True,  # Enable Prompt Object Model
            record_call=True,  # Built-in call recording
            auto_answer=True   # Automatic call handling
        )
        
        self._setup_agent_personality()
        self._setup_conversation_contexts()

copy

01

02

03

04

05

06

07

08

09

10

11

12

class RecipeAgent(AgentBase):
    def __init__(self):
        super().__init__(
            name="Chef Assistant",
            host="0.0.0.0",
            port=5000,
            use_pom=True,  # Enable Prompt Object Model
            record_call=True,  # Built-in call recording
            auto_answer=True   # Automatic call handling
        )
        
        self._setup_agent_personality()
        self._setup_conversation_contexts()



Configuring AI Behavior with POM

The Prompt Object Model (POM) structures AI behavior configuration:

copy

01

02

03

04

05

06

07

08

09

10

11

12

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"
    ])

copy

01

02

03

04

05

06

07

08

09

10

11

12

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"
    ])

copy

01

02

03

04

05

06

07

08

09

10

11

12

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"
    ])

copy

01

02

03

04

05

06

07

08

09

10

11

12

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:

copy

01

02

03

04

05

06

07

08

09

10

11

12

def _setup_conversation_contexts(self):
    """Define specialized conversation contexts"""
    
    # Recipe Discovery Context
    self.prompt_add_context("recipe_search", [
        "search_recipes", "get_recipe_details", "suggest_alternatives"
    ])
    
    # Active Cooking Context  
    self.prompt_add_context("cooking_guidance", [
        "get_next_step", "set_timer", "handle_cooking_questions"
    ])
    
    # Meal Planning Context
    self.prompt_add_context("meal_planning", [
        "create_meal_plan", "save_recipe_rating", "get_shopping_list"
    ])

copy

01

02

03

04

05

06

07

08

09

10

11

12

def _setup_conversation_contexts(self):
    """Define specialized conversation contexts"""
    
    # Recipe Discovery Context
    self.prompt_add_context("recipe_search", [
        "search_recipes", "get_recipe_details", "suggest_alternatives"
    ])
    
    # Active Cooking Context  
    self.prompt_add_context("cooking_guidance", [
        "get_next_step", "set_timer", "handle_cooking_questions"
    ])
    
    # Meal Planning Context
    self.prompt_add_context("meal_planning", [
        "create_meal_plan", "save_recipe_rating", "get_shopping_list"
    ])

copy

01

02

03

04

05

06

07

08

09

10

11

12

def _setup_conversation_contexts(self):
    """Define specialized conversation contexts"""
    
    # Recipe Discovery Context
    self.prompt_add_context("recipe_search", [
        "search_recipes", "get_recipe_details", "suggest_alternatives"
    ])
    
    # Active Cooking Context  
    self.prompt_add_context("cooking_guidance", [
        "get_next_step", "set_timer", "handle_cooking_questions"
    ])
    
    # Meal Planning Context
    self.prompt_add_context("meal_planning", [
        "create_meal_plan", "save_recipe_rating", "get_shopping_list"
    ])

copy

01

02

03

04

05

06

07

08

09

10

11

12

def _setup_conversation_contexts(self):
    """Define specialized conversation contexts"""
    
    # Recipe Discovery Context
    self.prompt_add_context("recipe_search", [
        "search_recipes", "get_recipe_details", "suggest_alternatives"
    ])
    
    # Active Cooking Context  
    self.prompt_add_context("cooking_guidance", [
        "get_next_step", "set_timer", "handle_cooking_questions"
    ])
    
    # Meal Planning Context
    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

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Good: Specific, focused data extraction
output={
    "recipes": "${results[*].{id: id, title: title, cookTime: readyInMinutes}}"
}

# Avoid: Returning entire API responses
output={"raw_response": "${*}"}  # Too much unused data

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Good: Specific, focused data extraction
output={
    "recipes": "${results[*].{id: id, title: title, cookTime: readyInMinutes}}"
}

# Avoid: Returning entire API responses
output={"raw_response": "${*}"}  # Too much unused data

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Good: Specific, focused data extraction
output={
    "recipes": "${results[*].{id: id, title: title, cookTime: readyInMinutes}}"
}

# Avoid: Returning entire API responses
output={"raw_response": "${*}"}  # Too much unused data

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Good: Specific, focused data extraction
output={
    "recipes": "${results[*].{id: id, title: title, cookTime: readyInMinutes}}"
}

# Avoid: Returning entire API responses
output={"raw_response": "${*}"}  # Too much unused data



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

copy

01

02

03

04

05

06

07

08

09

10

11

12

return SwaigFunctionResult(
    # Good: Conversational, scannable for voice
    response="I found 3 great pasta recipes: **Creamy Mushroom Fettuccine** (25 mins), **Pasta Primavera** (20 mins), and **Carbonara** (15 mins). Which sounds good?",
    
    # Avoid: Dense text blocks hard to speak naturally
    response="Recipe search results: 1. Creamy Mushroom Fettuccine - Prep time 25 minutes, Difficulty intermediate..."
)

copy

01

02

03

04

05

06

07

08

09

10

11

12

return SwaigFunctionResult(
    # Good: Conversational, scannable for voice
    response="I found 3 great pasta recipes: **Creamy Mushroom Fettuccine** (25 mins), **Pasta Primavera** (20 mins), and **Carbonara** (15 mins). Which sounds good?",
    
    # Avoid: Dense text blocks hard to speak naturally
    response="Recipe search results: 1. Creamy Mushroom Fettuccine - Prep time 25 minutes, Difficulty intermediate..."
)

copy

01

02

03

04

05

06

07

08

09

10

11

12

return SwaigFunctionResult(
    # Good: Conversational, scannable for voice
    response="I found 3 great pasta recipes: **Creamy Mushroom Fettuccine** (25 mins), **Pasta Primavera** (20 mins), and **Carbonara** (15 mins). Which sounds good?",
    
    # Avoid: Dense text blocks hard to speak naturally
    response="Recipe search results: 1. Creamy Mushroom Fettuccine - Prep time 25 minutes, Difficulty intermediate..."
)

copy

01

02

03

04

05

06

07

08

09

10

11

12

return SwaigFunctionResult(
    # Good: Conversational, scannable for voice
    response="I found 3 great pasta recipes: **Creamy Mushroom Fettuccine** (25 mins), **Pasta Primavera** (20 mins), and **Carbonara** (15 mins). Which sounds good?",
    
    # Avoid: Dense text blocks hard to speak naturally
    response="Recipe search results: 1. Creamy Mushroom Fettuccine - Prep time 25 minutes, Difficulty intermediate..."
)



Production Deployment Guide

Environment Configuration

Set up your environment variables:

copy

01

02

03

04

05

06

07

08

09

10

11

12

export SPOONACULAR_API_KEY="your_api_key_here"
export SIGNALWIRE_PROJECT_ID="your_project_id"
export SIGNALWIRE_TOKEN="your_token"

copy

01

02

03

04

05

06

07

08

09

10

11

12

export SPOONACULAR_API_KEY="your_api_key_here"
export SIGNALWIRE_PROJECT_ID="your_project_id"
export SIGNALWIRE_TOKEN="your_token"

copy

01

02

03

04

05

06

07

08

09

10

11

12

export SPOONACULAR_API_KEY="your_api_key_here"
export SIGNALWIRE_PROJECT_ID="your_project_id"
export SIGNALWIRE_TOKEN="your_token"

copy

01

02

03

04

05

06

07

08

09

10

11

12

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:

copy

01

02

03

04

05

06

07

08

09

10

11

12

if __name__ == "__main__":
    RecipeAgent().run()  # Works in all environments

copy

01

02

03

04

05

06

07

08

09

10

11

12

if __name__ == "__main__":
    RecipeAgent().run()  # Works in all environments

copy

01

02

03

04

05

06

07

08

09

10

11

12

if __name__ == "__main__":
    RecipeAgent().run()  # Works in all environments

copy

01

02

03

04

05

06

07

08

09

10

11

12

if __name__ == "__main__":
    RecipeAgent().run()  # Works in all environments



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:

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Test SWAIG function directly
agent = RecipeAgent()
result = agent.search_recipes("pasta", ingredients="mushrooms", max_results=3)
print(result)

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Test SWAIG function directly
agent = RecipeAgent()
result = agent.search_recipes("pasta", ingredients="mushrooms", max_results=3)
print(result)

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Test SWAIG function directly
agent = RecipeAgent()
result = agent.search_recipes("pasta", ingredients="mushrooms", max_results=3)
print(result)

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Test SWAIG function directly
agent = RecipeAgent()
result = agent.search_recipes("pasta", ingredients="mushrooms", max_results=3)
print(result)



Common SWAIG Patterns and Examples

Pattern 1: Simple API Integration

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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)

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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)

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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)

copy

01

02

03

04

05

06

07

08

09

10

11

12

@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

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="process_order")
def process_order(self, product: str, quantity: int):
    # Check inventory
    if not self._check_inventory(product, quantity):
        return SwaigFunctionResult(response="Sorry, insufficient inventory")
    
    # Calculate pricing
    price = self._calculate_price(product, quantity)
    
    # Reserve inventory
    reservation = self._reserve_items(product, quantity)
    
    return SwaigFunctionResult(
        response=f"Reserved {quantity} {product} for ${price}. Proceed with order?",
        action_data={"reservation_id": reservation.id}
    )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="process_order")
def process_order(self, product: str, quantity: int):
    # Check inventory
    if not self._check_inventory(product, quantity):
        return SwaigFunctionResult(response="Sorry, insufficient inventory")
    
    # Calculate pricing
    price = self._calculate_price(product, quantity)
    
    # Reserve inventory
    reservation = self._reserve_items(product, quantity)
    
    return SwaigFunctionResult(
        response=f"Reserved {quantity} {product} for ${price}. Proceed with order?",
        action_data={"reservation_id": reservation.id}
    )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="process_order")
def process_order(self, product: str, quantity: int):
    # Check inventory
    if not self._check_inventory(product, quantity):
        return SwaigFunctionResult(response="Sorry, insufficient inventory")
    
    # Calculate pricing
    price = self._calculate_price(product, quantity)
    
    # Reserve inventory
    reservation = self._reserve_items(product, quantity)
    
    return SwaigFunctionResult(
        response=f"Reserved {quantity} {product} for ${price}. Proceed with order?",
        action_data={"reservation_id": reservation.id}
    )

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="process_order")
def process_order(self, product: str, quantity: int):
    # Check inventory
    if not self._check_inventory(product, quantity):
        return SwaigFunctionResult(response="Sorry, insufficient inventory")
    
    # Calculate pricing
    price = self._calculate_price(product, quantity)
    
    # Reserve inventory
    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

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="get_account_balance")
def get_account_balance(self):
    # Use caller ID for automatic authentication
    caller_id = self.session_id  # Contains caller information
    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)

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="get_account_balance")
def get_account_balance(self):
    # Use caller ID for automatic authentication
    caller_id = self.session_id  # Contains caller information
    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)

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="get_account_balance")
def get_account_balance(self):
    # Use caller ID for automatic authentication
    caller_id = self.session_id  # Contains caller information
    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)

copy

01

02

03

04

05

06

07

08

09

10

11

12

@SWAIGFunction(name="get_account_balance")
def get_account_balance(self):
    # Use caller ID for automatic authentication
    caller_id = self.session_id  # Contains caller information
    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

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Wrong: Hardcoded values
input={"query": "pasta"}

# Right: Use variable substitution  
input={"query": "${query}"}

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Wrong: Hardcoded values
input={"query": "pasta"}

# Right: Use variable substitution  
input={"query": "${query}"}

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Wrong: Hardcoded values
input={"query": "pasta"}

# Right: Use variable substitution  
input={"query": "${query}"}

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Wrong: Hardcoded values
input={"query": "pasta"}

# Right: Use variable substitution  
input={"query": "${query}"}



Missing Error Handling

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Add try/catch around API calls
try:
    return self.swaig_data_map(data_map, **kwargs)
except Exception as e:
    return SwaigFunctionResult(response="Sorry, I'm having technical difficulties.")

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Add try/catch around API calls
try:
    return self.swaig_data_map(data_map, **kwargs)
except Exception as e:
    return SwaigFunctionResult(response="Sorry, I'm having technical difficulties.")

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Add try/catch around API calls
try:
    return self.swaig_data_map(data_map, **kwargs)
except Exception as e:
    return SwaigFunctionResult(response="Sorry, I'm having technical difficulties.")

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Add try/catch around API calls
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

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Always provide defaults for state retrieval
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})  # ✅
user_prefs = self.state_manager.get_state(self.session_id, "preferences")      # ❌ May return None

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Always provide defaults for state retrieval
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})  # ✅
user_prefs = self.state_manager.get_state(self.session_id, "preferences")      # ❌ May return None

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Always provide defaults for state retrieval
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})  # ✅
user_prefs = self.state_manager.get_state(self.session_id, "preferences")      # ❌ May return None

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Always provide defaults for state retrieval
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})  # ✅
user_prefs = self.state_manager.get_state(self.session_id, "preferences")      # ❌ May return None



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

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://github.com/signalwire/signalwire-agents.git
cd signalwire-agents
pip install signalwire-agents
git clone https://github.com/signalwire/signalwire-agents.git
cd signalwire-agents
pip install signalwire-agents
git clone https://github.com/signalwire/signalwire-agents.git
cd signalwire-agents
pip install signalwire-agents
git clone https://github.com/signalwire/signalwire-agents.git
cd signalwire-agents



Step 2: Get Your API Keysfrom the SignalWire Dashboard

  • 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:

ngrok http 5000
ngrok http 5000
ngrok http 5000
ngrok http 5000


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.

Top Linear Gradient  Lines image

Frequently asked questions

Frequently asked questions

The questions we hear most, answered.

The questions we hear most, answered.

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Always provide defaults for state retrieval
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})  # ✅
user_prefs = self.state_manager.get_state(self.session_id, "preferences")      # ❌ May return None

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Always provide defaults for state retrieval
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})  # ✅
user_prefs = self.state_manager.get_state(self.session_id, "preferences")      # ❌ May return None

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Always provide defaults for state retrieval
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})  # ✅
user_prefs = self.state_manager.get_state(self.session_id, "preferences")      # ❌ May return None

copy

01

02

03

04

05

06

07

08

09

10

11

12

# Always provide defaults for state retrieval
user_prefs = self.state_manager.get_state(self.session_id, "preferences", {})  # ✅
user_prefs = self.state_manager.get_state(self.session_id, "preferences")      # ❌ May return None
Bottom Linear Gradient  Lines image

The Communications Stack for What's Next

APIs built for speed. Infrastructure built for scale. AI built in from day one.

The Communications Stack for What's Next

APIs built for speed. Infrastructure built for scale. AI built in from day one.

The Communications Stack for What's Next

APIs built for speed. Infrastructure built for scale. AI built in from day one.

The Communications Stack for What's Next

APIs built for speed. Infrastructure built for scale. AI built in from day one.