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