signalwire-agents 0.1.10__py3-none-any.whl → 0.1.12__py3-none-any.whl

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