| Duration | 2 hours |
| Day | 5 of 5 |
Learning Objectives
By the end of this module, students will be able to:
- Deploy multiple agents on a single server
- Implement path-based routing
- Share state between agents
- Configure health checks
Topics
1. Why Multi-Agent? (15 min)
The Problem
One server, multiple use cases:
/sales → Sales Agent
/support → Support Agent
/billing → Billing Agent
/spanish → Spanish Agent
Running separate servers is wasteful and complex.
The Solution: AgentServer
from signalwire_agents import AgentServer
server = AgentServer(host="0.0.0.0", port=3000)
server.register(SalesAgent())
server.register(SupportAgent())
server.register(BillingAgent())
server.run()
One server, multiple agents!
2. AgentServer Basics (25 min)
Creating a Server
from signalwire_agents import AgentServer, AgentBase
# Create agents
class SalesAgent(AgentBase):
def __init__(self):
super().__init__(name="sales", route="/sales")
self.prompt_add_section("Role", "You are a sales agent.")
self.add_language("English", "en-US", "rime.spore")
class SupportAgent(AgentBase):
def __init__(self):
super().__init__(name="support", route="/support")
self.prompt_add_section("Role", "You are a support agent.")
self.add_language("English", "en-US", "rime.spore")
# Create server and register agents
server = AgentServer(host="0.0.0.0", port=3000)
server.register(SalesAgent())
server.register(SupportAgent())
if __name__ == "__main__":
server.run()
Endpoints Created
http://localhost:3000/sales → SalesAgent SWML
http://localhost:3000/sales/swaig → SalesAgent functions
http://localhost:3000/support → SupportAgent SWML
http://localhost:3000/support/swaig → SupportAgent functions
Server Options
server = AgentServer(
host="0.0.0.0", # Bind address
port=3000, # Port number
log_level="info", # Logging verbosity
)
3. Routing Patterns (30 min)
Path-Based Routing
Different paths for different agents:
server.register(SalesAgent()) # /sales
server.register(SupportAgent()) # /support
server.register(BillingAgent()) # /billing
SignalWire Configuration:
- Sales number →
https://host.com/sales - Support number →
https://host.com/support - Billing number →
https://host.com/billing
Language-Based Routing
class EnglishAgent(AgentBase):
def __init__(self):
super().__init__(name="english", route="/en")
self.add_language("English", "en-US", "rime.spore")
class SpanishAgent(AgentBase):
def __init__(self):
super().__init__(name="spanish", route="/es")
self.add_language("Spanish", "es-US", "gcloud.es-US-Neural2-A")
Department Router Pattern
Create a router that forwards to specialized agents:
class RouterAgent(AgentBase):
def __init__(self):
super().__init__(name="router", route="/main")
self.prompt_add_section(
"Role",
"You route callers to the right department. "
"Ask what they need help with."
)
@AgentBase.tool(
description="Route to appropriate department",
parameters={
"type": "object",
"properties": {
"department": {
"type": "string",
"enum": ["sales", "support", "billing"]
}
},
"required": ["department"]
}
)
def route_call(self, args: dict, raw_data: dict = None) -> SwaigFunctionResult:
department = args.get("department", "")
routes = {
"sales": "+15551111111",
"support": "+15552222222",
"billing": "+15553333333",
}
return (
SwaigFunctionResult(f"Connecting you to {department}.")
.connect(routes[department], final=True)
)
4. Shared State (25 min)
The Challenge
Agents on same server may need to share:
- Customer data
- Session information
- Configuration
Shared Dictionary
from signalwire_agents import AgentServer, AgentBase
# Shared state
shared_data = {
"config": {
"business_hours": "9 AM to 5 PM",
"support_email": "support@example.com"
},
"sessions": {}
}
class SalesAgent(AgentBase):
def __init__(self, shared):
super().__init__(name="sales", route="/sales")
self.shared = shared
@AgentBase.tool(description="Get business hours")
def get_hours(self, args: dict, raw_data: dict = None) -> SwaigFunctionResult:
hours = self.shared["config"]["business_hours"]
return SwaigFunctionResult(f"We're open {hours}.")
class SupportAgent(AgentBase):
def __init__(self, shared):
super().__init__(name="support", route="/support")
self.shared = shared
@AgentBase.tool(description="Get support email")
def get_email(self, args: dict, raw_data: dict = None) -> SwaigFunctionResult:
email = self.shared["config"]["support_email"]
return SwaigFunctionResult(f"Email us at {email}.")
# Create with shared state
server = AgentServer()
server.register(SalesAgent(shared_data))
server.register(SupportAgent(shared_data))
Session Sharing
class Agent1(AgentBase):
def store_customer(self, call_id: str, name: str):
self.shared["sessions"][call_id] = {"name": name}
class Agent2(AgentBase):
def get_customer(self, call_id: str):
return self.shared["sessions"].get(call_id, {})
5. Health Checks (15 min)
Built-in Health Endpoint
AgentServer includes /health:
curl http://localhost:3000/health
Response:
{
"status": "healthy",
"agents": ["sales", "support", "billing"],
"timestamp": "2024-01-15T10:30:00Z"
}
Custom Health Checks
@server.app.get("/ready")
async def readiness():
# Check dependencies
db_ok = check_database()
api_ok = check_external_api()
if db_ok and api_ok:
return {"status": "ready"}
else:
return JSONResponse(
status_code=503,
content={"status": "not ready"}
)
Load Balancer Configuration
For production with multiple instances:
┌─────────────────┐
│ Load Balancer │
│ /health check │
└────────┬────────┘
│
┌────┴────┐
│ │
┌───▼───┐ ┌───▼───┐
│ Inst1 │ │ Inst2 │
│ :3000 │ │ :3000 │
└───────┘ └───────┘
6. Complete Example (20 min)
Click to reveal complete solution
Multi-Department Support Server
#!/usr/bin/env python3
"""Multi-agent support server."""
import os
from signalwire_agents import AgentServer, AgentBase, SwaigFunctionResult
# Shared configuration
CONFIG = {
"company": "TechCorp",
"hours": "9 AM to 6 PM EST",
"support_email": "help@techcorp.com",
}
class MainAgent(AgentBase):
"""Front door agent that routes calls."""
def __init__(self):
super().__init__(name="main", route="/")
self.prompt_add_section(
"Role",
f"You are the receptionist for {CONFIG['company']}. "
"Help callers reach the right department."
)
self.prompt_add_section(
"Departments",
bullets=[
"Sales - for new purchases and pricing",
"Support - for technical help",
"Billing - for payments and invoices"
]
)
self.add_language("English", "en-US", "rime.spore")
@AgentBase.tool(description="Transfer to a department")
def transfer_department(self, args: dict, raw_data: dict = None) -> SwaigFunctionResult:
department = args.get("department", "")
routes = {
"sales": "/sales",
"support": "/support",
"billing": "/billing"
}
if department.lower() not in routes:
return SwaigFunctionResult(
"I can transfer you to sales, support, or billing."
)
return (
SwaigFunctionResult(f"Transferring to {department}.")
.swml_change_context(routes[department.lower()])
)
class SalesAgent(AgentBase):
"""Sales department agent."""
def __init__(self):
super().__init__(name="sales", route="/sales")
self.prompt_add_section(
"Role",
f"You are a sales representative for {CONFIG['company']}. "
"Help customers with product information and pricing."
)
self.add_language("English", "en-US", "rime.marsh")
@AgentBase.tool(description="Get pricing information")
def get_pricing(self, args: dict, raw_data: dict = None) -> SwaigFunctionResult:
product = args.get("product", "")
prices = {
"basic": "$99/month",
"pro": "$199/month",
"enterprise": "Custom pricing"
}
price = prices.get(product.lower(), "Please specify: basic, pro, or enterprise")
return SwaigFunctionResult(f"{product} is {price}.")
class SupportAgent(AgentBase):
"""Technical support agent."""
def __init__(self):
super().__init__(name="support", route="/support")
self.prompt_add_section(
"Role",
f"You are a technical support agent for {CONFIG['company']}. "
"Help customers resolve technical issues."
)
self.add_language("English", "en-US", "rime.spore")
@AgentBase.tool(description="Create support ticket")
def create_ticket(self, args: dict, raw_data: dict = None) -> SwaigFunctionResult:
issue = args.get("issue", "")
ticket_id = "T-" + str(hash(issue))[:6]
return SwaigFunctionResult(
f"I've created ticket {ticket_id} for your issue. "
f"You'll receive updates at {CONFIG['support_email']}."
)
class BillingAgent(AgentBase):
"""Billing department agent."""
def __init__(self):
super().__init__(name="billing", route="/billing")
self.prompt_add_section(
"Role",
f"You are a billing specialist for {CONFIG['company']}. "
"Help with payments, invoices, and account questions."
)
self.add_language("English", "en-US", "rime.cove")
@AgentBase.tool(description="Get account balance")
def get_balance(self, args: dict, raw_data: dict = None) -> SwaigFunctionResult:
account_id = args.get("account_id", "")
# Mock balance lookup
return SwaigFunctionResult(
f"Account {account_id} has a balance of $0.00. You're all paid up!"
)
def main():
"""Start the multi-agent server."""
host = os.getenv("HOST", "0.0.0.0")
port = int(os.getenv("PORT", "3000"))
server = AgentServer(host=host, port=port)
# Register all agents
server.register(MainAgent())
server.register(SalesAgent())
server.register(SupportAgent())
server.register(BillingAgent())
print(f"Multi-agent server starting on {host}:{port}")
print("Endpoints:")
print(f" Main: http://{host}:{port}/")
print(f" Sales: http://{host}:{port}/sales")
print(f" Support: http://{host}:{port}/support")
print(f" Billing: http://{host}:{port}/billing")
print(f" Health: http://{host}:{port}/health")
server.run()
if __name__ == "__main__":
main()
Key Takeaways
- AgentServer hosts multiple agents - One server, many endpoints
- Route-based organization - Different paths for different agents
- Shared state is possible - Pass data between agents
- Health checks are essential - Monitor server status
- Scales horizontally - Run multiple instances behind load balancer
Preparation for Lab 2.9
- Have 2-3 agent concepts ready
- Think about what they might share
- Consider routing logic
Lab Preview
In Lab 2.9, you will:
- Create a multi-agent server
- Implement department routing
- Add shared configuration
- Test health endpoints