{"slug":"signalwire-agents-sdk","title":"SignalWire Agents SDK: Building Voice AI Agents with SWAIG","tags":["signalwire","voice-ai","agents","swaig","python","telephony"],"agent_summary":"SignalWire Agents SDK reference for building voice AI agents — AgentBase setup, SWAIG tool registration, FunctionResult patterns, DataMap for API passthrough, and multi-agent server deployment.","trigger_phrases":["SignalWire agents","SWAIG","voice AI agent","AgentBase","FunctionResult","DataMap builder","SignalWire SDK","telephony AI"],"runnable":false,"markdown":"\n## Overview\n\nSignalWire Agents SDK (Python) for building production voice AI agents. The SDK handles telephony transport, speech recognition, TTS, and the SWAIG (SignalWire AI Gateway) function calling protocol.\n\n## Core Architecture\n\n```\nCaller → SignalWire → AgentBase → SWAIG Function\n                          ↑\n              (speech recognition, TTS, routing)\n```\n\nYour agent is a Python class extending `AgentBase`. You define prompt, personality, and tools (`@tool` decorated methods). SignalWire handles all telephony complexity.\n\n## AgentBase Setup\n\n```python\nfrom signalwire_agents import AgentBase\n\nclass CustomerServiceAgent(AgentBase):\n    def __init__(self):\n        super().__init__(\n            name=\"customer-service\",\n            route=\"/customer-service\",\n            host=\"0.0.0.0\",\n            port=3000,\n        )\n\n        # AI model configuration\n        self.set_params({\n            \"ai_model\": \"gpt-4o\",\n            \"temperature\": 0.7,\n            \"max_tokens\": 1024,\n        })\n\n        # Voice configuration\n        self.set_params({\n            \"voice\": \"elevenlabs.rachel\",\n            \"language\": \"en-US\",\n        })\n\n        # System prompt\n        self.set_prompt_text(\n            \"You are a helpful customer service agent for Acme Corp. \"\n            \"Be professional, concise, and empathetic. \"\n            \"Always verify the customer's account before discussing order details.\"\n        )\n\nif __name__ == \"__main__\":\n    agent = CustomerServiceAgent()\n    agent.run()\n```\n\n## @tool Decorator — SWAIG Function Registration\n\n```python\nfrom signalwire_agents import AgentBase\nfrom signalwire_agents.core.function_result import SwaigFunctionResult\n\nclass SalesAgent(AgentBase):\n    def __init__(self):\n        super().__init__(name=\"sales\", route=\"/sales\")\n        self.set_prompt_text(\"You are a sales assistant.\")\n\n    @AgentBase.tool(\n        name=\"lookup_pricing\",\n        description=\"Look up pricing for a product\",\n        parameters={\n            \"product_id\": {\n                \"type\": \"string\",\n                \"description\": \"The product ID to look up\",\n            },\n            \"quantity\": {\n                \"type\": \"integer\",\n                \"description\": \"Number of units\",\n                \"default\": 1,\n            },\n        },\n        required=[\"product_id\"],\n    )\n    def lookup_pricing(self, args, raw_data) -> SwaigFunctionResult:\n        product_id = args.get(\"product_id\")\n        quantity = args.get(\"quantity\", 1)\n\n        # Look up price in database\n        price = self.db.get_price(product_id)\n        if not price:\n            return SwaigFunctionResult(f\"Product {product_id} not found.\")\n\n        total = price * quantity\n        return SwaigFunctionResult(\n            f\"Product {product_id}: ${price:.2f} each. \"\n            f\"Total for {quantity} units: ${total:.2f}.\"\n        )\n```\n\n## FunctionResult Patterns\n\n```python\nfrom signalwire_agents.core.function_result import SwaigFunctionResult\n\n# Simple text response\nreturn SwaigFunctionResult(\"The order has been placed.\")\n\n# Stop the conversation\nreturn SwaigFunctionResult(\"Goodbye! Have a great day.\").stop()\n\n# Transfer to another number\nreturn SwaigFunctionResult(\"Connecting you to a specialist.\").transfer(\"+15551234567\")\n\n# Transfer to another AI agent\nreturn SwaigFunctionResult(\"Transferring to billing.\").transfer_to_agent(\"/billing\")\n\n# Post action: collect information\nreturn SwaigFunctionResult(\"What is your account number?\").need_input()\n```\n\n## DataMap Builder — Server-Side API Passthrough\n\nDataMap lets the SignalWire platform call external APIs directly, without your Python server in the loop:\n\n```python\nfrom signalwire_agents.core.data_map import DataMap\n\nclass WeatherAgent(AgentBase):\n    def __init__(self):\n        super().__init__(name=\"weather\", route=\"/weather\")\n\n        # Build a DataMap tool — SignalWire calls the API, not your server\n        weather_tool = (\n            DataMap(\"get_weather\")\n            .description(\"Get current weather for a city\")\n            .parameter(\"city\", \"string\", \"City name\", required=True)\n            .webhook(\n                method=\"GET\",\n                url=\"https://api.openweathermap.org/data/2.5/weather\",\n                params={\n                    \"q\": \"${args.city}\",\n                    \"appid\": \"${env.OPENWEATHER_API_KEY}\",\n                    \"units\": \"metric\",\n                },\n            )\n            .output(\n                SwaigFunctionResult(\n                    \"Current weather in ${args.city}: \"\n                    \"${response.weather[0].description}, \"\n                    \"${response.main.temp}°C\"\n                )\n            )\n        )\n        self.register_tool(weather_tool)\n```\n\n## Multi-Agent Server\n\n```python\nfrom signalwire_agents import AgentServer\n\nfrom agents.sales import SalesAgent\nfrom agents.support import SupportAgent\nfrom agents.billing import BillingAgent\n\nserver = AgentServer(host=\"0.0.0.0\", port=3000)\n\nserver.register(SalesAgent())\nserver.register(SupportAgent())\nserver.register(BillingAgent())\n\n# Each agent is available at its configured route:\n# /sales, /support, /billing\nserver.run()\n```\n\n## Built-In Skills\n\n```python\nclass MyAgent(AgentBase):\n    def __init__(self):\n        super().__init__(name=\"my-agent\", route=\"/agent\")\n\n        # Add pre-built capabilities\n        self.add_skill(\"datetime\")       # Tell time/date\n        self.add_skill(\"web_search\")     # Search the web\n        self.add_skill(\"calculator\")     # Math operations\n        self.add_skill(\"send_sms\", {     # Send SMS (with config)\n            \"from_number\": \"+15550001111\",\n        })\n```\n\n## Anti-Patterns\n\n| Anti-Pattern | Problem | Fix |\n|--------------|---------|-----|\n| Blocking I/O in tool handler | Blocks other calls on the thread | Use `asyncio` or thread pool |\n| Secrets hardcoded in `@tool` | Visible in logs/tracebacks | Use `${env.VAR}` in DataMap or `os.getenv()` |\n| Single agent handling all intents | Prompt bloat, poor accuracy | Split by domain — sales, support, billing |\n| No `FunctionResult.stop()` on end | Call may hang waiting for next input | Always `.stop()` on farewell |\n\n## Deployment\n\n```dockerfile\nFROM python:3.11-slim\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY . .\nENV PORT=3000\nEXPOSE 3000\nCMD [\"python\", \"main.py\"]\n```\n\nSignalWire must be able to reach your agent's port via HTTP. Use a reverse proxy (nginx) or deploy behind Render/Railway/Fly.io.\n","html":"<h2>Overview</h2>\n<p>SignalWire Agents SDK (Python) for building production voice AI agents. The SDK handles telephony transport, speech recognition, TTS, and the SWAIG (SignalWire AI Gateway) function calling protocol.</p>\n<h2>Core Architecture</h2>\n<pre><code>Caller → SignalWire → AgentBase → SWAIG Function\n                          ↑\n              (speech recognition, TTS, routing)\n</code></pre>\n<p>Your agent is a Python class extending <code>AgentBase</code>. You define prompt, personality, and tools (<code>@tool</code> decorated methods). SignalWire handles all telephony complexity.</p>\n<h2>AgentBase Setup</h2>\n<pre><code class=\"language-python\">from signalwire_agents import AgentBase\n\nclass CustomerServiceAgent(AgentBase):\n    def __init__(self):\n        super().__init__(\n            name=\"customer-service\",\n            route=\"/customer-service\",\n            host=\"0.0.0.0\",\n            port=3000,\n        )\n\n        # AI model configuration\n        self.set_params({\n            \"ai_model\": \"gpt-4o\",\n            \"temperature\": 0.7,\n            \"max_tokens\": 1024,\n        })\n\n        # Voice configuration\n        self.set_params({\n            \"voice\": \"elevenlabs.rachel\",\n            \"language\": \"en-US\",\n        })\n\n        # System prompt\n        self.set_prompt_text(\n            \"You are a helpful customer service agent for Acme Corp. \"\n            \"Be professional, concise, and empathetic. \"\n            \"Always verify the customer's account before discussing order details.\"\n        )\n\nif __name__ == \"__main__\":\n    agent = CustomerServiceAgent()\n    agent.run()\n</code></pre>\n<h2>@tool Decorator — SWAIG Function Registration</h2>\n<pre><code class=\"language-python\">from signalwire_agents import AgentBase\nfrom signalwire_agents.core.function_result import SwaigFunctionResult\n\nclass SalesAgent(AgentBase):\n    def __init__(self):\n        super().__init__(name=\"sales\", route=\"/sales\")\n        self.set_prompt_text(\"You are a sales assistant.\")\n\n    @AgentBase.tool(\n        name=\"lookup_pricing\",\n        description=\"Look up pricing for a product\",\n        parameters={\n            \"product_id\": {\n                \"type\": \"string\",\n                \"description\": \"The product ID to look up\",\n            },\n            \"quantity\": {\n                \"type\": \"integer\",\n                \"description\": \"Number of units\",\n                \"default\": 1,\n            },\n        },\n        required=[\"product_id\"],\n    )\n    def lookup_pricing(self, args, raw_data) -> SwaigFunctionResult:\n        product_id = args.get(\"product_id\")\n        quantity = args.get(\"quantity\", 1)\n\n        # Look up price in database\n        price = self.db.get_price(product_id)\n        if not price:\n            return SwaigFunctionResult(f\"Product {product_id} not found.\")\n\n        total = price * quantity\n        return SwaigFunctionResult(\n            f\"Product {product_id}: ${price:.2f} each. \"\n            f\"Total for {quantity} units: ${total:.2f}.\"\n        )\n</code></pre>\n<h2>FunctionResult Patterns</h2>\n<pre><code class=\"language-python\">from signalwire_agents.core.function_result import SwaigFunctionResult\n\n# Simple text response\nreturn SwaigFunctionResult(\"The order has been placed.\")\n\n# Stop the conversation\nreturn SwaigFunctionResult(\"Goodbye! Have a great day.\").stop()\n\n# Transfer to another number\nreturn SwaigFunctionResult(\"Connecting you to a specialist.\").transfer(\"+15551234567\")\n\n# Transfer to another AI agent\nreturn SwaigFunctionResult(\"Transferring to billing.\").transfer_to_agent(\"/billing\")\n\n# Post action: collect information\nreturn SwaigFunctionResult(\"What is your account number?\").need_input()\n</code></pre>\n<h2>DataMap Builder — Server-Side API Passthrough</h2>\n<p>DataMap lets the SignalWire platform call external APIs directly, without your Python server in the loop:</p>\n<pre><code class=\"language-python\">from signalwire_agents.core.data_map import DataMap\n\nclass WeatherAgent(AgentBase):\n    def __init__(self):\n        super().__init__(name=\"weather\", route=\"/weather\")\n\n        # Build a DataMap tool — SignalWire calls the API, not your server\n        weather_tool = (\n            DataMap(\"get_weather\")\n            .description(\"Get current weather for a city\")\n            .parameter(\"city\", \"string\", \"City name\", required=True)\n            .webhook(\n                method=\"GET\",\n                url=\"https://api.openweathermap.org/data/2.5/weather\",\n                params={\n                    \"q\": \"${args.city}\",\n                    \"appid\": \"${env.OPENWEATHER_API_KEY}\",\n                    \"units\": \"metric\",\n                },\n            )\n            .output(\n                SwaigFunctionResult(\n                    \"Current weather in ${args.city}: \"\n                    \"${response.weather[0].description}, \"\n                    \"${response.main.temp}°C\"\n                )\n            )\n        )\n        self.register_tool(weather_tool)\n</code></pre>\n<h2>Multi-Agent Server</h2>\n<pre><code class=\"language-python\">from signalwire_agents import AgentServer\n\nfrom agents.sales import SalesAgent\nfrom agents.support import SupportAgent\nfrom agents.billing import BillingAgent\n\nserver = AgentServer(host=\"0.0.0.0\", port=3000)\n\nserver.register(SalesAgent())\nserver.register(SupportAgent())\nserver.register(BillingAgent())\n\n# Each agent is available at its configured route:\n# /sales, /support, /billing\nserver.run()\n</code></pre>\n<h2>Built-In Skills</h2>\n<pre><code class=\"language-python\">class MyAgent(AgentBase):\n    def __init__(self):\n        super().__init__(name=\"my-agent\", route=\"/agent\")\n\n        # Add pre-built capabilities\n        self.add_skill(\"datetime\")       # Tell time/date\n        self.add_skill(\"web_search\")     # Search the web\n        self.add_skill(\"calculator\")     # Math operations\n        self.add_skill(\"send_sms\", {     # Send SMS (with config)\n            \"from_number\": \"+15550001111\",\n        })\n</code></pre>\n<h2>Anti-Patterns</h2>\n<p>| Anti-Pattern | Problem | Fix |\n|--------------|---------|-----|\n| Blocking I/O in tool handler | Blocks other calls on the thread | Use <code>asyncio</code> or thread pool |\n| Secrets hardcoded in <code>@tool</code> | Visible in logs/tracebacks | Use <code>${env.VAR}</code> in DataMap or <code>os.getenv()</code> |\n| Single agent handling all intents | Prompt bloat, poor accuracy | Split by domain — sales, support, billing |\n| No <code>FunctionResult.stop()</code> on end | Call may hang waiting for next input | Always <code>.stop()</code> on farewell |</p>\n<h2>Deployment</h2>\n<pre><code class=\"language-dockerfile\">FROM python:3.11-slim\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY . .\nENV PORT=3000\nEXPOSE 3000\nCMD [\"python\", \"main.py\"]\n</code></pre>\n<p>SignalWire must be able to reach your agent's port via HTTP. Use a reverse proxy (nginx) or deploy behind Render/Railway/Fly.io.</p>\n"}