Static or Dynamic agents?
Static vs Dynamic Agents
Choose between static agents (fixed configuration) and dynamic agents (runtime customization) based on whether you need per-call personalization.
Understanding the Difference
| Aspect | Static Agent | Dynamic Agent |
|---|---|---|
| Configuration | Set once at startup | Per-request based on call data |
| Behavior | Same for all callers | Different for different callers |
Use Static When:
- Same prompt for everyone
- Generic assistant
- Simple IVR
- FAQ bot
Use Dynamic When:
- Personalized greetings
- Caller-specific data
- Account-based routing
- Multi-tenant applications
Static Agents
Static agents have fixed configuration determined at instantiation time.
Example: Static Customer Service Agent
1 from signalwire_agents import AgentBase, SwaigFunctionResult 2 3 4 class StaticSupportAgent(AgentBase): 5 """Same behavior for all callers.""" 6 7 def __init__(self): 8 super().__init__(name="static-support") 9 10 self.add_language("English", "en-US", "rime.spore") 11 12 self.prompt_add_section( 13 "Role", 14 "You are a customer service agent for Acme Corp. " 15 "Help callers with general inquiries about our products." 16 ) 17 18 self.prompt_add_section( 19 "Guidelines", 20 bullets=[ 21 "Be helpful and professional", 22 "Answer questions about products", 23 "Transfer complex issues to support" 24 ] 25 ) 26 27 self.define_tool( 28 name="get_store_hours", 29 description="Get store hours", 30 parameters={}, 31 handler=self.get_store_hours 32 ) 33 34 def get_store_hours(self, args, raw_data): 35 return SwaigFunctionResult( 36 "We're open Monday through Friday, 9 AM to 5 PM." 37 ) 38 39 40 if __name__ == "__main__": 41 agent = StaticSupportAgent() 42 agent.run()
Dynamic Agents
Dynamic agents customize their behavior based on the incoming request using the on_swml_request method.
The on_swml_request Method
1 def on_swml_request(self, request_data=None, callback_path=None, request=None): 2 """ 3 Called before SWML is generated for each request. 4 5 Args: 6 request_data: Optional dict containing the parsed POST body from SignalWire. 7 Call information is nested under the 'call' key: 8 - call["call_id"]: Unique call identifier 9 - call["from"]: Caller's phone number 10 - call["from_number"]: Alternative caller number field 11 - call["to"]: Number that was called 12 - call["direction"]: "inbound" or "outbound" 13 callback_path: Optional callback path for routing 14 request: Optional FastAPI Request object for accessing query params, headers, etc. 15 16 Returns: 17 Optional dict with modifications to apply (usually None for simple cases) 18 """ 19 pass
Example: Dynamic Personalized Agent
1 from signalwire_agents import AgentBase, SwaigFunctionResult 2 3 4 class DynamicPersonalizedAgent(AgentBase): 5 """Customizes greeting based on caller.""" 6 7 # Simulated customer database 8 CUSTOMERS = { 9 "+15551234567": {"name": "John Smith", "tier": "gold", "account": "A001"}, 10 "+15559876543": {"name": "Jane Doe", "tier": "platinum", "account": "A002"}, 11 } 12 13 def __init__(self): 14 super().__init__(name="dynamic-agent") 15 16 self.add_language("English", "en-US", "rime.spore") 17 18 # Base configuration 19 self.set_params({ 20 "end_of_speech_timeout": 500, 21 "attention_timeout": 15000 22 }) 23 24 # Functions available to all callers 25 self.define_tool( 26 name="get_account_status", 27 description="Get the caller's account status", 28 parameters={}, 29 handler=self.get_account_status 30 ) 31 32 # Store caller info for function access 33 self._current_caller = None 34 35 def on_swml_request(self, request_data=None, callback_path=None, request=None): 36 """Customize behavior based on caller.""" 37 # Extract call data (nested under 'call' key in request_data) 38 call_data = (request_data or {}).get("call", {}) 39 caller_num = call_data.get("from") or call_data.get("from_number", "") 40 41 # Look up caller in database 42 customer = self.CUSTOMERS.get(caller_num) 43 44 if customer: 45 # Known customer - personalized experience 46 self._current_caller = customer 47 48 self.prompt_add_section( 49 "Role", 50 f"You are a premium support agent for Acme Corp. " 51 f"You are speaking with {customer['name']}, a {customer['tier']} member." 52 ) 53 54 self.prompt_add_section( 55 "Context", 56 f"Customer account: {customer['account']}\n" 57 f"Membership tier: {customer['tier'].upper()}" 58 ) 59 60 if customer["tier"] == "platinum": 61 self.prompt_add_section( 62 "Special Treatment", 63 "This is a platinum customer. Prioritize their requests and " 64 "offer expedited service on all issues." 65 ) 66 else: 67 # Unknown caller - generic experience 68 self._current_caller = None 69 70 self.prompt_add_section( 71 "Role", 72 "You are a customer service agent for Acme Corp. " 73 "Help the caller with their inquiry and offer to create an account." 74 ) 75 76 def get_account_status(self, args, raw_data): 77 if self._current_caller: 78 return SwaigFunctionResult( 79 f"Account {self._current_caller['account']} is active. " 80 f"Tier: {self._current_caller['tier'].upper()}" 81 ) 82 return SwaigFunctionResult( 83 "No account found. Would you like to create one?" 84 ) 85 86 87 if __name__ == "__main__": 88 agent = DynamicPersonalizedAgent() 89 agent.run()
Request Data Fields
The request_data dictionary is the parsed POST body from SignalWire. Call information is nested under the call key:
| Field | Description | Example |
|---|---|---|
call["call_id"] | Unique call identifier | "a1b2c3d4-..." |
call["from"] | Caller’s phone number | "+15551234567" |
call["from_number"] | Alternative caller number field | "+15551234567" |
call["to"] | Number that was called | "+15559876543" |
call["direction"] | Call direction | "inbound" |
Important: Always use defensive access when working with request_data:
1 def on_swml_request(self, request_data=None, callback_path=None, request=None): 2 call_data = (request_data or {}).get("call", {}) 3 caller_num = call_data.get("from") or call_data.get("from_number", "") 4 call_id = call_data.get("call_id", "")
Dynamic Function Registration
You can also register functions dynamically based on the caller:
1 class DynamicFunctionsAgent(AgentBase): 2 """Different functions for different callers.""" 3 4 ADMIN_NUMBERS = ["+15551111111", "+15552222222"] 5 6 def __init__(self): 7 super().__init__(name="dynamic-functions") 8 self.add_language("English", "en-US", "rime.spore") 9 10 # Base functions for everyone 11 self.define_tool( 12 name="get_info", 13 description="Get general information", 14 parameters={}, 15 handler=self.get_info 16 ) 17 18 def on_swml_request(self, request_data=None, callback_path=None, request=None): 19 call_data = (request_data or {}).get("call", {}) 20 caller_num = call_data.get("from") or call_data.get("from_number", "") 21 22 self.prompt_add_section("Role", "You are a helpful assistant.") 23 24 # Add admin functions only for admin callers 25 if caller_num in self.ADMIN_NUMBERS: 26 self.prompt_add_section( 27 "Admin Access", 28 "This caller has administrator privileges. " 29 "They can access system administration functions." 30 ) 31 32 self.define_tool( 33 name="admin_reset", 34 description="Reset system configuration (admin only)", 35 parameters={}, 36 handler=self.admin_reset 37 ) 38 39 self.define_tool( 40 name="admin_report", 41 description="Generate system report (admin only)", 42 parameters={}, 43 handler=self.admin_report 44 ) 45 46 def get_info(self, args, raw_data): 47 return SwaigFunctionResult("General information...") 48 49 def admin_reset(self, args, raw_data): 50 return SwaigFunctionResult("System reset initiated.") 51 52 def admin_report(self, args, raw_data): 53 return SwaigFunctionResult("Report generated: All systems operational.")
Multi-Tenant Applications
Dynamic agents are ideal for multi-tenant scenarios:
1 class MultiTenantAgent(AgentBase): 2 """Different branding per tenant.""" 3 4 TENANTS = { 5 "+15551111111": { 6 "company": "Acme Corp", 7 "voice": "rime.spore", 8 "greeting": "Welcome to Acme Corp support!" 9 }, 10 "+15552222222": { 11 "company": "Beta Industries", 12 "voice": "rime.marsh", 13 "greeting": "Thank you for calling Beta Industries!" 14 } 15 } 16 17 def __init__(self): 18 super().__init__(name="multi-tenant") 19 20 def on_swml_request(self, request_data=None, callback_path=None, request=None): 21 call_data = (request_data or {}).get("call", {}) 22 called_num = call_data.get("to", "") 23 24 tenant = self.TENANTS.get(called_num, { 25 "company": "Default Company", 26 "voice": "rime.spore", 27 "greeting": "Hello!" 28 }) 29 30 # Configure for this tenant 31 self.add_language("English", "en-US", tenant["voice"]) 32 33 self.prompt_add_section( 34 "Role", 35 f"You are a customer service agent for {tenant['company']}. " 36 f"Start by saying: {tenant['greeting']}" 37 )
Comparison Summary
| Aspect | Static | Dynamic |
|---|---|---|
| Configuration | Once at startup | Per-request |
| Performance | Slightly faster | Minimal overhead |
| Use Case | Generic assistants | Personalized experiences |
| Complexity | Simpler | More complex |
| Testing | Easier | Requires more scenarios |
| Method | __init__ only | on_swml_request |
Best Practices
- Start static, go dynamic when needed - Don’t over-engineer
- Cache expensive lookups - Database calls in
on_swml_requestadd latency - Clear prompts between calls - Use
self.pom.clear()if reusing sections - Log caller info - Helps with debugging dynamic behavior
- Test multiple scenarios - Each caller path needs testing
1 def on_swml_request(self, request_data=None, callback_path=None, request=None): 2 # Clear previous dynamic configuration 3 self.pom.clear() 4 5 # Log for debugging 6 call_data = (request_data or {}).get("call", {}) 7 self.log.info("request_received", 8 caller=call_data.get("from"), 9 called=call_data.get("to") 10 ) 11 12 # Configure based on request 13 self._configure_for_caller(request_data)