SignalWire Python Agents SDK
Python SDK that auto-generates SWML, registers SWAIG endpoints, and runs a FastAPI/uvicorn server. The primary class is AgentBase. Nine mixins compose the feature set: prompt, tool, skill, AI config, web, auth, serverless, state, MCP server. Every setter returns self for fluent chaining.
Install
pip install signalwire-agents
pip install signalwire-pom # only when using PomBuilder directly
Key imports
from signalwire import AgentBase, AgentServer, SWMLService
from signalwire import SWAIGFunction, FunctionResult, DataMap
from signalwire import (
create_simple_context, create_simple_api_tool,
create_expression_tool, register_skill, add_skill_directory,
)
from signalwire.core.pom_builder import PomBuilder
from signalwire.core.skill_base import SkillBase
from signalwire.mcp_gateway import MCPGateway
from signalwire.prefabs import (
ConciergeAgent, FAQBotAgent, InfoGathererAgent,
ReceptionistAgent, SurveyAgent,
)
AgentBase constructor
| Parameter | Type | Default | Notes |
|---|---|---|---|
| name | str | required | Display name, used in logging and SIP username mapping |
| route | str | "/" | HTTP route path |
| host | str | "0.0.0.0" | Bind address |
| port | int | PORT env or 3000 | Listen port |
| agent_id | str | auto UUID | Unique instance ID |
| use_pom | bool | True | Enable Prompt Object Model |
| auto_answer | bool | True | Add answer verb before ai in SWML |
| record_call | bool | False | Enable call recording |
| record_format | str | "mp4" | "mp4" or "wav" |
| record_stereo | bool | True | Separate channel per party |
| basic_auth | tuple[str,str] | env or auto | (username, password) |
| token_expiry_secs | int | 3600 | SWAIG auth token TTL |
| native_functions | list[str] | None | Built-in platform functions, e.g. ["check_time"] |
| config_file | str | None | Path to JSON config |
| schema_validation | bool | True | Validate generated SWML |
| suppress_logs | bool | False | Silence SDK output |
Entry points
agent.run() # auto-detects serverless vs uvicorn
agent.serve() # always starts FastAPI/uvicorn
Subclass pattern
from signalwire import AgentBase, FunctionResult
class SupportAgent(AgentBase):
def __init__(self):
super().__init__(name="support-agent", route="/support")
self.add_language("English", "en-US", "rime.spore")
self.set_prompt_text("You are a friendly customer support agent.")
self.add_hints(["SignalWire", "SWML", "SWAIG"])
self.set_params({"temperature": 0.7, "end_of_speech_timeout": 1000})
if __name__ == "__main__":
SupportAgent().run()
Instance pattern (no subclass)
agent = AgentBase(name="assistant", route="/assistant")
agent.set_prompt_text("You are a helpful assistant.")
agent.add_language("English", "en-US", "rime.spore")
agent.serve()
Defining the prompt
Plain text
agent.set_prompt_text("You are a helpful assistant.")
Structured POM sections
agent.prompt_add_section("Role", "You are a customer service agent.")
agent.prompt_add_section("Guidelines", bullets=[
"Be concise",
"Never promise timelines you can't keep",
])
agent.prompt_add_subsection("Guidelines", "Escalation", "Transfer if unresolved after 2 attempts.")
agent.prompt_add_to_section("Guidelines", bullet="Always verify caller identity first.")
AI parameters
agent.set_params({
"temperature": 0.7,
"end_of_speech_timeout": 1000,
"attention_timeout": 10000,
"max_speech_timeout": 30000,
})
agent.set_param("temperature", 0.5)
Post-prompt for call summary
agent.set_post_prompt("Summarize the call outcome in one sentence.")
agent.set_post_prompt_url("https://yourserver.com/summaries")
Language and voice
agent.add_language("English", "en-US", "rime.spore")
agent.add_language("Spanish", "es-MX", "rime.luna")
Hints, patterns, pronunciation
agent.add_hint("SignalWire")
agent.add_hints(["SWML", "SWAIG", "webhook"])
agent.add_pattern_hint(r"\b\d{3}-\d{4}\b", "phone number")
agent.add_pronunciation("GHL", "G H L")
@tool decorator — preferred SWAIG registration
The @tool decorator is the canonical way to register SWAIG functions. It works on standalone functions and on methods inside subclasses.
Instance decorator with type inference
agent = AgentBase(name="assistant", route="/assistant")
agent.set_prompt_text("You are a helpful assistant.")
@agent.tool(description="Look up a customer's order status")
def check_order(args, raw_data=None):
order_id = args.get("order_id")
return FunctionResult(f"Order {order_id} shipped March 28.")
agent.serve()
Instance decorator with explicit schema
@agent.tool(
name="search_products",
description="Search the product catalog",
parameters={
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"category": {"type": "string", "description": "Product category"},
},
},
required=["query"],
fillers={"en-US": ["Searching...", "Let me find that..."]},
)
def search_products(args, raw_data=None):
query = args.get("query")
return FunctionResult(f"Found 3 results for '{query}'.")
Class decorator (subclass)
class SupportAgent(AgentBase):
@AgentBase.tool(description="Transfer to a human agent")
def transfer_to_human(self, args, raw_data=None):
return FunctionResult("Transferring now.").connect("+15551234567")
Typed parameters auto-inferred
@agent.tool(description="Calculate shipping cost")
def calculate_shipping(weight_kg: float, destination: str, express: bool = False):
cost = weight_kg * (5.00 if express else 2.50)
return FunctionResult(f"Shipping to {destination}: ${cost:.2f}")
Decorator parameters
| Parameter | Type | Default | Notes |
|---|---|---|---|
| name | str | function __name__ | Exposed to the AI |
| description | str | docstring or "Function {name}" | AI reads to decide when to call |
| parameters | dict | inferred from type hints | Full JSON Schema object |
| secure | bool | True | Token-validate calls |
| fillers | dict[str, list[str]] | None | e.g. {"en-US": ["One moment..."]} |
| webhook_url | str | None | Forward to external URL instead of local handler |
| required | list[str] | inferred | Required parameter names |
FunctionResult — required return type
Every tool handler must return FunctionResult. All methods return self so calls can be chained.
return FunctionResult("Done.")
return FunctionResult("I'll transfer you. Anything else?", post_process=True)
post_process=True makes the AI speak the response, take one more turn, then execute actions.
Call control
.connect("+15551234567") # transfer
.connect("+15551234567", final=True) # transfer, end conversation
.hangup() # end immediately
.hold(timeout=30) # hold with timeout
.swml_transfer("https://your.api/flow", return_message="Transfer failed")
Data
.update_global_data({"key": "value"}) # merge into session global_data
.remove_global_data(["key"]) # remove keys
.set_metadata({"local_key": "val"}) # function-scoped only
Context navigation
.swml_change_step("step_name")
.swml_change_context("context_name")
.switch_context("context_name", system_prompt="New prompt", consolidate=True)
Media
.play_background_file("https://example.com/hold.mp3")
.stop_background_file()
.record_call(format="mp4", stereo=True)
Messaging, speech, function control
.send_sms(to_number="+15551234567", from_number="+15559876543", body="Your order shipped.")
.say("Please hold while I look that up.")
.wait_for_user(True)
.stop()
.toggle_functions({"lookup_order": False, "escalate": True})
Chaining example
@agent.tool(name="transfer_to_billing", description="Transfer caller to billing")
def transfer_to_billing(args, raw_data):
return (
FunctionResult("I'll transfer you to billing. Anything else first?", post_process=True)
.update_global_data({"transferred": True})
.send_sms(to_number="+15551234567", from_number="+15559876543",
body="You are being transferred to billing.")
.connect("+15551234567", final=True) # terminal — always last
)
AgentServer — multiple agents, one port
Mount any number of agents on different routes and serve them from one uvicorn process.
from signalwire import AgentServer
server = AgentServer(host="0.0.0.0", port=3000)
server.mount(SalesAgent(), route="/sales")
server.mount(SupportAgent(), route="/support")
server.run()
Endpoints exposed: /sales, /support, /health, /ready.
Anti-patterns
- Returning a string instead of
FunctionResultfrom a tool — handler will error. - Placing
.connect(..., final=True)mid-chain —final=Trueis terminal. Always last. - Forgetting
post_process=Truewhen you want the AI to speak before acting. - Hardcoding
portwhen deploying to serverless — useagent.run()so the SDK auto-detects. - Skipping
add_language— without an explicit language and voice, the agent uses a low-quality default.