{"slug":"signalwire-voice-agents","title":"SignalWire Voice AI Agents: SWML, SWAIG, and the Python Agents SDK","tags":["signalwire","voice-ai","swml","swaig","python","telephony","agents"],"agent_summary":"SignalWire voice agent development — SWML verb structure, SWAIG tool-calling functions, data_map serverless handlers, Python AgentBase SDK, Call Flow Builder nodes, and IVR-to-AI migration patterns.","trigger_phrases":["SignalWire","SWML","SWAIG","voice AI agent","SignalWire call flow","AgentBase","voice agent","IVR replacement","signalwire-agents"],"runnable":false,"markdown":"\n## Overview\n\nSignalWire provides three layers for building voice AI: the Call Flow Builder (visual, no-code), SWML (YAML/JSON script), and the Python Agents SDK (code-first). All three converge on the same runtime — SWML is the intermediate representation.\n\n## SWML Structure\n\n```yaml\nversion: 1.0.0\nsections:\n  main:\n    - answer\n    - ai:\n        prompt:\n          text: |\n            You are a helpful receptionist for Acme Corp.\n            Be friendly, concise, and professional.\n        SWAIG:\n          functions:\n            - function: get_hours\n              description: \"Get business hours. Call when user asks when we're open.\"\n              parameters:\n                type: object\n                properties: {}\n              web_hook_url: https://api.example.com/swaig\n```\n\n## SWAIG Function Schema\n\nSWAIG (SignalWire AI Gateway) is the tool-calling mechanism. Functions are defined inside `ai.SWAIG.functions[]`.\n\n```json\n{\n  \"function\": \"get_balance\",\n  \"description\": \"Get customer account balance. Call when user asks about balance.\",\n  \"active\": true,\n  \"parameters\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"account_id\": {\n        \"type\": \"string\",\n        \"description\": \"Customer account ID\"\n      }\n    },\n    \"required\": [\"account_id\"]\n  },\n  \"web_hook_url\": \"https://api.example.com/swaig\",\n  \"fillers\": {\n    \"en-US\": [\"Let me check on that...\", \"One moment please...\"]\n  }\n}\n```\n\nWhen the AI invokes a function, it POSTs to `web_hook_url`. Response must include:\n\n```json\n{\n  \"response\": \"Your balance is $127.50.\",\n  \"action\": []\n}\n```\n\n## data_map: Serverless Function Handler\n\nFor simple API passthrough without a webhook server, use `data_map`:\n\n```yaml\n- function: get_weather\n  description: \"Get current weather. Call when user asks about weather.\"\n  parameters:\n    type: object\n    properties:\n      city:\n        type: string\n  data_map:\n    webhooks:\n      - url: \"https://api.weather.com/v1/current?city=${args.city}&key=API_KEY\"\n        method: GET\n        output:\n          response: \"The weather in ${args.city} is ${response.condition}, ${response.temp_f}°F.\"\n```\n\n`data_map` evaluates expressions inline — no server required.\n\n## Python Agents SDK (AgentBase)\n\n```python\nfrom signalwire import AgentBase\n\nclass MyAgent(AgentBase):\n    def __init__(self):\n        super().__init__(\n            name=\"receptionist\",\n            route=\"/\",\n            host=\"0.0.0.0\",\n            port=3000,\n        )\n        self.set_prompt_text(\n            \"You are a helpful receptionist for Acme Corp. Be concise.\"\n        )\n\n        @self.tool(\n            name=\"get_hours\",\n            description=\"Get business hours. Call when user asks when we're open.\",\n        )\n        def get_hours():\n            return FunctionResult(\"We're open Monday–Friday, 9am to 5pm PST.\")\n\nagent = MyAgent()\nagent.run()\n```\n\n### Key AgentBase Methods\n\n| Method | Purpose |\n|--------|---------|\n| `set_prompt_text(text)` | Set main system prompt |\n| `set_temperature(n)` | AI temperature (0.0–2.0) |\n| `set_max_tokens(n)` | Response length limit |\n| `set_language(\"en-US\")` | Voice language |\n| `set_voice(\"en-US-Neural2-F\")` | TTS voice |\n| `set_record_call(True)` | Enable call recording |\n| `add_language(\"es-US\", ...)` | Multi-language support |\n\n### SWAIG Defaults\n\n```python\nself.set_swaig_defaults(\n    web_hook_url=\"https://api.example.com/swaig\",\n    web_hook_auth_user=\"user\",\n    web_hook_auth_password=\"secret\",\n)\n```\n\nApplies authentication to all functions that don't override it.\n\n## Call Flow Builder Nodes\n\n| Node | Description |\n|------|-------------|\n| **Handle Call** | Entry point (mandatory, one per flow) |\n| **AI Agent** | Connects to an AI Agent resource |\n| **Gather Input** | Collects DTMF or speech |\n| **Forward to Phone** | Transfer to number or SIP endpoint |\n| **Play Audio or TTS** | Play file, silence, or synthesized speech |\n| **Request** | HTTP GET/POST to external API |\n| **Conditions** | JavaScript if/else logic |\n| **Set Variables** | Create `%{vars.key}` variables |\n| **Execute SWML** | Fetch and run remote SWML document |\n| **Voicemail Recording** | Async voicemail capture |\n\n### Variables in CFB\n\n```\n%{call.from}        # Caller's phone number\n%{call.to}          # Dialed number\n%{vars.account_id}  # Custom variable set via Set Variables node\n%{request_response.balance}  # Field from a Request node response\n%{record_call_url}  # URL of recorded call\n```\n\n## IVR to AI Migration Pattern\n\n**Before (IVR tree):**\n```\nPress 1 for Hours → Play hours message\nPress 2 for Location → Play address\nPress 3 for Agent → Transfer to queue\n```\n\n**After (AI agent):**\n```yaml\nai:\n  prompt:\n    text: |\n      You handle calls for Acme Corp.\n      For hours, say: Monday–Friday 9am–5pm PST.\n      For location, say: 123 Main St, Portland OR.\n      For agent requests, use transfer_to_agent function.\n  SWAIG:\n    functions:\n      - function: transfer_to_agent\n        description: \"Transfer caller to live agent when requested.\"\n        parameters:\n          type: object\n          properties: {}\n        data_map:\n          output:\n            action:\n              - transfer: \"+15551234567\"\n```\n\n## Webhook Auth\n\nSignalWire signs SWAIG webhook requests with HMAC-SHA256. Verify in your handler:\n\n```python\nimport hmac, hashlib\n\ndef verify_swaig_signature(body: bytes, signature: str, secret: str) -> bool:\n    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, signature)\n```\n\n## Multi-Agent Server\n\n```python\nfrom signalwire import AgentServer\n\nserver = AgentServer(host=\"0.0.0.0\", port=3000)\nserver.add_agent(ReceptionistAgent(), route=\"/reception\")\nserver.add_agent(SupportAgent(), route=\"/support\")\nserver.run()\n```\n","html":"<h2>Overview</h2>\n<p>SignalWire provides three layers for building voice AI: the Call Flow Builder (visual, no-code), SWML (YAML/JSON script), and the Python Agents SDK (code-first). All three converge on the same runtime — SWML is the intermediate representation.</p>\n<h2>SWML Structure</h2>\n<pre><code class=\"language-yaml\">version: 1.0.0\nsections:\n  main:\n    - answer\n    - ai:\n        prompt:\n          text: |\n            You are a helpful receptionist for Acme Corp.\n            Be friendly, concise, and professional.\n        SWAIG:\n          functions:\n            - function: get_hours\n              description: \"Get business hours. Call when user asks when we're open.\"\n              parameters:\n                type: object\n                properties: {}\n              web_hook_url: https://api.example.com/swaig\n</code></pre>\n<h2>SWAIG Function Schema</h2>\n<p>SWAIG (SignalWire AI Gateway) is the tool-calling mechanism. Functions are defined inside <code>ai.SWAIG.functions[]</code>.</p>\n<pre><code class=\"language-json\">{\n  \"function\": \"get_balance\",\n  \"description\": \"Get customer account balance. Call when user asks about balance.\",\n  \"active\": true,\n  \"parameters\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"account_id\": {\n        \"type\": \"string\",\n        \"description\": \"Customer account ID\"\n      }\n    },\n    \"required\": [\"account_id\"]\n  },\n  \"web_hook_url\": \"https://api.example.com/swaig\",\n  \"fillers\": {\n    \"en-US\": [\"Let me check on that...\", \"One moment please...\"]\n  }\n}\n</code></pre>\n<p>When the AI invokes a function, it POSTs to <code>web_hook_url</code>. Response must include:</p>\n<pre><code class=\"language-json\">{\n  \"response\": \"Your balance is $127.50.\",\n  \"action\": []\n}\n</code></pre>\n<h2>data_map: Serverless Function Handler</h2>\n<p>For simple API passthrough without a webhook server, use <code>data_map</code>:</p>\n<pre><code class=\"language-yaml\">- function: get_weather\n  description: \"Get current weather. Call when user asks about weather.\"\n  parameters:\n    type: object\n    properties:\n      city:\n        type: string\n  data_map:\n    webhooks:\n      - url: \"https://api.weather.com/v1/current?city=${args.city}&#x26;key=API_KEY\"\n        method: GET\n        output:\n          response: \"The weather in ${args.city} is ${response.condition}, ${response.temp_f}°F.\"\n</code></pre>\n<p><code>data_map</code> evaluates expressions inline — no server required.</p>\n<h2>Python Agents SDK (AgentBase)</h2>\n<pre><code class=\"language-python\">from signalwire import AgentBase\n\nclass MyAgent(AgentBase):\n    def __init__(self):\n        super().__init__(\n            name=\"receptionist\",\n            route=\"/\",\n            host=\"0.0.0.0\",\n            port=3000,\n        )\n        self.set_prompt_text(\n            \"You are a helpful receptionist for Acme Corp. Be concise.\"\n        )\n\n        @self.tool(\n            name=\"get_hours\",\n            description=\"Get business hours. Call when user asks when we're open.\",\n        )\n        def get_hours():\n            return FunctionResult(\"We're open Monday–Friday, 9am to 5pm PST.\")\n\nagent = MyAgent()\nagent.run()\n</code></pre>\n<h3>Key AgentBase Methods</h3>\n<p>| Method | Purpose |\n|--------|---------|\n| <code>set_prompt_text(text)</code> | Set main system prompt |\n| <code>set_temperature(n)</code> | AI temperature (0.0–2.0) |\n| <code>set_max_tokens(n)</code> | Response length limit |\n| <code>set_language(\"en-US\")</code> | Voice language |\n| <code>set_voice(\"en-US-Neural2-F\")</code> | TTS voice |\n| <code>set_record_call(True)</code> | Enable call recording |\n| <code>add_language(\"es-US\", ...)</code> | Multi-language support |</p>\n<h3>SWAIG Defaults</h3>\n<pre><code class=\"language-python\">self.set_swaig_defaults(\n    web_hook_url=\"https://api.example.com/swaig\",\n    web_hook_auth_user=\"user\",\n    web_hook_auth_password=\"secret\",\n)\n</code></pre>\n<p>Applies authentication to all functions that don't override it.</p>\n<h2>Call Flow Builder Nodes</h2>\n<p>| Node | Description |\n|------|-------------|\n| <strong>Handle Call</strong> | Entry point (mandatory, one per flow) |\n| <strong>AI Agent</strong> | Connects to an AI Agent resource |\n| <strong>Gather Input</strong> | Collects DTMF or speech |\n| <strong>Forward to Phone</strong> | Transfer to number or SIP endpoint |\n| <strong>Play Audio or TTS</strong> | Play file, silence, or synthesized speech |\n| <strong>Request</strong> | HTTP GET/POST to external API |\n| <strong>Conditions</strong> | JavaScript if/else logic |\n| <strong>Set Variables</strong> | Create <code>%{vars.key}</code> variables |\n| <strong>Execute SWML</strong> | Fetch and run remote SWML document |\n| <strong>Voicemail Recording</strong> | Async voicemail capture |</p>\n<h3>Variables in CFB</h3>\n<pre><code>%{call.from}        # Caller's phone number\n%{call.to}          # Dialed number\n%{vars.account_id}  # Custom variable set via Set Variables node\n%{request_response.balance}  # Field from a Request node response\n%{record_call_url}  # URL of recorded call\n</code></pre>\n<h2>IVR to AI Migration Pattern</h2>\n<p><strong>Before (IVR tree):</strong></p>\n<pre><code>Press 1 for Hours → Play hours message\nPress 2 for Location → Play address\nPress 3 for Agent → Transfer to queue\n</code></pre>\n<p><strong>After (AI agent):</strong></p>\n<pre><code class=\"language-yaml\">ai:\n  prompt:\n    text: |\n      You handle calls for Acme Corp.\n      For hours, say: Monday–Friday 9am–5pm PST.\n      For location, say: 123 Main St, Portland OR.\n      For agent requests, use transfer_to_agent function.\n  SWAIG:\n    functions:\n      - function: transfer_to_agent\n        description: \"Transfer caller to live agent when requested.\"\n        parameters:\n          type: object\n          properties: {}\n        data_map:\n          output:\n            action:\n              - transfer: \"+15551234567\"\n</code></pre>\n<h2>Webhook Auth</h2>\n<p>SignalWire signs SWAIG webhook requests with HMAC-SHA256. Verify in your handler:</p>\n<pre><code class=\"language-python\">import hmac, hashlib\n\ndef verify_swaig_signature(body: bytes, signature: str, secret: str) -> bool:\n    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, signature)\n</code></pre>\n<h2>Multi-Agent Server</h2>\n<pre><code class=\"language-python\">from signalwire import AgentServer\n\nserver = AgentServer(host=\"0.0.0.0\", port=3000)\nserver.add_agent(ReceptionistAgent(), route=\"/reception\")\nserver.add_agent(SupportAgent(), route=\"/support\")\nserver.run()\n</code></pre>\n"}