signalwire-agents 0.1.7__tar.gz → 0.1.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {signalwire_agents-0.1.7/signalwire_agents.egg-info → signalwire_agents-0.1.8}/PKG-INFO +70 -2
  2. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/README.md +69 -1
  3. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/pyproject.toml +1 -1
  4. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/__init__.py +1 -1
  5. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/agent_base.py +265 -41
  6. signalwire_agents-0.1.8/signalwire_agents/core/function_result.py +1153 -0
  7. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/swml_builder.py +5 -1
  8. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/prefabs/info_gatherer.py +149 -33
  9. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/prefabs/receptionist.py +14 -22
  10. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8/signalwire_agents.egg-info}/PKG-INFO +70 -2
  11. signalwire_agents-0.1.7/signalwire_agents/core/function_result.py +0 -123
  12. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/LICENSE +0 -0
  13. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/schema.json +0 -0
  14. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/setup.cfg +0 -0
  15. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/setup.py +0 -0
  16. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/agent_server.py +0 -0
  17. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/__init__.py +0 -0
  18. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/pom_builder.py +0 -0
  19. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/security/__init__.py +0 -0
  20. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/security/session_manager.py +0 -0
  21. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/state/__init__.py +0 -0
  22. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/state/file_state_manager.py +0 -0
  23. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/state/state_manager.py +0 -0
  24. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/swaig_function.py +0 -0
  25. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/swml_handler.py +0 -0
  26. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/swml_renderer.py +0 -0
  27. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/core/swml_service.py +0 -0
  28. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/prefabs/__init__.py +0 -0
  29. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/prefabs/concierge.py +0 -0
  30. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/prefabs/faq_bot.py +0 -0
  31. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/prefabs/survey.py +0 -0
  32. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/schema.json +0 -0
  33. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/utils/__init__.py +0 -0
  34. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/utils/pom_utils.py +0 -0
  35. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/utils/schema_utils.py +0 -0
  36. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/utils/token_generators.py +0 -0
  37. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents/utils/validators.py +0 -0
  38. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents.egg-info/SOURCES.txt +0 -0
  39. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents.egg-info/dependency_links.txt +0 -0
  40. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents.egg-info/requires.txt +0 -0
  41. {signalwire_agents-0.1.7 → signalwire_agents-0.1.8}/signalwire_agents.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalwire_agents
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: SignalWire AI Agents SDK
5
5
  Author-email: SignalWire Team <info@signalwire.com>
6
6
  Project-URL: Homepage, https://github.com/signalwire/signalwire-ai-agents
@@ -35,6 +35,7 @@ A Python SDK for creating, hosting, and securing SignalWire AI agents as microse
35
35
  - **Self-Contained Agents**: Each agent is both a web app and an AI persona
36
36
  - **Prompt Object Model**: Structured prompt composition using POM
37
37
  - **SWAIG Integration**: Easily define and handle AI tools/functions
38
+ - **Dynamic Configuration**: Configure agents per-request for multi-tenant apps and personalization
38
39
  - **Custom Routing**: Dynamic request handling for different paths and content
39
40
  - **SIP Integration**: Route SIP calls to agents based on SIP usernames
40
41
  - **Security Built-In**: Session management, function-specific security tokens, and basic auth
@@ -165,6 +166,73 @@ Available prefabs include:
165
166
  - `FAQBotAgent`: Answers questions based on a knowledge base
166
167
  - `ConciergeAgent`: Routes users to specialized agents
167
168
  - `SurveyAgent`: Conducts structured surveys with questions and rating scales
169
+ - `ReceptionistAgent`: Greets callers and transfers them to appropriate departments
170
+
171
+ ## Dynamic Agent Configuration
172
+
173
+ Configure agents dynamically based on request parameters for multi-tenant applications, A/B testing, and personalization.
174
+
175
+ ### Static vs Dynamic Configuration
176
+
177
+ - **Static**: Agent configured once at startup (traditional approach)
178
+ - **Dynamic**: Agent configured fresh for each request based on parameters
179
+
180
+ ### Basic Example
181
+
182
+ ```python
183
+ from signalwire_agents import AgentBase
184
+
185
+ class DynamicAgent(AgentBase):
186
+ def __init__(self):
187
+ super().__init__(name="dynamic-agent", route="/dynamic")
188
+
189
+ # Set up dynamic configuration callback
190
+ self.set_dynamic_config_callback(self.configure_per_request)
191
+
192
+ def configure_per_request(self, query_params, body_params, headers, agent):
193
+ """Configure agent based on request parameters"""
194
+
195
+ # Extract parameters from request
196
+ tier = query_params.get('tier', 'standard')
197
+ language = query_params.get('language', 'en')
198
+ customer_id = query_params.get('customer_id')
199
+
200
+ # Configure voice and language
201
+ if language == 'es':
202
+ agent.add_language("Spanish", "es-ES", "rime.spore:mistv2")
203
+ else:
204
+ agent.add_language("English", "en-US", "rime.spore:mistv2")
205
+
206
+ # Configure based on service tier
207
+ if tier == 'premium':
208
+ agent.set_params({"end_of_speech_timeout": 300}) # Faster response
209
+ agent.prompt_add_section("Service Level", "You provide premium support.")
210
+ else:
211
+ agent.set_params({"end_of_speech_timeout": 500}) # Standard response
212
+ agent.prompt_add_section("Service Level", "You provide standard support.")
213
+
214
+ # Personalize with customer data
215
+ global_data = {"tier": tier, "language": language}
216
+ if customer_id:
217
+ global_data["customer_id"] = customer_id
218
+ agent.set_global_data(global_data)
219
+
220
+ # Usage examples:
221
+ # curl "http://localhost:3000/dynamic?tier=premium&language=es&customer_id=123"
222
+ # curl "http://localhost:3000/dynamic?tier=standard&language=en"
223
+ ```
224
+
225
+ ### Use Cases
226
+
227
+ - **Multi-tenant SaaS**: Different configurations per customer/organization
228
+ - **A/B Testing**: Test different agent behaviors with different user groups
229
+ - **Personalization**: Customize voice, prompts, and behavior per user
230
+ - **Localization**: Language and cultural adaptation based on user location
231
+ - **Dynamic Pricing**: Adjust features and capabilities based on subscription tiers
232
+
233
+ The `EphemeralAgentConfig` object provides all the same familiar methods as `AgentBase` (like `add_language()`, `prompt_add_section()`, `set_global_data()`) but applies them per-request instead of at startup.
234
+
235
+ For detailed documentation and advanced examples, see the [Agent Guide](docs/agent_guide.md#dynamic-agent-configuration).
168
236
 
169
237
  ## Configuration
170
238
 
@@ -189,7 +257,7 @@ To enable HTTPS directly (without a reverse proxy), set `SWML_SSL_ENABLED` to "t
189
257
 
190
258
  The package includes comprehensive documentation in the `docs/` directory:
191
259
 
192
- - [Agent Guide](docs/agent_guide.md) - Detailed guide to creating and customizing agents
260
+ - [Agent Guide](docs/agent_guide.md) - Detailed guide to creating and customizing agents, including dynamic configuration
193
261
  - [Architecture](docs/architecture.md) - Overview of the SDK architecture and core concepts
194
262
  - [SWML Service Guide](docs/swml_service_guide.md) - Guide to the underlying SWML service
195
263
 
@@ -7,6 +7,7 @@ A Python SDK for creating, hosting, and securing SignalWire AI agents as microse
7
7
  - **Self-Contained Agents**: Each agent is both a web app and an AI persona
8
8
  - **Prompt Object Model**: Structured prompt composition using POM
9
9
  - **SWAIG Integration**: Easily define and handle AI tools/functions
10
+ - **Dynamic Configuration**: Configure agents per-request for multi-tenant apps and personalization
10
11
  - **Custom Routing**: Dynamic request handling for different paths and content
11
12
  - **SIP Integration**: Route SIP calls to agents based on SIP usernames
12
13
  - **Security Built-In**: Session management, function-specific security tokens, and basic auth
@@ -137,6 +138,73 @@ Available prefabs include:
137
138
  - `FAQBotAgent`: Answers questions based on a knowledge base
138
139
  - `ConciergeAgent`: Routes users to specialized agents
139
140
  - `SurveyAgent`: Conducts structured surveys with questions and rating scales
141
+ - `ReceptionistAgent`: Greets callers and transfers them to appropriate departments
142
+
143
+ ## Dynamic Agent Configuration
144
+
145
+ Configure agents dynamically based on request parameters for multi-tenant applications, A/B testing, and personalization.
146
+
147
+ ### Static vs Dynamic Configuration
148
+
149
+ - **Static**: Agent configured once at startup (traditional approach)
150
+ - **Dynamic**: Agent configured fresh for each request based on parameters
151
+
152
+ ### Basic Example
153
+
154
+ ```python
155
+ from signalwire_agents import AgentBase
156
+
157
+ class DynamicAgent(AgentBase):
158
+ def __init__(self):
159
+ super().__init__(name="dynamic-agent", route="/dynamic")
160
+
161
+ # Set up dynamic configuration callback
162
+ self.set_dynamic_config_callback(self.configure_per_request)
163
+
164
+ def configure_per_request(self, query_params, body_params, headers, agent):
165
+ """Configure agent based on request parameters"""
166
+
167
+ # Extract parameters from request
168
+ tier = query_params.get('tier', 'standard')
169
+ language = query_params.get('language', 'en')
170
+ customer_id = query_params.get('customer_id')
171
+
172
+ # Configure voice and language
173
+ if language == 'es':
174
+ agent.add_language("Spanish", "es-ES", "rime.spore:mistv2")
175
+ else:
176
+ agent.add_language("English", "en-US", "rime.spore:mistv2")
177
+
178
+ # Configure based on service tier
179
+ if tier == 'premium':
180
+ agent.set_params({"end_of_speech_timeout": 300}) # Faster response
181
+ agent.prompt_add_section("Service Level", "You provide premium support.")
182
+ else:
183
+ agent.set_params({"end_of_speech_timeout": 500}) # Standard response
184
+ agent.prompt_add_section("Service Level", "You provide standard support.")
185
+
186
+ # Personalize with customer data
187
+ global_data = {"tier": tier, "language": language}
188
+ if customer_id:
189
+ global_data["customer_id"] = customer_id
190
+ agent.set_global_data(global_data)
191
+
192
+ # Usage examples:
193
+ # curl "http://localhost:3000/dynamic?tier=premium&language=es&customer_id=123"
194
+ # curl "http://localhost:3000/dynamic?tier=standard&language=en"
195
+ ```
196
+
197
+ ### Use Cases
198
+
199
+ - **Multi-tenant SaaS**: Different configurations per customer/organization
200
+ - **A/B Testing**: Test different agent behaviors with different user groups
201
+ - **Personalization**: Customize voice, prompts, and behavior per user
202
+ - **Localization**: Language and cultural adaptation based on user location
203
+ - **Dynamic Pricing**: Adjust features and capabilities based on subscription tiers
204
+
205
+ The `EphemeralAgentConfig` object provides all the same familiar methods as `AgentBase` (like `add_language()`, `prompt_add_section()`, `set_global_data()`) but applies them per-request instead of at startup.
206
+
207
+ For detailed documentation and advanced examples, see the [Agent Guide](docs/agent_guide.md#dynamic-agent-configuration).
140
208
 
141
209
  ## Configuration
142
210
 
@@ -161,7 +229,7 @@ To enable HTTPS directly (without a reverse proxy), set `SWML_SSL_ENABLED` to "t
161
229
 
162
230
  The package includes comprehensive documentation in the `docs/` directory:
163
231
 
164
- - [Agent Guide](docs/agent_guide.md) - Detailed guide to creating and customizing agents
232
+ - [Agent Guide](docs/agent_guide.md) - Detailed guide to creating and customizing agents, including dynamic configuration
165
233
  - [Architecture](docs/architecture.md) - Overview of the SDK architecture and core concepts
166
234
  - [SWML Service Guide](docs/swml_service_guide.md) - Guide to the underlying SWML service
167
235
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "signalwire_agents"
7
- version = "0.1.7"
7
+ version = "0.1.8"
8
8
  description = "SignalWire AI Agents SDK"
9
9
  authors = [
10
10
  {name = "SignalWire Team", email = "info@signalwire.com"}
@@ -14,7 +14,7 @@ SignalWire AI Agents SDK
14
14
  A package for building AI agents using SignalWire's AI and SWML capabilities.
15
15
  """
16
16
 
17
- __version__ = "0.1.7"
17
+ __version__ = "0.1.8"
18
18
 
19
19
  # Import core classes for easier access
20
20
  from signalwire_agents.core.agent_base import AgentBase
@@ -79,6 +79,169 @@ from signalwire_agents.core.swml_handler import AIVerbHandler
79
79
  # Create a logger
80
80
  logger = structlog.get_logger("agent_base")
81
81
 
82
+ class EphemeralAgentConfig:
83
+ """
84
+ An ephemeral configurator object that mimics AgentBase's configuration interface.
85
+
86
+ This allows dynamic configuration callbacks to use the same familiar methods
87
+ they would use during agent initialization, but for per-request configuration.
88
+ """
89
+
90
+ def __init__(self):
91
+ # Initialize all configuration containers
92
+ self._hints = []
93
+ self._languages = []
94
+ self._pronounce = []
95
+ self._params = {}
96
+ self._global_data = {}
97
+ self._prompt_sections = []
98
+ self._raw_prompt = None
99
+ self._post_prompt = None
100
+ self._function_includes = []
101
+ self._native_functions = []
102
+
103
+ # Mirror all the AgentBase configuration methods
104
+
105
+ def add_hint(self, hint: str) -> 'EphemeralAgentConfig':
106
+ """Add a simple string hint"""
107
+ if isinstance(hint, str) and hint:
108
+ self._hints.append(hint)
109
+ return self
110
+
111
+ def add_hints(self, hints: List[str]) -> 'EphemeralAgentConfig':
112
+ """Add multiple string hints"""
113
+ if hints and isinstance(hints, list):
114
+ for hint in hints:
115
+ if isinstance(hint, str) and hint:
116
+ self._hints.append(hint)
117
+ return self
118
+
119
+ def add_language(self, name: str, code: str, voice: str, **kwargs) -> 'EphemeralAgentConfig':
120
+ """Add a language configuration"""
121
+ language = {
122
+ "name": name,
123
+ "code": code,
124
+ "voice": voice
125
+ }
126
+
127
+ # Handle additional parameters
128
+ for key, value in kwargs.items():
129
+ if key in ["engine", "model", "speech_fillers", "function_fillers", "fillers"]:
130
+ language[key] = value
131
+
132
+ self._languages.append(language)
133
+ return self
134
+
135
+ def add_pronunciation(self, replace: str, with_text: str, ignore_case: bool = False) -> 'EphemeralAgentConfig':
136
+ """Add a pronunciation rule"""
137
+ if replace and with_text:
138
+ rule = {"replace": replace, "with": with_text}
139
+ if ignore_case:
140
+ rule["ignore_case"] = True
141
+ self._pronounce.append(rule)
142
+ return self
143
+
144
+ def set_param(self, key: str, value: Any) -> 'EphemeralAgentConfig':
145
+ """Set a single AI parameter"""
146
+ if key:
147
+ self._params[key] = value
148
+ return self
149
+
150
+ def set_params(self, params: Dict[str, Any]) -> 'EphemeralAgentConfig':
151
+ """Set multiple AI parameters"""
152
+ if params and isinstance(params, dict):
153
+ self._params.update(params)
154
+ return self
155
+
156
+ def set_global_data(self, data: Dict[str, Any]) -> 'EphemeralAgentConfig':
157
+ """Set global data"""
158
+ if data and isinstance(data, dict):
159
+ self._global_data = data
160
+ return self
161
+
162
+ def update_global_data(self, data: Dict[str, Any]) -> 'EphemeralAgentConfig':
163
+ """Update global data"""
164
+ if data and isinstance(data, dict):
165
+ self._global_data.update(data)
166
+ return self
167
+
168
+ def set_prompt_text(self, text: str) -> 'EphemeralAgentConfig':
169
+ """Set raw prompt text"""
170
+ self._raw_prompt = text
171
+ return self
172
+
173
+ def set_post_prompt(self, text: str) -> 'EphemeralAgentConfig':
174
+ """Set post-prompt text"""
175
+ self._post_prompt = text
176
+ return self
177
+
178
+ def prompt_add_section(self, title: str, body: str = "", bullets: Optional[List[str]] = None, **kwargs) -> 'EphemeralAgentConfig':
179
+ """Add a prompt section"""
180
+ section = {
181
+ "title": title,
182
+ "body": body
183
+ }
184
+ if bullets:
185
+ section["bullets"] = bullets
186
+
187
+ # Handle additional parameters
188
+ for key, value in kwargs.items():
189
+ if key in ["numbered", "numbered_bullets", "subsections"]:
190
+ section[key] = value
191
+
192
+ self._prompt_sections.append(section)
193
+ return self
194
+
195
+ def set_native_functions(self, function_names: List[str]) -> 'EphemeralAgentConfig':
196
+ """Set native functions"""
197
+ if function_names and isinstance(function_names, list):
198
+ self._native_functions = [name for name in function_names if isinstance(name, str)]
199
+ return self
200
+
201
+ def add_function_include(self, url: str, functions: List[str], meta_data: Optional[Dict[str, Any]] = None) -> 'EphemeralAgentConfig':
202
+ """Add a function include"""
203
+ if url and functions and isinstance(functions, list):
204
+ include = {"url": url, "functions": functions}
205
+ if meta_data and isinstance(meta_data, dict):
206
+ include["meta_data"] = meta_data
207
+ self._function_includes.append(include)
208
+ return self
209
+
210
+ def extract_config(self) -> Dict[str, Any]:
211
+ """
212
+ Extract the configuration as a dictionary for applying to the real agent.
213
+
214
+ Returns:
215
+ Dictionary containing all the configuration changes
216
+ """
217
+ config = {}
218
+
219
+ if self._hints:
220
+ config["hints"] = self._hints
221
+ if self._languages:
222
+ config["languages"] = self._languages
223
+ if self._pronounce:
224
+ config["pronounce"] = self._pronounce
225
+ if self._params:
226
+ config["params"] = self._params
227
+ if self._global_data:
228
+ config["global_data"] = self._global_data
229
+ if self._function_includes:
230
+ config["function_includes"] = self._function_includes
231
+ if self._native_functions:
232
+ config["native_functions"] = self._native_functions
233
+
234
+ # Handle prompt sections - these should be applied to the agent's POM, not as raw config
235
+ # The calling code should use these to build the prompt properly
236
+ if self._prompt_sections:
237
+ config["_ephemeral_prompt_sections"] = self._prompt_sections
238
+ if self._raw_prompt:
239
+ config["_ephemeral_raw_prompt"] = self._raw_prompt
240
+ if self._post_prompt:
241
+ config["_ephemeral_post_prompt"] = self._post_prompt
242
+
243
+ return config
244
+
82
245
  class AgentBase(SWMLService):
83
246
  """
84
247
  Base class for all SignalWire AI Agents.
@@ -238,6 +401,9 @@ class AgentBase(SWMLService):
238
401
  self._params = {}
239
402
  self._global_data = {}
240
403
  self._function_includes = []
404
+
405
+ # Dynamic configuration callback
406
+ self._dynamic_config_callback = None
241
407
 
242
408
  def _process_prompt_sections(self):
243
409
  """
@@ -1211,36 +1377,23 @@ class AgentBase(SWMLService):
1211
1377
  # Add the AI verb to the document
1212
1378
  self.add_verb("ai", ai_config)
1213
1379
 
1214
- # Apply any modifications from the callback
1380
+ # Apply any modifications from the callback to agent state
1215
1381
  if modifications and isinstance(modifications, dict):
1216
- # We need a way to apply modifications to the document
1217
- # Get the current document
1218
- document = self.get_document()
1219
-
1220
- # Simple recursive update function
1221
- def update_dict(target, source):
1222
- for key, value in source.items():
1223
- if isinstance(value, dict) and key in target and isinstance(target[key], dict):
1224
- update_dict(target[key], value)
1225
- else:
1226
- target[key] = value
1227
-
1228
- # Apply modifications to the document
1229
- update_dict(document, modifications)
1230
-
1231
- # Since we can't directly set the document in SWMLService,
1232
- # we'll need to reset and rebuild if there are modifications
1382
+ # Handle global_data modifications by updating the AI config directly
1383
+ if "global_data" in modifications:
1384
+ if modifications["global_data"]:
1385
+ # Merge the modification global_data with existing global_data
1386
+ ai_config["global_data"] = {**ai_config.get("global_data", {}), **modifications["global_data"]}
1387
+
1388
+ # Handle other modifications by updating the AI config
1389
+ for key, value in modifications.items():
1390
+ if key != "global_data": # global_data handled above
1391
+ ai_config[key] = value
1392
+
1393
+ # Clear and rebuild the document with the modified AI config
1233
1394
  self.reset_document()
1234
-
1235
- # Add the modified document's sections
1236
- for section_name, section_content in document["sections"].items():
1237
- if section_name != "main": # Main section is created by default
1238
- self.add_section(section_name)
1239
-
1240
- # Add each verb to the section
1241
- for verb_obj in section_content:
1242
- for verb_name, verb_config in verb_obj.items():
1243
- self.add_verb_to_section(section_name, verb_name, verb_config)
1395
+ self.add_answer_verb()
1396
+ self.add_verb("ai", ai_config)
1244
1397
 
1245
1398
  # Return the rendered document as a string
1246
1399
  return self.render_document()
@@ -2108,16 +2261,15 @@ class AgentBase(SWMLService):
2108
2261
 
2109
2262
  # Allow subclasses to inspect/modify the request
2110
2263
  modifications = None
2111
- if body:
2112
- try:
2113
- modifications = self.on_swml_request(body, callback_path)
2114
- if modifications:
2115
- req_log.debug("request_modifications_applied")
2116
- except Exception as e:
2117
- req_log.error("error_in_request_modifier", error=str(e))
2264
+ try:
2265
+ modifications = self.on_swml_request(body, callback_path, request)
2266
+ if modifications:
2267
+ req_log.debug("request_modifications_applied")
2268
+ except Exception as e:
2269
+ req_log.error("error_in_request_modifier", error=str(e))
2118
2270
 
2119
2271
  # Render SWML
2120
- swml = self._render_swml(call_id)
2272
+ swml = self._render_swml(call_id, modifications)
2121
2273
  req_log.debug("swml_rendered", swml_size=len(swml))
2122
2274
 
2123
2275
  # Return as JSON
@@ -2176,13 +2328,15 @@ class AgentBase(SWMLService):
2176
2328
 
2177
2329
  # Allow subclasses to inspect/modify the request
2178
2330
  modifications = None
2179
- if body:
2180
- modifications = self.on_swml_request(body)
2331
+ try:
2332
+ modifications = self.on_swml_request(body, None, request)
2181
2333
  if modifications:
2182
2334
  req_log.debug("request_modifications_applied")
2335
+ except Exception as e:
2336
+ req_log.error("error_in_request_modifier", error=str(e))
2183
2337
 
2184
2338
  # Render SWML
2185
- swml = self._render_swml(call_id)
2339
+ swml = self._render_swml(call_id, modifications)
2186
2340
  req_log.debug("swml_rendered", swml_size=len(swml))
2187
2341
 
2188
2342
  # Return as JSON
@@ -2492,24 +2646,66 @@ class AgentBase(SWMLService):
2492
2646
  """
2493
2647
  # First try to call on_swml_request if it exists (backward compatibility)
2494
2648
  if hasattr(self, 'on_swml_request') and callable(getattr(self, 'on_swml_request')):
2495
- return self.on_swml_request(request_data, callback_path)
2649
+ return self.on_swml_request(request_data, callback_path, None)
2496
2650
 
2497
2651
  # If no on_swml_request or it returned None, we'll proceed with default rendering
2498
2652
  # We're not returning any modifications here because _render_swml will be called
2499
2653
  # to generate the complete SWML document
2500
2654
  return None
2501
2655
 
2502
- def on_swml_request(self, request_data: Optional[dict] = None, callback_path: Optional[str] = None) -> Optional[dict]:
2656
+ def on_swml_request(self, request_data: Optional[dict] = None, callback_path: Optional[str] = None, request: Optional[Request] = None) -> Optional[dict]:
2503
2657
  """
2504
2658
  Customization point for subclasses to modify SWML based on request data
2505
2659
 
2506
2660
  Args:
2507
2661
  request_data: Optional dictionary containing the parsed POST body
2508
2662
  callback_path: Optional callback path
2663
+ request: Optional FastAPI Request object for accessing query params, headers, etc.
2509
2664
 
2510
2665
  Returns:
2511
2666
  Optional dict with modifications to apply to the SWML document
2512
2667
  """
2668
+ # Handle dynamic configuration callback if set
2669
+ if self._dynamic_config_callback and request:
2670
+ try:
2671
+ # Extract request data
2672
+ query_params = dict(request.query_params)
2673
+ body_params = request_data or {}
2674
+ headers = dict(request.headers)
2675
+
2676
+ # Create ephemeral configurator
2677
+ agent_config = EphemeralAgentConfig()
2678
+
2679
+ # Call the user's configuration callback
2680
+ self._dynamic_config_callback(query_params, body_params, headers, agent_config)
2681
+
2682
+ # Extract the configuration
2683
+ config = agent_config.extract_config()
2684
+ if config:
2685
+ # Handle ephemeral prompt sections by applying them to this agent instance
2686
+ if "_ephemeral_prompt_sections" in config:
2687
+ for section in config["_ephemeral_prompt_sections"]:
2688
+ self.prompt_add_section(
2689
+ section["title"],
2690
+ section.get("body", ""),
2691
+ section.get("bullets"),
2692
+ **{k: v for k, v in section.items() if k not in ["title", "body", "bullets"]}
2693
+ )
2694
+ del config["_ephemeral_prompt_sections"]
2695
+
2696
+ if "_ephemeral_raw_prompt" in config:
2697
+ self._raw_prompt = config["_ephemeral_raw_prompt"]
2698
+ del config["_ephemeral_raw_prompt"]
2699
+
2700
+ if "_ephemeral_post_prompt" in config:
2701
+ self._post_prompt = config["_ephemeral_post_prompt"]
2702
+ del config["_ephemeral_post_prompt"]
2703
+
2704
+ return config
2705
+
2706
+ except Exception as e:
2707
+ self.log.error("dynamic_config_error", error=str(e))
2708
+
2513
2709
  # Default implementation does nothing
2514
2710
  return None
2515
2711
 
@@ -2542,6 +2738,34 @@ class AgentBase(SWMLService):
2542
2738
  self._routing_callbacks = {}
2543
2739
  self._routing_callbacks[normalized_path] = callback_fn
2544
2740
 
2741
+ def set_dynamic_config_callback(self, callback: Callable[[dict, dict, dict, EphemeralAgentConfig], None]) -> 'AgentBase':
2742
+ """
2743
+ Set a callback function for dynamic agent configuration
2744
+
2745
+ This callback receives an EphemeralAgentConfig object that provides the same
2746
+ configuration methods as AgentBase, allowing you to dynamically configure
2747
+ the agent's voice, prompt, parameters, etc. based on request data.
2748
+
2749
+ Args:
2750
+ callback: Function that takes (query_params, body_params, headers, agent_config)
2751
+ and configures the agent_config object using familiar methods like:
2752
+ - agent_config.add_language(...)
2753
+ - agent_config.prompt_add_section(...)
2754
+ - agent_config.set_params(...)
2755
+ - agent_config.set_global_data(...)
2756
+
2757
+ Example:
2758
+ def my_config(query_params, body_params, headers, agent):
2759
+ if query_params.get('tier') == 'premium':
2760
+ agent.add_language("English", "en-US", "premium_voice")
2761
+ agent.set_params({"end_of_speech_timeout": 500})
2762
+ agent.set_global_data({"tier": query_params.get('tier', 'standard')})
2763
+
2764
+ my_agent.set_dynamic_config_callback(my_config)
2765
+ """
2766
+ self._dynamic_config_callback = callback
2767
+ return self
2768
+
2545
2769
  def manual_set_proxy_url(self, proxy_url: str) -> 'AgentBase':
2546
2770
  """
2547
2771
  Manually set the proxy URL base for webhook callbacks