SWAIG (SignalWire AI Gateway)

View as MarkdownOpen in Claude

What is SWAIG?

SWAIG (SignalWire AI Gateway) connects the AI conversation to your backend logic. When the AI decides it needs to perform an action (like looking up an order or checking a balance), it calls a SWAIG function that you’ve defined.

SWAIG function call flow.
SWAIG Function Flow

SWAIG in SWML

When your agent generates SWML, it includes SWAIG function definitions in the ai verb:

1{
2 "version": "1.0.0",
3 "sections": {
4 "main": [
5 {
6 "ai": {
7 "SWAIG": {
8 "defaults": {
9 "web_hook_url": "https://your-agent.com/swaig"
10 },
11 "functions": [
12 {
13 "function": "get_balance",
14 "description": "Get the customer's current account balance",
15 "parameters": {
16 "type": "object",
17 "properties": {
18 "account_id": {
19 "type": "string",
20 "description": "The customer's account ID"
21 }
22 },
23 "required": ["account_id"]
24 }
25 }
26 ]
27 }
28 }
29 }
30 ]
31 }
32}

Defining SWAIG Functions

There are three ways to define SWAIG functions in your agent:

Method 1: define_tool()

The most explicit way to register a function:

1from signalwire import AgentBase, FunctionResult
2
3class MyAgent(AgentBase):
4 def __init__(self):
5 super().__init__(name="my-agent")
6
7 self.define_tool(
8 name="get_balance",
9 description="Get account balance for a customer",
10 parameters={
11 "type": "object",
12 "properties": {
13 "account_id": {
14 "type": "string",
15 "description": "The account ID to look up"
16 }
17 },
18 "required": ["account_id"]
19 },
20 handler=self.get_balance
21 )
22
23 def get_balance(self, args, raw_data):
24 account_id = args.get("account_id")
25 return FunctionResult(f"Account {account_id} has a balance of $150.00")

Method 2: @AgentBase.tool Decorator

A cleaner approach using decorators:

1from signalwire import AgentBase, FunctionResult
2
3class MyAgent(AgentBase):
4 def __init__(self):
5 super().__init__(name="my-agent")
6
7 @AgentBase.tool(
8 name="get_balance",
9 description="Get account balance for a customer",
10 parameters={
11 "type": "object",
12 "properties": {
13 "account_id": {
14 "type": "string",
15 "description": "The account ID to look up"
16 }
17 },
18 "required": ["account_id"]
19 }
20 )
21 def get_balance(self, args, raw_data):
22 account_id = args.get("account_id")
23 return FunctionResult(f"Account {account_id} has a balance of $150.00")

Method 3: DataMap (Serverless)

For direct API integration without code:

1from signalwire import AgentBase
2
3class MyAgent(AgentBase):
4 def __init__(self):
5 super().__init__(name="my-agent")
6
7 self.data_map.add_tool(
8 name="get_balance",
9 description="Get account balance",
10 parameters={
11 "account_id": {
12 "type": "string",
13 "description": "The account ID"
14 }
15 },
16 data_map={
17 "webhooks": [
18 {
19 "url": "https://api.example.com/accounts/${enc:args.account_id}/balance",
20 "method": "GET",
21 "headers": {
22 "Authorization": "Bearer ${env.API_KEY}"
23 },
24 "output": {
25 "response": "Account balance is $${balance}",
26 "action": [{"set_global_data": {"balance": "${balance}"}}]
27 }
28 }
29 ]
30 }
31 )

Function Handler Signature

Every SWAIG function handler receives two arguments — the parsed arguments and the full request payload:

LanguageHandler Signature
Pythondef handler(self, args: dict, raw_data: dict)
TypeScript(args: Record<string, unknown>, rawData: Record<string, unknown>) => FunctionResult

The raw_data Payload

The raw_data contains rich context about the call:

1def my_function(self, args, raw_data):
2 # Call metadata
3 # Call information (nested under 'call' key)
4 call_data = raw_data.get("call", {})
5 call_id = call_data.get("call_id") or raw_data.get("call_id") # Fallback for compatibility
6 call_sid = raw_data.get("call_sid")
7
8 # Caller information (from nested call object)
9 from_number = call_data.get("from") or call_data.get("from_number")
10 to_number = call_data.get("to") or call_data.get("to_number")
11
12 # Global data (shared state)
13 global_data = raw_data.get("global_data", {})
14 customer_name = global_data.get("customer_name")
15
16 # Conversation context
17 meta_data = raw_data.get("meta_data", {})
18
19 return FunctionResult("Processed")

FunctionResult

Always return a FunctionResult from your handlers. The class name varies by language:

LanguageFunctionResult Constructor
PythonFunctionResult("text")
TypeScriptFunctionResult("text")
1from signalwire import FunctionResult
2
3def simple_response(self, args, raw_data):
4 # Simple text response - AI will speak this
5 return FunctionResult("Your order has been placed successfully.")
6
7def response_with_actions(self, args, raw_data):
8 result = FunctionResult("Transferring you now.")
9 result.connect("+15551234567", final=True, from_addr="+15559876543")
10 return result
11
12def response_with_data(self, args, raw_data):
13 result = FunctionResult("I've saved your preferences.")
14 result.update_global_data({"user_preference": "email", "confirmed": True})
15 return result

Common Actions

ActionPurposeExample
set_global_dataStore data for later use{"key": "value"}
transferEnd AI, prepare for transferTrue
swmlExecute SWML after AI ends{"version": "1.0.0", ...}
stopEnd the AI conversationTrue
toggle_functionsEnable/disable functions[{"active": false, "function": "fn_name"}]
saySpeak text immediately"Please hold..."
play_filePlay audio file"https://example.com/hold_music.mp3"

SWAIG Request Flow

SWAIG request processing flow.
SWAIG Request Processing

SWAIG Request Format

SignalWire sends a POST request with this structure:

1{
2 "action": "swaig_action",
3 "function": "get_balance",
4 "argument": {
5 "parsed": [
6 {
7 "account_id": "12345"
8 }
9 ],
10 "raw": "{\"account_id\": \"12345\"}"
11 },
12 "call": {
13 "call_id": "uuid-here",
14 "from": "+15551234567",
15 "from_number": "+15551234567",
16 "to": "+15559876543",
17 "to_number": "+15559876543",
18 "direction": "inbound"
19 },
20 "call_id": "uuid-here",
21 "call_sid": "call-sid-here",
22 "global_data": {
23 "customer_name": "John Doe"
24 },
25 "meta_data": {},
26 "ai_session_id": "session-uuid"
27}

Call information (caller/callee numbers, call_id, direction) is nested under the call key. Always use defensive access: call_data = raw_data.get("call", {}). Some fields may also appear at the top level for backwards compatibility.

SWAIG Response Format

Your agent responds with:

1{
2 "response": "The account balance is $150.00",
3 "action": [
4 {
5 "set_global_data": {
6 "last_balance_check": "2024-01-15T10:30:00Z"
7 }
8 }
9 ]
10}

Or for a transfer:

1{
2 "response": "Transferring you to a specialist now.",
3 "action": [
4 {"transfer": true},
5 {
6 "swml": {
7 "version": "1.0.0",
8 "sections": {
9 "main": [
10 {"connect": {"to": "+15551234567", "from": "+15559876543"}}
11 ]
12 }
13 }
14 }
15 ]
16}

Function Parameters (JSON Schema)

SWAIG functions use JSON Schema for parameter definitions:

1self.define_tool(
2 name="search_orders",
3 description="Search customer orders",
4 parameters={
5 "type": "object",
6 "properties": {
7 "customer_id": {
8 "type": "string",
9 "description": "Customer ID to search for"
10 },
11 "status": {
12 "type": "string",
13 "enum": ["pending", "shipped", "delivered", "cancelled"],
14 "description": "Filter by order status"
15 },
16 "limit": {
17 "type": "integer",
18 "description": "Maximum number of results",
19 "default": 10
20 },
21 "include_details": {
22 "type": "boolean",
23 "description": "Include full order details",
24 "default": False
25 }
26 },
27 "required": ["customer_id"]
28 },
29 handler=self.search_orders
30)

Webhook Security

SWAIG endpoints support multiple security layers:

  1. Basic Authentication: HTTP Basic Auth on all requests
  2. Function Tokens: Per-function security tokens
  3. HTTPS: TLS encryption in transit
1## Function-specific token security
2self.define_tool(
3 name="sensitive_action",
4 description="Perform a sensitive action",
5 parameters={...},
6 handler=self.sensitive_action,
7 secure=True # Enables per-function token validation
8)

Testing SWAIG Functions

Use swaig-test to test functions locally:

$## List all registered functions
$swaig-test my_agent.py --list-tools
$
$## Execute a function with arguments
$swaig-test my_agent.py --exec get_balance --account_id 12345
$
$## View the SWAIG configuration in SWML
$swaig-test my_agent.py --dump-swml | grep -A 50 '"SWAIG"'

Best Practices

  1. Keep functions focused: One function, one purpose
  2. Write clear descriptions: Help the AI understand when to use each function
  3. Validate inputs: Check for required arguments
  4. Handle errors gracefully: Return helpful error messages
  5. Use global_data: Share state between function calls
  6. Log for debugging: Track function calls and responses
1def get_balance(self, args, raw_data):
2 account_id = args.get("account_id")
3
4 if not account_id:
5 return FunctionResult(
6 "I need an account ID to look up the balance. "
7 "Could you provide your account number?"
8 )
9
10 try:
11 balance = self.lookup_balance(account_id)
12 return FunctionResult(f"Your current balance is ${balance:.2f}")
13 except AccountNotFoundError:
14 return FunctionResult(
15 "I couldn't find an account with that ID. "
16 "Could you verify the account number?"
17 )

Next Steps

Now that you understand how SWAIG connects AI to your code, let’s trace the complete lifecycle of a request through the system.