signalwire-agents 0.1.10__tar.gz → 0.1.11__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 (68) hide show
  1. signalwire_agents-0.1.11/PKG-INFO +756 -0
  2. signalwire_agents-0.1.11/README.md +726 -0
  3. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/pyproject.toml +5 -2
  4. signalwire_agents-0.1.11/signalwire_agents/__init__.py +64 -0
  5. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/agent_server.py +46 -2
  6. signalwire_agents-0.1.11/signalwire_agents/cli/__init__.py +9 -0
  7. signalwire_agents-0.1.11/signalwire_agents/cli/test_swaig.py +2545 -0
  8. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/agent_base.py +691 -82
  9. signalwire_agents-0.1.11/signalwire_agents/core/contexts.py +289 -0
  10. signalwire_agents-0.1.11/signalwire_agents/core/data_map.py +499 -0
  11. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/function_result.py +57 -10
  12. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/skill_base.py +27 -37
  13. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/skill_manager.py +89 -23
  14. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/swaig_function.py +13 -1
  15. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/swml_handler.py +37 -13
  16. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/swml_service.py +37 -28
  17. signalwire_agents-0.1.11/signalwire_agents/skills/datasphere/__init__.py +12 -0
  18. signalwire_agents-0.1.11/signalwire_agents/skills/datasphere/skill.py +229 -0
  19. signalwire_agents-0.1.11/signalwire_agents/skills/datasphere_serverless/__init__.py +1 -0
  20. signalwire_agents-0.1.11/signalwire_agents/skills/datasphere_serverless/skill.py +156 -0
  21. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/skills/datetime/skill.py +9 -5
  22. signalwire_agents-0.1.11/signalwire_agents/skills/joke/__init__.py +1 -0
  23. signalwire_agents-0.1.11/signalwire_agents/skills/joke/skill.py +88 -0
  24. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/skills/math/skill.py +9 -6
  25. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/skills/registry.py +23 -4
  26. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/skills/web_search/skill.py +57 -21
  27. signalwire_agents-0.1.11/signalwire_agents/skills/wikipedia/__init__.py +9 -0
  28. signalwire_agents-0.1.11/signalwire_agents/skills/wikipedia/skill.py +180 -0
  29. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/utils/__init__.py +2 -0
  30. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/utils/schema_utils.py +111 -44
  31. signalwire_agents-0.1.11/signalwire_agents/utils/serverless.py +38 -0
  32. signalwire_agents-0.1.11/signalwire_agents.egg-info/PKG-INFO +756 -0
  33. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents.egg-info/SOURCES.txt +14 -0
  34. signalwire_agents-0.1.11/signalwire_agents.egg-info/entry_points.txt +2 -0
  35. signalwire_agents-0.1.10/PKG-INFO +0 -319
  36. signalwire_agents-0.1.10/README.md +0 -289
  37. signalwire_agents-0.1.10/signalwire_agents/__init__.py +0 -29
  38. signalwire_agents-0.1.10/signalwire_agents.egg-info/PKG-INFO +0 -319
  39. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/LICENSE +0 -0
  40. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/schema.json +0 -0
  41. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/setup.cfg +0 -0
  42. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/setup.py +0 -0
  43. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/__init__.py +0 -0
  44. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/pom_builder.py +0 -0
  45. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/security/__init__.py +0 -0
  46. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/security/session_manager.py +0 -0
  47. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/state/__init__.py +0 -0
  48. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/state/file_state_manager.py +0 -0
  49. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/state/state_manager.py +0 -0
  50. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/swml_builder.py +0 -0
  51. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/core/swml_renderer.py +0 -0
  52. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/prefabs/__init__.py +0 -0
  53. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/prefabs/concierge.py +0 -0
  54. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/prefabs/faq_bot.py +0 -0
  55. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/prefabs/info_gatherer.py +0 -0
  56. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/prefabs/receptionist.py +0 -0
  57. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/prefabs/survey.py +0 -0
  58. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/schema.json +0 -0
  59. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/skills/__init__.py +0 -0
  60. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/skills/datetime/__init__.py +0 -0
  61. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/skills/math/__init__.py +0 -0
  62. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/skills/web_search/__init__.py +0 -0
  63. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/utils/pom_utils.py +0 -0
  64. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/utils/token_generators.py +0 -0
  65. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents/utils/validators.py +0 -0
  66. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents.egg-info/dependency_links.txt +0 -0
  67. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents.egg-info/requires.txt +0 -0
  68. {signalwire_agents-0.1.10 → signalwire_agents-0.1.11}/signalwire_agents.egg-info/top_level.txt +0 -0
@@ -0,0 +1,756 @@
1
+ Metadata-Version: 2.4
2
+ Name: signalwire_agents
3
+ Version: 0.1.11
4
+ Summary: SignalWire AI Agents SDK
5
+ Author-email: SignalWire Team <info@signalwire.com>
6
+ Project-URL: Homepage, https://github.com/signalwire/signalwire-ai-agents
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.7
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Requires-Python: >=3.7
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: fastapi==0.115.12
20
+ Requires-Dist: pydantic==2.11.4
21
+ Requires-Dist: PyYAML==6.0.2
22
+ Requires-Dist: Requests==2.32.3
23
+ Requires-Dist: setuptools==66.1.1
24
+ Requires-Dist: signalwire_pom==2.7.1
25
+ Requires-Dist: structlog==25.3.0
26
+ Requires-Dist: uvicorn==0.34.2
27
+ Requires-Dist: beautifulsoup4==4.12.3
28
+ Requires-Dist: pytz==2023.3
29
+ Dynamic: license-file
30
+
31
+ # SignalWire AI Agent SDK
32
+
33
+ A Python SDK for creating, hosting, and securing SignalWire AI agents as microservices with minimal boilerplate.
34
+
35
+ ## Features
36
+
37
+ - **Self-Contained Agents**: Each agent is both a web app and an AI persona
38
+ - **Prompt Object Model**: Structured prompt composition using POM
39
+ - **SWAIG Integration**: Easily define and handle AI tools/functions
40
+ - **Dynamic Configuration**: Configure agents per-request for multi-tenant apps and personalization
41
+ - **Custom Routing**: Dynamic request handling for different paths and content
42
+ - **SIP Integration**: Route SIP calls to agents based on SIP usernames
43
+ - **Security Built-In**: Session management, function-specific security tokens, and basic auth
44
+ - **State Management**: Persistent conversation state with automatic tracking
45
+ - **Prefab Archetypes**: Ready-to-use agent types for common scenarios
46
+ - **Multi-Agent Support**: Host multiple agents on a single server
47
+ - **Modular Skills System**: Add capabilities to agents with simple one-liner calls
48
+
49
+ ## Skills System
50
+
51
+ The SignalWire Agents SDK includes a powerful modular skills system that allows you to add complex capabilities to your agents with simple one-liner calls:
52
+
53
+ ```python
54
+ from signalwire_agents import AgentBase
55
+
56
+ # Create an agent
57
+ agent = AgentBase("My Assistant", route="/assistant")
58
+
59
+ # Add skills with one-liners
60
+ agent.add_skill("web_search", {
61
+ "api_key": "your-google-api-key",
62
+ "search_engine_id": "your-search-engine-id"
63
+ }) # Web search capability
64
+ agent.add_skill("datetime") # Current date/time info
65
+ agent.add_skill("math") # Mathematical calculations
66
+
67
+ # Configure skills with parameters
68
+ agent.add_skill("web_search", {
69
+ "api_key": "your-google-api-key",
70
+ "search_engine_id": "your-search-engine-id",
71
+ "num_results": 1, # Get 1 search results
72
+ "no_results_message": "Sorry, I couldn't find anything about '{query}'. Try rephrasing your question."
73
+ })
74
+
75
+ # Advanced: Customize SWAIG function properties
76
+ agent.add_skill("math", {
77
+ "swaig_fields": {
78
+ "secure": False, # Override security settings
79
+ "fillers": {"en-US": ["Calculating..."]} # Custom filler phrases
80
+ }
81
+ })
82
+
83
+ # Multiple web search instances with different tool names
84
+ agent.add_skill("web_search", {
85
+ "api_key": "your-google-api-key",
86
+ "search_engine_id": "general-search-engine-id",
87
+ "tool_name": "search_general", # Creates search_general tool
88
+ "num_results": 1
89
+ })
90
+
91
+ agent.add_skill("web_search", {
92
+ "api_key": "your-google-api-key",
93
+ "search_engine_id": "news-search-engine-id",
94
+ "tool_name": "search_news", # Creates search_news tool
95
+ "num_results": 3,
96
+ "delay": 0.5
97
+ })
98
+
99
+ # Multiple DataSphere instances with different tool names
100
+ agent.add_skill("datasphere", {
101
+ "space_name": "my-space",
102
+ "project_id": "my-project",
103
+ "token": "my-token",
104
+ "document_id": "drinks-doc",
105
+ "tool_name": "search_drinks", # Creates search_drinks tool
106
+ "count": 2
107
+ })
108
+
109
+ agent.add_skill("datasphere", {
110
+ "space_name": "my-space",
111
+ "project_id": "my-project",
112
+ "token": "my-token",
113
+ "document_id": "food-doc",
114
+ "tool_name": "search_recipes", # Creates search_recipes tool
115
+ "tags": ["Food", "Recipes"]
116
+ })
117
+
118
+ agent.serve()
119
+ ```
120
+
121
+ ### Available Built-in Skills
122
+
123
+ - **web_search**: Google Custom Search API integration with web scraping (supports multiple instances)
124
+ - **datetime**: Current date and time with timezone support
125
+ - **math**: Safe mathematical expression evaluation
126
+ - **datasphere**: SignalWire DataSphere knowledge search (supports multiple instances)
127
+
128
+ ### Benefits
129
+
130
+ - **One-liner integration**: `agent.add_skill("skill_name")`
131
+ - **Configurable parameters**: `agent.add_skill("skill_name", {"param": "value"})`
132
+ - **Automatic discovery**: Skills are automatically found from the skills directory
133
+ - **Dependency validation**: Clear error messages for missing requirements
134
+ - **Modular architecture**: Skills are self-contained and reusable
135
+
136
+ For detailed documentation, see [Skills System README](docs/SKILLS_SYSTEM_README.md).
137
+
138
+ ## DataMap Tools
139
+
140
+ The SDK provides a DataMap system for creating SWAIG tools that integrate directly with REST APIs without requiring custom webhook endpoints. DataMap tools execute on the SignalWire server, making them simpler to deploy than traditional webhook-based tools.
141
+
142
+ ### Basic DataMap Usage
143
+
144
+ ```python
145
+ from signalwire_agents import AgentBase
146
+ from signalwire_agents.core.data_map import DataMap
147
+ from signalwire_agents.core.function_result import SwaigFunctionResult
148
+
149
+ class APIAgent(AgentBase):
150
+ def __init__(self):
151
+ super().__init__(name="api-agent", route="/api")
152
+
153
+ # Create a simple weather API tool
154
+ weather_tool = (DataMap('get_weather')
155
+ .description('Get current weather information')
156
+ .parameter('location', 'string', 'City name', required=True)
157
+ .webhook('GET', 'https://api.weather.com/v1/current?key=YOUR_API_KEY&q=${location}')
158
+ .output(SwaigFunctionResult('Weather in ${location}: ${response.current.condition.text}, ${response.current.temp_f}°F'))
159
+ )
160
+
161
+ # Register the tool with the agent
162
+ self.register_swaig_function(weather_tool.to_swaig_function())
163
+
164
+ agent = APIAgent()
165
+ agent.serve()
166
+ ```
167
+
168
+ ### Advanced DataMap Examples
169
+
170
+ ```python
171
+ # POST API with authentication
172
+ search_tool = (DataMap('search_knowledge')
173
+ .description('Search company knowledge base')
174
+ .parameter('query', 'string', 'Search query', required=True)
175
+ .webhook('POST', 'https://api.company.com/search',
176
+ headers={'Authorization': 'Bearer YOUR_TOKEN'})
177
+ .body({'query': '${query}', 'limit': 3})
178
+ .output(SwaigFunctionResult('Found: ${response.title} - ${response.summary}'))
179
+ )
180
+
181
+ # Expression-based tools (no API calls)
182
+ control_tool = (DataMap('file_control')
183
+ .description('Control file playback')
184
+ .parameter('command', 'string', 'Playback command')
185
+ .parameter('filename', 'string', 'File to control', required=False)
186
+ .expression(r'start.*', SwaigFunctionResult().add_action('start_playback', {'file': '${args.filename}'}))
187
+ .expression(r'stop.*', SwaigFunctionResult().add_action('stop_playback', True))
188
+ )
189
+
190
+ # Process API response arrays
191
+ docs_tool = (DataMap('get_latest_docs')
192
+ .description('Get latest documentation')
193
+ .webhook('GET', 'https://api.docs.com/latest')
194
+ .foreach('${response.documents}')
195
+ .output(SwaigFunctionResult('Document: ${foreach.title} (${foreach.updated_date})'))
196
+ )
197
+ ```
198
+
199
+ ### Helper Functions
200
+
201
+ For simpler use cases, use the convenience functions:
202
+
203
+ ```python
204
+ from signalwire_agents.core.data_map import create_simple_api_tool, create_expression_tool
205
+
206
+ # Simple API tool
207
+ weather = create_simple_api_tool(
208
+ name='get_weather',
209
+ url='https://api.weather.com/v1/current?key=API_KEY&q=${location}',
210
+ response_template='Weather in ${location}: ${response.current.condition.text}',
211
+ parameters={'location': {'type': 'string', 'description': 'City name', 'required': True}}
212
+ )
213
+
214
+ # Expression-based tool
215
+ file_control = create_expression_tool(
216
+ name='file_control',
217
+ patterns={
218
+ r'start.*': SwaigFunctionResult().add_action('start_playback', {'file': '${args.filename}'}),
219
+ r'stop.*': SwaigFunctionResult().add_action('stop_playback', True)
220
+ },
221
+ parameters={'command': {'type': 'string', 'description': 'Playback command'}}
222
+ )
223
+
224
+ # Register with agent
225
+ self.register_swaig_function(weather.to_swaig_function())
226
+ self.register_swaig_function(file_control.to_swaig_function())
227
+ ```
228
+
229
+ ### Variable Expansion
230
+
231
+ DataMap tools support powerful variable expansion using `${variable}` syntax:
232
+
233
+ - **Function arguments**: `${args.parameter_name}`
234
+ - **API responses**: `${response.field.nested_field}`
235
+ - **Array processing**: `${foreach.item_field}` (when using foreach)
236
+ - **Global data**: `${global_data.key}`
237
+ - **Metadata**: `${meta_data.call_id}`
238
+
239
+ ### Benefits of DataMap Tools
240
+
241
+ - **No webhook infrastructure**: Tools run on SignalWire servers
242
+ - **Simplified deployment**: No need to expose endpoints
243
+ - **Built-in authentication**: Support for API keys, Bearer tokens, Basic auth
244
+ - **Response processing**: Built-in JSON path traversal and array iteration
245
+ - **Error handling**: Automatic error detection with `error_keys`
246
+ - **Pattern matching**: Expression-based responses without API calls
247
+
248
+ For detailed documentation, see [DataMap Guide](docs/datamap_guide.md).
249
+
250
+ ## Contexts and Steps
251
+
252
+ The SignalWire Agents SDK provides a powerful alternative to traditional Prompt Object Model (POM) prompts through the **Contexts and Steps** system. This feature allows you to create structured, workflow-driven AI interactions with explicit navigation control and step-by-step guidance.
253
+
254
+ ### Why Use Contexts and Steps?
255
+
256
+ - **Structured Workflows**: Define clear, step-by-step processes for complex interactions
257
+ - **Navigation Control**: Explicitly control which steps or contexts users can access
258
+ - **Completion Criteria**: Set specific criteria for step completion and progression
259
+ - **Function Restrictions**: Limit which AI tools are available in each step
260
+ - **Workflow Isolation**: Create separate contexts for different conversation flows
261
+ - **Backward Compatibility**: Works alongside traditional prompts and all existing AgentBase features
262
+
263
+ ### Basic Usage
264
+
265
+ ```python
266
+ from signalwire_agents import AgentBase
267
+
268
+ class WorkflowAgent(AgentBase):
269
+ def __init__(self):
270
+ super().__init__(name="Workflow Assistant", route="/workflow")
271
+
272
+ # Define contexts and steps (alternative to traditional prompts)
273
+ contexts = self.define_contexts()
274
+
275
+ # Create a single context named "default" (required for single context)
276
+ context = contexts.create_context("default")
277
+
278
+ # Add step-by-step workflow
279
+ context.create_step("greeting") \
280
+ .set_text("Welcome! I'm here to help you complete your application. Let's start with your personal information.") \
281
+ .set_step_criteria("User has provided their name and confirmed they want to continue") \
282
+ .set_valid_steps(["personal_info"]) # Can only go to personal_info step
283
+
284
+ context.create_step("personal_info") \
285
+ .add_section("Instructions", "Collect the user's personal information") \
286
+ .add_bullets(["Ask for full name", "Ask for email address", "Ask for phone number"]) \
287
+ .set_step_criteria("All personal information has been collected and confirmed") \
288
+ .set_valid_steps(["review", "personal_info"]) # Can stay or move to review
289
+
290
+ context.create_step("review") \
291
+ .set_text("Let me review the information you've provided. Please confirm if everything is correct.") \
292
+ .set_step_criteria("User has confirmed or requested changes") \
293
+ .set_valid_steps(["personal_info", "complete"]) # Can go back or complete
294
+
295
+ context.create_step("complete") \
296
+ .set_text("Thank you! Your application has been submitted successfully.") \
297
+ .set_step_criteria("Application processing is complete")
298
+ # No valid_steps = end of workflow
299
+
300
+ agent = WorkflowAgent()
301
+ agent.serve()
302
+ ```
303
+
304
+ ### Advanced Features
305
+
306
+ ```python
307
+ class MultiContextAgent(AgentBase):
308
+ def __init__(self):
309
+ super().__init__(name="Multi-Context Agent", route="/multi-context")
310
+
311
+ # Add skills first
312
+ self.add_skill("datetime")
313
+ self.add_skill("math")
314
+
315
+ contexts = self.define_contexts()
316
+
317
+ # Main conversation context
318
+ main_context = contexts.create_context("main")
319
+ main_context.create_step("welcome") \
320
+ .set_text("Welcome! I can help with calculations or provide date/time info. What would you like to do?") \
321
+ .set_step_criteria("User has chosen a service type") \
322
+ .set_valid_contexts(["calculator", "datetime_info"]) # Can switch contexts
323
+
324
+ # Calculator context with function restrictions
325
+ calc_context = contexts.create_context("calculator")
326
+ calc_context.create_step("math_mode") \
327
+ .add_section("Role", "You are a mathematical assistant") \
328
+ .add_section("Instructions", "Help users with calculations") \
329
+ .set_functions(["math"]) # Only math function available \
330
+ .set_step_criteria("Calculation is complete") \
331
+ .set_valid_contexts(["main"]) # Can return to main
332
+
333
+ # DateTime context
334
+ datetime_context = contexts.create_context("datetime_info")
335
+ datetime_context.create_step("time_mode") \
336
+ .set_text("I can provide current date and time information. What would you like to know?") \
337
+ .set_functions(["datetime"]) # Only datetime function available \
338
+ .set_step_criteria("Date/time information has been provided") \
339
+ .set_valid_contexts(["main"]) # Can return to main
340
+ ```
341
+
342
+ ### Context and Step Methods
343
+
344
+ #### Context Methods
345
+ - `create_step(name)`: Create a new step in this context
346
+ - `set_valid_contexts(contexts)`: Control which contexts can be accessed from this context
347
+
348
+ #### Step Methods
349
+ - `set_text(text)`: Set direct text prompt for the step
350
+ - `add_section(title, body)`: Add POM-style section (alternative to set_text)
351
+ - `add_bullets(bullets)`: Add bullet points to the current or last section
352
+ - `set_step_criteria(criteria)`: Define completion criteria for this step
353
+ - `set_functions(functions)`: Restrict available functions ("none" or array of function names)
354
+ - `set_valid_steps(steps)`: Control navigation to other steps in same context
355
+ - `set_valid_contexts(contexts)`: Control navigation to other contexts
356
+
357
+ ### Navigation Rules
358
+
359
+ - **Valid Steps**: If omitted, only "next" step is implied. If specified, only those steps are allowed.
360
+ - **Valid Contexts**: If omitted, user is trapped in current context. If specified, can navigate to those contexts.
361
+ - **Single Context**: Must be named "default" for single-context workflows.
362
+ - **Function Restrictions**: Use `set_functions(["function_name"])` or `set_functions("none")` to control AI tool access.
363
+
364
+ ### Complete Example: Customer Support Workflow
365
+
366
+ ```python
367
+ class SupportAgent(AgentBase):
368
+ def __init__(self):
369
+ super().__init__(name="Customer Support", route="/support")
370
+
371
+ # Add skills for enhanced capabilities
372
+ self.add_skill("datetime")
373
+ self.add_skill("web_search", {"api_key": "your-key", "search_engine_id": "your-id"})
374
+
375
+ contexts = self.define_contexts()
376
+
377
+ # Triage context
378
+ triage = contexts.create_context("triage")
379
+ triage.create_step("initial_greeting") \
380
+ .add_section("Role", "You are a helpful customer support agent") \
381
+ .add_section("Goal", "Understand the customer's issue and route them appropriately") \
382
+ .add_bullets(["Be empathetic and professional", "Ask clarifying questions", "Categorize the issue"]) \
383
+ .set_step_criteria("Issue type has been identified") \
384
+ .set_valid_contexts(["technical_support", "billing_support", "general_inquiry"])
385
+
386
+ # Technical support context
387
+ tech = contexts.create_context("technical_support")
388
+ tech.create_step("technical_diagnosis") \
389
+ .add_section("Role", "You are a technical support specialist") \
390
+ .add_section("Instructions", "Help diagnose and resolve technical issues") \
391
+ .set_functions(["web_search", "datetime"]) # Can search for solutions and check times \
392
+ .set_step_criteria("Technical issue is resolved or escalated") \
393
+ .set_valid_contexts(["triage"]) # Can return to triage
394
+
395
+ # Billing support context
396
+ billing = contexts.create_context("billing_support")
397
+ billing.create_step("billing_assistance") \
398
+ .set_text("I'll help you with your billing inquiry. Please provide your account details.") \
399
+ .set_functions("none") # No external tools for sensitive billing info \
400
+ .set_step_criteria("Billing issue is addressed") \
401
+ .set_valid_contexts(["triage"])
402
+
403
+ # General inquiry context
404
+ general = contexts.create_context("general_inquiry")
405
+ general.create_step("general_help") \
406
+ .set_text("I'm here to help with general questions. What can I assist you with?") \
407
+ .set_functions(["web_search", "datetime"]) # Full access to search and time \
408
+ .set_step_criteria("Inquiry has been answered") \
409
+ .set_valid_contexts(["triage"])
410
+
411
+ agent = SupportAgent()
412
+ agent.serve()
413
+ ```
414
+
415
+ ### Benefits
416
+
417
+ - **Clear Structure**: Explicit workflow definition makes agent behavior predictable
418
+ - **Enhanced Control**: Fine-grained control over function access and navigation
419
+ - **Improved UX**: Users understand where they are in the process and what's expected
420
+ - **Debugging**: Easy to trace and debug workflow issues
421
+ - **Scalability**: Complex multi-step processes are easier to maintain
422
+
423
+ For detailed documentation and advanced examples, see [Contexts and Steps Guide](docs/contexts_guide.md).
424
+
425
+ ## Installation
426
+
427
+ ```bash
428
+ pip install signalwire-agents
429
+ ```
430
+
431
+ ## Quick Start
432
+
433
+ ```python
434
+ from signalwire_agents import AgentBase
435
+ from signalwire_agents.core.function_result import SwaigFunctionResult
436
+
437
+ class SimpleAgent(AgentBase):
438
+ def __init__(self):
439
+ super().__init__(name="simple", route="/simple")
440
+
441
+ # Configure the agent's personality
442
+ self.prompt_add_section("Personality", body="You are a helpful assistant.")
443
+ self.prompt_add_section("Goal", body="Help users with basic questions.")
444
+ self.prompt_add_section("Instructions", bullets=["Be concise and clear."])
445
+
446
+ # Alternative using convenience methods:
447
+ # self.setPersonality("You are a helpful assistant.")
448
+ # self.setGoal("Help users with basic questions.")
449
+ # self.setInstructions(["Be concise and clear."])
450
+
451
+ @AgentBase.tool(
452
+ name="get_time",
453
+ description="Get the current time",
454
+ parameters={}
455
+ )
456
+ def get_time(self, args, raw_data):
457
+ from datetime import datetime
458
+ now = datetime.now().strftime("%H:%M:%S")
459
+ return SwaigFunctionResult(f"The current time is {now}")
460
+
461
+ # Run the agent
462
+ if __name__ == "__main__":
463
+ agent = SimpleAgent()
464
+ agent.serve(host="0.0.0.0", port=8000)
465
+ ```
466
+
467
+ ## Using State Management
468
+
469
+ ```python
470
+ from signalwire_agents import AgentBase
471
+ from signalwire_agents.core.function_result import SwaigFunctionResult
472
+ from signalwire_agents.core.state import FileStateManager
473
+
474
+ class StatefulAgent(AgentBase):
475
+ def __init__(self):
476
+ # Configure state management
477
+ state_manager = FileStateManager(storage_dir="./state_data")
478
+
479
+ super().__init__(
480
+ name="stateful",
481
+ route="/stateful",
482
+ enable_state_tracking=True, # Enable state tracking
483
+ state_manager=state_manager # Use custom state manager
484
+ )
485
+
486
+ # When enable_state_tracking=True, startup_hook and hangup_hook
487
+ # are automatically registered to track session lifecycle
488
+
489
+ # Custom tool for accessing and updating state
490
+ @AgentBase.tool(
491
+ name="save_preference",
492
+ description="Save a user preference",
493
+ parameters={
494
+ "preference_name": {
495
+ "type": "string",
496
+ "description": "Name of the preference to save"
497
+ },
498
+ "preference_value": {
499
+ "type": "string",
500
+ "description": "Value of the preference"
501
+ }
502
+ }
503
+ )
504
+ def save_preference(self, args, raw_data):
505
+ # Get the call ID from the raw data
506
+ call_id = raw_data.get("call_id")
507
+
508
+ if call_id:
509
+ # Get current state or empty dict if none exists
510
+ state = self.get_state(call_id) or {}
511
+
512
+ # Update the state
513
+ preferences = state.get("preferences", {})
514
+ preferences[args.get("preference_name")] = args.get("preference_value")
515
+ state["preferences"] = preferences
516
+
517
+ # Save the updated state
518
+ self.update_state(call_id, state)
519
+
520
+ return SwaigFunctionResult("Preference saved successfully")
521
+ else:
522
+ return SwaigFunctionResult("Could not save preference: No call ID")
523
+ ```
524
+
525
+ ## Using Prefab Agents
526
+
527
+ ```python
528
+ from signalwire_agents.prefabs import InfoGathererAgent
529
+
530
+ agent = InfoGathererAgent(
531
+ fields=[
532
+ {"name": "full_name", "prompt": "What is your full name?"},
533
+ {"name": "reason", "prompt": "How can I help you today?"}
534
+ ],
535
+ confirmation_template="Thanks {full_name}, I'll help you with {reason}.",
536
+ name="info-gatherer",
537
+ route="/info-gatherer"
538
+ )
539
+
540
+ agent.serve(host="0.0.0.0", port=8000)
541
+ ```
542
+
543
+ Available prefabs include:
544
+ - `InfoGathererAgent`: Collects structured information from users
545
+ - `FAQBotAgent`: Answers questions based on a knowledge base
546
+ - `ConciergeAgent`: Routes users to specialized agents
547
+ - `SurveyAgent`: Conducts structured surveys with questions and rating scales
548
+ - `ReceptionistAgent`: Greets callers and transfers them to appropriate departments
549
+
550
+ ## Dynamic Agent Configuration
551
+
552
+ Configure agents dynamically based on request parameters for multi-tenant applications, A/B testing, and personalization.
553
+
554
+ ### Static vs Dynamic Configuration
555
+
556
+ - **Static**: Agent configured once at startup (traditional approach)
557
+ - **Dynamic**: Agent configured fresh for each request based on parameters
558
+
559
+ ### Basic Example
560
+
561
+ ```python
562
+ from signalwire_agents import AgentBase
563
+
564
+ class DynamicAgent(AgentBase):
565
+ def __init__(self):
566
+ super().__init__(name="dynamic-agent", route="/dynamic")
567
+
568
+ # Set up dynamic configuration callback
569
+ self.set_dynamic_config_callback(self.configure_per_request)
570
+
571
+ def configure_per_request(self, query_params, body_params, headers, agent):
572
+ """Configure agent based on request parameters"""
573
+
574
+ # Extract parameters from request
575
+ tier = query_params.get('tier', 'standard')
576
+ language = query_params.get('language', 'en')
577
+ customer_id = query_params.get('customer_id')
578
+
579
+ # Configure voice and language
580
+ if language == 'es':
581
+ agent.add_language("Spanish", "es-ES", "rime.spore:mistv2")
582
+ else:
583
+ agent.add_language("English", "en-US", "rime.spore:mistv2")
584
+
585
+ # Configure based on service tier
586
+ if tier == 'premium':
587
+ agent.set_params({"end_of_speech_timeout": 300}) # Faster response
588
+ agent.prompt_add_section("Service Level", "You provide premium support.")
589
+ else:
590
+ agent.set_params({"end_of_speech_timeout": 500}) # Standard response
591
+ agent.prompt_add_section("Service Level", "You provide standard support.")
592
+
593
+ # Personalize with customer data
594
+ global_data = {"tier": tier, "language": language}
595
+ if customer_id:
596
+ global_data["customer_id"] = customer_id
597
+ agent.set_global_data(global_data)
598
+
599
+ # Usage examples:
600
+ # curl "http://localhost:3000/dynamic?tier=premium&language=es&customer_id=123"
601
+ # curl "http://localhost:3000/dynamic?tier=standard&language=en"
602
+ ```
603
+
604
+ ### Use Cases
605
+
606
+ - **Multi-tenant SaaS**: Different configurations per customer/organization
607
+ - **A/B Testing**: Test different agent behaviors with different user groups
608
+ - **Personalization**: Customize voice, prompts, and behavior per user
609
+ - **Localization**: Language and cultural adaptation based on user location
610
+ - **Dynamic Pricing**: Adjust features and capabilities based on subscription tiers
611
+
612
+ 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.
613
+
614
+ For detailed documentation and advanced examples, see the [Agent Guide](docs/agent_guide.md#dynamic-agent-configuration).
615
+
616
+ ## Configuration
617
+
618
+ ### Environment Variables
619
+
620
+ The SDK supports the following environment variables:
621
+
622
+ - `SWML_BASIC_AUTH_USER`: Username for basic auth (default: auto-generated)
623
+ - `SWML_BASIC_AUTH_PASSWORD`: Password for basic auth (default: auto-generated)
624
+ - `SWML_PROXY_URL_BASE`: Base URL to use when behind a reverse proxy, used for constructing webhook URLs
625
+ - `SWML_SSL_ENABLED`: Enable HTTPS/SSL support (values: "true", "1", "yes")
626
+ - `SWML_SSL_CERT_PATH`: Path to SSL certificate file
627
+ - `SWML_SSL_KEY_PATH`: Path to SSL private key file
628
+ - `SWML_DOMAIN`: Domain name for SSL certificate and external URLs
629
+ - `SWML_SCHEMA_PATH`: Optional path to override the location of the schema.json file
630
+
631
+ When the auth environment variables are set, they will be used for all agents instead of generating random credentials. The proxy URL base is useful when your service is behind a reverse proxy or when you need external services to access your webhooks.
632
+
633
+ To enable HTTPS directly (without a reverse proxy), set `SWML_SSL_ENABLED` to "true", provide valid paths to your certificate and key files, and specify your domain name.
634
+
635
+ ## Testing
636
+
637
+ The SDK includes a powerful `swaig-test` CLI tool for comprehensive local testing and serverless environment simulation.
638
+
639
+ ### Local Testing
640
+
641
+ Test your agents locally without deployment:
642
+
643
+ ```bash
644
+ # Install the SDK
645
+ pip install -e .
646
+
647
+ # Discover agents in a file
648
+ swaig-test examples/my_agent.py
649
+
650
+ # List available functions
651
+ swaig-test examples/my_agent.py --list-tools
652
+
653
+ # Test SWAIG functions with CLI syntax
654
+ swaig-test examples/my_agent.py --exec get_weather --location "New York"
655
+
656
+ # Generate and inspect SWML documents
657
+ swaig-test examples/my_agent.py --dump-swml
658
+ swaig-test examples/my_agent.py --dump-swml --format-json | jq '.'
659
+ ```
660
+
661
+ ### Serverless Environment Simulation
662
+
663
+ Test your agents in simulated serverless environments without deployment:
664
+
665
+ ```bash
666
+ # Test in AWS Lambda environment
667
+ swaig-test examples/my_agent.py --simulate-serverless lambda --dump-swml
668
+
669
+ # Test Lambda function execution with proper response format
670
+ swaig-test examples/my_agent.py --simulate-serverless lambda \
671
+ --exec get_weather --location "Miami" --full-request
672
+
673
+ # Test with custom Lambda configuration
674
+ swaig-test examples/my_agent.py --simulate-serverless lambda \
675
+ --aws-function-name my-production-function \
676
+ --aws-region us-west-2 \
677
+ --exec my_function --param value
678
+
679
+ # Test CGI environment
680
+ swaig-test examples/my_agent.py --simulate-serverless cgi \
681
+ --cgi-host my-server.com --cgi-https --dump-swml
682
+
683
+ # Test Google Cloud Functions
684
+ swaig-test examples/my_agent.py --simulate-serverless cloud_function \
685
+ --gcp-function-url https://my-function.cloudfunctions.net \
686
+ --exec my_function
687
+
688
+ # Test Azure Functions
689
+ swaig-test examples/my_agent.py --simulate-serverless azure_function \
690
+ --azure-function-url https://my-function.azurewebsites.net \
691
+ --exec my_function
692
+ ```
693
+
694
+ ### Environment Management
695
+
696
+ Use environment files for consistent testing across platforms:
697
+
698
+ ```bash
699
+ # Create environment file
700
+ cat > production.env << EOF
701
+ AWS_LAMBDA_FUNCTION_NAME=prod-my-agent
702
+ AWS_REGION=us-east-1
703
+ API_KEY=prod_api_key_123
704
+ DEBUG=false
705
+ EOF
706
+
707
+ # Test with environment file
708
+ swaig-test examples/my_agent.py --simulate-serverless lambda \
709
+ --env-file production.env --exec my_function
710
+
711
+ # Override specific variables
712
+ swaig-test examples/my_agent.py --simulate-serverless lambda \
713
+ --env-file production.env --env DEBUG=true --dump-swml
714
+ ```
715
+
716
+ ### Cross-Platform Testing
717
+
718
+ Test the same agent across multiple serverless platforms:
719
+
720
+ ```bash
721
+ # Test across all platforms
722
+ for platform in lambda cgi cloud_function azure_function; do
723
+ echo "Testing $platform..."
724
+ swaig-test examples/my_agent.py --simulate-serverless $platform \
725
+ --exec my_function --param value
726
+ done
727
+
728
+ # Compare webhook URLs across platforms
729
+ swaig-test examples/my_agent.py --simulate-serverless lambda --dump-swml | grep web_hook_url
730
+ swaig-test examples/my_agent.py --simulate-serverless cgi --cgi-host example.com --dump-swml | grep web_hook_url
731
+ ```
732
+
733
+ ### Key Benefits
734
+
735
+ - **No Deployment Required**: Test serverless behavior locally
736
+ - **Environment Simulation**: Complete platform-specific environment variable setup
737
+ - **URL Generation**: Verify webhook URLs are generated correctly for each platform
738
+ - **Function Execution**: Test with platform-specific request/response formats
739
+ - **Environment Files**: Reusable configurations for different stages
740
+ - **Multi-Platform**: Test Lambda, CGI, Cloud Functions, and Azure Functions
741
+
742
+ For detailed testing documentation, see the [CLI Testing Guide](docs/cli_testing_guide.md).
743
+
744
+ ## Documentation
745
+
746
+ The package includes comprehensive documentation in the `docs/` directory:
747
+
748
+ - [Agent Guide](docs/agent_guide.md) - Detailed guide to creating and customizing agents, including dynamic configuration
749
+ - [Architecture](docs/architecture.md) - Overview of the SDK architecture and core concepts
750
+ - [SWML Service Guide](docs/swml_service_guide.md) - Guide to the underlying SWML service
751
+
752
+ These documents provide in-depth explanations of the features, APIs, and usage patterns.
753
+
754
+ ## License
755
+
756
+ MIT