T
Telephony SOPKnowledge Base
Search
← All topics

SignalWire Python Agents SDK

runnable

AgentBase setup, fluent prompt and parameter configuration, the @tool decorator for SWAIG, FunctionResult chaining, and the AgentServer pattern for serving multiple agents on one port.

signalwirepythonagents-sdkvoice-aiswaigswml
Agent trigger phrases: how do I build a SignalWire AI agent in python · AgentBase setup · register SWAIG tool decorator · FunctionResult connect hangup · AgentServer multi-agent · post_prompt url callback · Python voice AI SignalWire

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 FunctionResult from a tool — handler will error.
  • Placing .connect(..., final=True) mid-chain — final=True is terminal. Always last.
  • Forgetting post_process=True when you want the AI to speak before acting.
  • Hardcoding port when deploying to serverless — use agent.run() so the SDK auto-detects.
  • Skipping add_language — without an explicit language and voice, the agent uses a low-quality default.

See also