> Fetch clean Markdown by appending `.md` to any page URL under https://signalwire.com/docs or requesting it with the HTTP header `Accept: text/markdown`. The root index at https://signalwire.com/docs/llms.txt lists the available documentation indexes.

# SWAIG (SignalWire AI Gateway)

> SWAIG connects the AI conversation to your backend logic, letting the AI call functions you define via webhooks.

[lifecycle]: /docs/server-sdks/guides/lifecycle

[ref-agentbase]: /docs/server-sdks/reference/python/agents/agent-base

[ref-datamap]: /docs/server-sdks/reference/python/agents/data-map

[ref-functionresult]: /docs/server-sdks/reference/python/agents/function-result

### 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.

```mermaid
sequenceDiagram
    participant User
    participant AI as SignalWire AI Engine
    participant Agent as Your agent
    User->>AI: What is my account balance?
    Note over AI: Transcribe speech, understand intent,<br />select get_balance(account_id)
    AI->>Agent: POST /swaig with function and arguments
    Note over Agent: Look up balance and return<br />SwaigFunctionResult
    Agent-->>AI: Balance is $150.00
    AI-->>User: Speak the result
```

### SWAIG in SWML

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

```json
{
  "version": "1.0.0",
  "sections": {
    "main": [
      {
        "ai": {
          "SWAIG": {
            "defaults": {
              "web_hook_url": "https://your-agent.com/swaig"
            },
            "functions": [
              {
                "function": "get_balance",
                "description": "Get the customer's current account balance",
                "parameters": {
                  "type": "object",
                  "properties": {
                    "account_id": {
                      "type": "string",
                      "description": "The customer's account ID"
                    }
                  },
                  "required": ["account_id"]
                }
              }
            ]
          }
        }
      }
    ]
  }
}
```

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

#### Python

```python
from signalwire import AgentBase, FunctionResult

class MyAgent(AgentBase):
    def __init__(self):
        super().__init__(name="my-agent")

        self.define_tool(
            name="get_balance",
            description="Get account balance for a customer",
            parameters={
                "type": "object",
                "properties": {
                    "account_id": {
                        "type": "string",
                        "description": "The account ID to look up"
                    }
                },
                "required": ["account_id"]
            },
            handler=self.get_balance
        )

    def get_balance(self, args, raw_data):
        account_id = args.get("account_id")
        return FunctionResult(f"Account {account_id} has a balance of $150.00")
```

#### TypeScript

```typescript
agent.defineTool({
  name: 'get_balance',
  description: 'Get account balance for a customer',
  parameters: {
    type: 'object',
    properties: {
      account_id: { type: 'string', description: 'The account ID to look up' },
    },
    required: ['account_id'],
  },
  handler: (args) => {
    return new FunctionResult(`Account ${args['account_id']} has a balance of $150.00`);
  },
});
```

#### Method 2: @[AgentBase][ref-agentbase].tool Decorator

A cleaner approach using decorators:

```python
from signalwire import AgentBase, FunctionResult

class MyAgent(AgentBase):
    def __init__(self):
        super().__init__(name="my-agent")

    @AgentBase.tool(
        name="get_balance",
        description="Get account balance for a customer",
        parameters={
            "type": "object",
            "properties": {
                "account_id": {
                    "type": "string",
                    "description": "The account ID to look up"
                }
            },
            "required": ["account_id"]
        }
    )
    def get_balance(self, args, raw_data):
        account_id = args.get("account_id")
        return FunctionResult(f"Account {account_id} has a balance of $150.00")
```

#### Method 3: [DataMap][ref-datamap] (Serverless)

For direct API integration without code:

```python
from signalwire import AgentBase

class MyAgent(AgentBase):
    def __init__(self):
        super().__init__(name="my-agent")

        self.data_map.add_tool(
            name="get_balance",
            description="Get account balance",
            parameters={
                "account_id": {
                    "type": "string",
                    "description": "The account ID"
                }
            },
            data_map={
                "webhooks": [
                    {
                        "url": "https://api.example.com/accounts/${enc:args.account_id}/balance",
                        "method": "GET",
                        "headers": {
                            "Authorization": "Bearer ${env.API_KEY}"
                        },
                        "output": {
                            "response": "Account balance is $${balance}",
                            "action": [{"set_global_data": {"balance": "${balance}"}}]
                        }
                    }
                ]
            }
        )
```

### Function Handler Signature

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

| Language   | Handler Signature                                                                     |
| ---------- | ------------------------------------------------------------------------------------- |
| Python     | `def 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:

```python
def my_function(self, args, raw_data):
    # Call metadata
    # Call information (nested under 'call' key)
    call_data = raw_data.get("call", {})
    call_id = call_data.get("call_id") or raw_data.get("call_id")  # Fallback for compatibility
    call_sid = raw_data.get("call_sid")

    # Caller information (from nested call object)
    from_number = call_data.get("from") or call_data.get("from_number")
    to_number = call_data.get("to") or call_data.get("to_number")

    # Global data (shared state)
    global_data = raw_data.get("global_data", {})
    customer_name = global_data.get("customer_name")

    # Conversation context
    meta_data = raw_data.get("meta_data", {})

    return FunctionResult("Processed")
```

### FunctionResult

Always return a [`FunctionResult`][ref-functionresult] from your handlers. The class name varies by language:

| Language   | FunctionResult Constructor |
| ---------- | -------------------------- |
| Python     | `FunctionResult("text")`   |
| TypeScript | `FunctionResult("text")`   |

#### Python

```python
from signalwire import FunctionResult

def simple_response(self, args, raw_data):
    # Simple text response - AI will speak this
    return FunctionResult("Your order has been placed successfully.")

def response_with_actions(self, args, raw_data):
    result = FunctionResult("Transferring you now.")
    result.connect("+15551234567", final=True, from_addr="+15559876543")
    return result

def response_with_data(self, args, raw_data):
    result = FunctionResult("I've saved your preferences.")
    result.update_global_data({"user_preference": "email", "confirmed": True})
    return result
```

#### TypeScript

```typescript
// Simple response
return new FunctionResult('Your order has been placed successfully.');

// With transfer
return new FunctionResult('Transferring you now.')
  .connect('+15551234567', true);

// With data
return new FunctionResult("I've saved your preferences.")
  .updateGlobalData({ user_preference: 'email', confirmed: true });
```

### Common Actions

| Action             | Purpose                                                                          | Example                                      |
| ------------------ | -------------------------------------------------------------------------------- | -------------------------------------------- |
| `set_global_data`  | Store data for later use                                                         | `{"key": "value"}`                           |
| `transfer`         | End AI, prepare for transfer. **Required** as `True` to perform a hard transfer. | `True`                                       |
| `SWML`             | Execute SWML after AI ends                                                       | `{"version": "1.0.0", ...}`                  |
| `stop`             | End the AI conversation                                                          | `True`                                       |
| `toggle_functions` | Enable/disable functions                                                         | `[{"active": false, "function": "fn_name"}]` |
| `say`              | Speak text immediately                                                           | `"Please hold..."`                           |
| `play_file`        | Play audio file                                                                  | `"https://example.com/hold_music.mp3"`       |

### SWAIG Request Flow

```mermaid
flowchart TD
    request["SignalWire sends POST /swaig"] --> basic["Validate Basic Auth"]
    basic --> token["Validate function-specific token, if configured"]
    token --> lookup["Look up function in ToolRegistry"]
    lookup -->|found| handler["Execute handler"]
    handler --> result["Return SwaigFunctionResult"]
    result --> json["Format JSON response"]
    lookup -->|not found| error["Return error response"]
    json & error --> response["Send response to SignalWire"]
    response --> continue["AI incorporates response into conversation"]
```

### SWAIG Request Format

SignalWire sends a POST request with this structure:

```json
{
  "action": "swaig_action",
  "function": "get_balance",
  "argument": {
    "parsed": [
      {
        "account_id": "12345"
      }
    ],
    "raw": "{\"account_id\": \"12345\"}"
  },
  "call": {
    "call_id": "uuid-here",
    "from": "+15551234567",
    "from_number": "+15551234567",
    "to": "+15559876543",
    "to_number": "+15559876543",
    "direction": "inbound"
  },
  "call_id": "uuid-here",
  "call_sid": "call-sid-here",
  "global_data": {
    "customer_name": "John Doe"
  },
  "meta_data": {},
  "ai_session_id": "session-uuid"
}
```

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:

```json
{
  "response": "The account balance is $150.00",
  "action": [
    {
      "set_global_data": {
        "last_balance_check": "2024-01-15T10:30:00Z"
      }
    }
  ]
}
```

Or for a transfer:

```json
{
  "response": "Transferring you to a specialist now.",
  "action": [
    {
      "transfer": true,
      "SWML": {
        "version": "1.0.0",
        "sections": {
          "main": [
            {"connect": {"to": "+15551234567", "from": "+15559876543"}}
          ]
        }
      }
    }
  ]
}
```

### Function Parameters (JSON Schema)

SWAIG functions use JSON Schema for parameter definitions:

```python
self.define_tool(
    name="search_orders",
    description="Search customer orders",
    parameters={
        "type": "object",
        "properties": {
            "customer_id": {
                "type": "string",
                "description": "Customer ID to search for"
            },
            "status": {
                "type": "string",
                "enum": ["pending", "shipped", "delivered", "cancelled"],
                "description": "Filter by order status"
            },
            "limit": {
                "type": "integer",
                "description": "Maximum number of results",
                "default": 10
            },
            "include_details": {
                "type": "boolean",
                "description": "Include full order details",
                "default": False
            }
        },
        "required": ["customer_id"]
    },
    handler=self.search_orders
)
```

### 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

```python
## Function-specific token security
self.define_tool(
    name="sensitive_action",
    description="Perform a sensitive action",
    parameters={...},
    handler=self.sensitive_action,
    secure=True  # Enables per-function token validation
)
```

### Testing SWAIG Functions

Use `swaig-test` to test functions locally:

```bash
## 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

```python
def get_balance(self, args, raw_data):
    account_id = args.get("account_id")

    if not account_id:
        return FunctionResult(
            "I need an account ID to look up the balance. "
            "Could you provide your account number?"
        )

    try:
        balance = self.lookup_balance(account_id)
        return FunctionResult(f"Your current balance is ${balance:.2f}")
    except AccountNotFoundError:
        return FunctionResult(
            "I couldn't find an account with that ID. "
            "Could you verify the account number?"
        )
```

### Next Steps

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