signalwire-agents 0.1.6__py3-none-any.whl → 1.0.7__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 (140) hide show
  1. signalwire_agents/__init__.py +130 -4
  2. signalwire_agents/agent_server.py +438 -32
  3. signalwire_agents/agents/bedrock.py +296 -0
  4. signalwire_agents/cli/__init__.py +18 -0
  5. signalwire_agents/cli/build_search.py +1367 -0
  6. signalwire_agents/cli/config.py +80 -0
  7. signalwire_agents/cli/core/__init__.py +10 -0
  8. signalwire_agents/cli/core/agent_loader.py +470 -0
  9. signalwire_agents/cli/core/argparse_helpers.py +179 -0
  10. signalwire_agents/cli/core/dynamic_config.py +71 -0
  11. signalwire_agents/cli/core/service_loader.py +303 -0
  12. signalwire_agents/cli/execution/__init__.py +10 -0
  13. signalwire_agents/cli/execution/datamap_exec.py +446 -0
  14. signalwire_agents/cli/execution/webhook_exec.py +134 -0
  15. signalwire_agents/cli/init_project.py +1225 -0
  16. signalwire_agents/cli/output/__init__.py +10 -0
  17. signalwire_agents/cli/output/output_formatter.py +255 -0
  18. signalwire_agents/cli/output/swml_dump.py +186 -0
  19. signalwire_agents/cli/simulation/__init__.py +10 -0
  20. signalwire_agents/cli/simulation/data_generation.py +374 -0
  21. signalwire_agents/cli/simulation/data_overrides.py +200 -0
  22. signalwire_agents/cli/simulation/mock_env.py +282 -0
  23. signalwire_agents/cli/swaig_test_wrapper.py +52 -0
  24. signalwire_agents/cli/test_swaig.py +809 -0
  25. signalwire_agents/cli/types.py +81 -0
  26. signalwire_agents/core/__init__.py +2 -2
  27. signalwire_agents/core/agent/__init__.py +12 -0
  28. signalwire_agents/core/agent/config/__init__.py +12 -0
  29. signalwire_agents/core/agent/deployment/__init__.py +9 -0
  30. signalwire_agents/core/agent/deployment/handlers/__init__.py +9 -0
  31. signalwire_agents/core/agent/prompt/__init__.py +14 -0
  32. signalwire_agents/core/agent/prompt/manager.py +306 -0
  33. signalwire_agents/core/agent/routing/__init__.py +9 -0
  34. signalwire_agents/core/agent/security/__init__.py +9 -0
  35. signalwire_agents/core/agent/swml/__init__.py +9 -0
  36. signalwire_agents/core/agent/tools/__init__.py +15 -0
  37. signalwire_agents/core/agent/tools/decorator.py +97 -0
  38. signalwire_agents/core/agent/tools/registry.py +210 -0
  39. signalwire_agents/core/agent_base.py +959 -2166
  40. signalwire_agents/core/auth_handler.py +233 -0
  41. signalwire_agents/core/config_loader.py +259 -0
  42. signalwire_agents/core/contexts.py +707 -0
  43. signalwire_agents/core/data_map.py +487 -0
  44. signalwire_agents/core/function_result.py +1150 -1
  45. signalwire_agents/core/logging_config.py +376 -0
  46. signalwire_agents/core/mixins/__init__.py +28 -0
  47. signalwire_agents/core/mixins/ai_config_mixin.py +442 -0
  48. signalwire_agents/core/mixins/auth_mixin.py +287 -0
  49. signalwire_agents/core/mixins/prompt_mixin.py +358 -0
  50. signalwire_agents/core/mixins/serverless_mixin.py +368 -0
  51. signalwire_agents/core/mixins/skill_mixin.py +55 -0
  52. signalwire_agents/core/mixins/state_mixin.py +153 -0
  53. signalwire_agents/core/mixins/tool_mixin.py +230 -0
  54. signalwire_agents/core/mixins/web_mixin.py +1134 -0
  55. signalwire_agents/core/security/session_manager.py +174 -86
  56. signalwire_agents/core/security_config.py +333 -0
  57. signalwire_agents/core/skill_base.py +200 -0
  58. signalwire_agents/core/skill_manager.py +244 -0
  59. signalwire_agents/core/swaig_function.py +33 -9
  60. signalwire_agents/core/swml_builder.py +212 -12
  61. signalwire_agents/core/swml_handler.py +43 -13
  62. signalwire_agents/core/swml_renderer.py +123 -297
  63. signalwire_agents/core/swml_service.py +277 -260
  64. signalwire_agents/prefabs/concierge.py +6 -2
  65. signalwire_agents/prefabs/info_gatherer.py +149 -33
  66. signalwire_agents/prefabs/receptionist.py +14 -22
  67. signalwire_agents/prefabs/survey.py +6 -2
  68. signalwire_agents/schema.json +9218 -5489
  69. signalwire_agents/search/__init__.py +137 -0
  70. signalwire_agents/search/document_processor.py +1223 -0
  71. signalwire_agents/search/index_builder.py +804 -0
  72. signalwire_agents/search/migration.py +418 -0
  73. signalwire_agents/search/models.py +30 -0
  74. signalwire_agents/search/pgvector_backend.py +752 -0
  75. signalwire_agents/search/query_processor.py +502 -0
  76. signalwire_agents/search/search_engine.py +1264 -0
  77. signalwire_agents/search/search_service.py +574 -0
  78. signalwire_agents/skills/README.md +452 -0
  79. signalwire_agents/skills/__init__.py +23 -0
  80. signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
  81. signalwire_agents/skills/api_ninjas_trivia/__init__.py +12 -0
  82. signalwire_agents/skills/api_ninjas_trivia/skill.py +237 -0
  83. signalwire_agents/skills/datasphere/README.md +210 -0
  84. signalwire_agents/skills/datasphere/__init__.py +12 -0
  85. signalwire_agents/skills/datasphere/skill.py +310 -0
  86. signalwire_agents/skills/datasphere_serverless/README.md +258 -0
  87. signalwire_agents/skills/datasphere_serverless/__init__.py +10 -0
  88. signalwire_agents/skills/datasphere_serverless/skill.py +237 -0
  89. signalwire_agents/skills/datetime/README.md +132 -0
  90. signalwire_agents/skills/datetime/__init__.py +10 -0
  91. signalwire_agents/skills/datetime/skill.py +126 -0
  92. signalwire_agents/skills/joke/README.md +149 -0
  93. signalwire_agents/skills/joke/__init__.py +10 -0
  94. signalwire_agents/skills/joke/skill.py +109 -0
  95. signalwire_agents/skills/math/README.md +161 -0
  96. signalwire_agents/skills/math/__init__.py +10 -0
  97. signalwire_agents/skills/math/skill.py +105 -0
  98. signalwire_agents/skills/mcp_gateway/README.md +230 -0
  99. signalwire_agents/skills/mcp_gateway/__init__.py +10 -0
  100. signalwire_agents/skills/mcp_gateway/skill.py +421 -0
  101. signalwire_agents/skills/native_vector_search/README.md +210 -0
  102. signalwire_agents/skills/native_vector_search/__init__.py +10 -0
  103. signalwire_agents/skills/native_vector_search/skill.py +820 -0
  104. signalwire_agents/skills/play_background_file/README.md +218 -0
  105. signalwire_agents/skills/play_background_file/__init__.py +12 -0
  106. signalwire_agents/skills/play_background_file/skill.py +242 -0
  107. signalwire_agents/skills/registry.py +459 -0
  108. signalwire_agents/skills/spider/README.md +236 -0
  109. signalwire_agents/skills/spider/__init__.py +13 -0
  110. signalwire_agents/skills/spider/skill.py +598 -0
  111. signalwire_agents/skills/swml_transfer/README.md +395 -0
  112. signalwire_agents/skills/swml_transfer/__init__.py +10 -0
  113. signalwire_agents/skills/swml_transfer/skill.py +359 -0
  114. signalwire_agents/skills/weather_api/README.md +178 -0
  115. signalwire_agents/skills/weather_api/__init__.py +12 -0
  116. signalwire_agents/skills/weather_api/skill.py +191 -0
  117. signalwire_agents/skills/web_search/README.md +163 -0
  118. signalwire_agents/skills/web_search/__init__.py +10 -0
  119. signalwire_agents/skills/web_search/skill.py +739 -0
  120. signalwire_agents/skills/wikipedia_search/README.md +228 -0
  121. signalwire_agents/{core/state → skills/wikipedia_search}/__init__.py +5 -4
  122. signalwire_agents/skills/wikipedia_search/skill.py +210 -0
  123. signalwire_agents/utils/__init__.py +14 -0
  124. signalwire_agents/utils/schema_utils.py +111 -44
  125. signalwire_agents/web/__init__.py +17 -0
  126. signalwire_agents/web/web_service.py +559 -0
  127. signalwire_agents-1.0.7.data/data/share/man/man1/sw-agent-init.1 +307 -0
  128. signalwire_agents-1.0.7.data/data/share/man/man1/sw-search.1 +483 -0
  129. signalwire_agents-1.0.7.data/data/share/man/man1/swaig-test.1 +308 -0
  130. signalwire_agents-1.0.7.dist-info/METADATA +992 -0
  131. signalwire_agents-1.0.7.dist-info/RECORD +142 -0
  132. {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/WHEEL +1 -1
  133. signalwire_agents-1.0.7.dist-info/entry_points.txt +4 -0
  134. signalwire_agents/core/state/file_state_manager.py +0 -219
  135. signalwire_agents/core/state/state_manager.py +0 -101
  136. signalwire_agents-0.1.6.data/data/schema.json +0 -5611
  137. signalwire_agents-0.1.6.dist-info/METADATA +0 -199
  138. signalwire_agents-0.1.6.dist-info/RECORD +0 -34
  139. {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/licenses/LICENSE +0 -0
  140. {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/top_level.txt +0 -0
@@ -50,6 +50,8 @@ class ConciergeAgent(AgentBase):
50
50
  hours_of_operation: Optional[Dict[str, str]] = None,
51
51
  special_instructions: Optional[List[str]] = None,
52
52
  welcome_message: Optional[str] = None,
53
+ name: str = "concierge",
54
+ route: str = "/concierge",
53
55
  **kwargs
54
56
  ):
55
57
  """
@@ -62,12 +64,14 @@ class ConciergeAgent(AgentBase):
62
64
  hours_of_operation: Optional dictionary of operating hours
63
65
  special_instructions: Optional list of special instructions
64
66
  welcome_message: Optional custom welcome message
67
+ name: Agent name for the route
68
+ route: HTTP route for this agent
65
69
  **kwargs: Additional arguments for AgentBase
66
70
  """
67
71
  # Initialize the base agent
68
72
  super().__init__(
69
- name="concierge",
70
- route="/concierge",
73
+ name=name,
74
+ route=route,
71
75
  use_pom=True,
72
76
  **kwargs
73
77
  )
@@ -9,9 +9,12 @@ See LICENSE file in the project root for full license information.
9
9
 
10
10
  """
11
11
  InfoGathererAgent - Prefab agent for collecting answers to a series of questions
12
+
13
+ Supports both static (questions provided at init) and dynamic (questions determined
14
+ by a callback function) configuration modes.
12
15
  """
13
16
 
14
- from typing import List, Dict, Any, Optional, Union
17
+ from typing import List, Dict, Any, Optional, Union, Callable
15
18
  import json
16
19
 
17
20
  from signalwire_agents.core.agent_base import AgentBase
@@ -39,7 +42,7 @@ class InfoGathererAgent(AgentBase):
39
42
 
40
43
  def __init__(
41
44
  self,
42
- questions: List[Dict[str, str]],
45
+ questions: Optional[List[Dict[str, str]]] = None,
43
46
  name: str = "info_gatherer",
44
47
  route: str = "/info_gatherer",
45
48
  **kwargs
@@ -48,7 +51,8 @@ class InfoGathererAgent(AgentBase):
48
51
  Initialize an information gathering agent
49
52
 
50
53
  Args:
51
- questions: List of questions to ask, each with:
54
+ questions: Optional list of questions to ask. If None, questions will be determined
55
+ dynamically via a callback function. Each question dict should have:
52
56
  - key_name: Identifier for storing the answer
53
57
  - question_text: The actual question to ask the user
54
58
  - confirm: (Optional) If set to True, the agent will confirm the answer before submitting
@@ -64,39 +68,84 @@ class InfoGathererAgent(AgentBase):
64
68
  **kwargs
65
69
  )
66
70
 
67
- # Validate questions format
68
- self._validate_questions(questions)
69
-
70
- # Set up global data with questions and initial state
71
- self.set_global_data({
72
- "questions": questions,
73
- "question_index": 0,
74
- "answers": []
75
- })
71
+ # Store whether we're in static or dynamic mode
72
+ self._static_questions = questions
73
+ self._question_callback = None
76
74
 
77
- # Build a minimal prompt
78
- self._build_prompt()
75
+ if questions is not None:
76
+ # Static mode: validate questions and set up immediately
77
+ self._validate_questions(questions)
78
+ self.set_global_data({
79
+ "questions": questions,
80
+ "question_index": 0,
81
+ "answers": []
82
+ })
83
+ # Build prompt for static configuration
84
+ self._build_prompt()
85
+ else:
86
+ # Dynamic mode: questions will be set up via callback in on_swml_request
87
+ # Build a generic prompt
88
+ self._build_prompt("dynamic")
79
89
 
80
90
  # Configure additional agent settings
81
91
  self._configure_agent_settings()
82
92
 
93
+ def set_question_callback(self, callback: Callable[[dict, dict, dict], List[Dict[str, str]]]):
94
+ """
95
+ Set a callback function for dynamic question configuration
96
+
97
+ Args:
98
+ callback: Function that takes (query_params, body_params, headers) and returns
99
+ a list of question dictionaries. Each question dict should have:
100
+ - key_name: Identifier for storing the answer
101
+ - question_text: The actual question to ask the user
102
+ - confirm: (Optional) If True, agent will confirm answer before submitting
103
+
104
+ Example:
105
+ def my_question_callback(query_params, body_params, headers):
106
+ question_set = query_params.get('set', 'default')
107
+ if question_set == 'support':
108
+ return [
109
+ {"key_name": "name", "question_text": "What is your name?"},
110
+ {"key_name": "issue", "question_text": "What's the issue?"}
111
+ ]
112
+ else:
113
+ return [{"key_name": "name", "question_text": "What is your name?"}]
114
+
115
+ agent.set_question_callback(my_question_callback)
116
+ """
117
+ self._question_callback = callback
118
+
83
119
  def _validate_questions(self, questions):
84
120
  """Validate that questions are in the correct format"""
85
121
  if not questions:
86
122
  raise ValueError("At least one question is required")
87
123
 
124
+ if not isinstance(questions, list):
125
+ raise ValueError("Questions must be a list")
126
+
88
127
  for i, question in enumerate(questions):
128
+ if not isinstance(question, dict):
129
+ raise ValueError(f"Question {i+1} must be a dictionary")
89
130
  if "key_name" not in question:
90
131
  raise ValueError(f"Question {i+1} is missing 'key_name' field")
91
132
  if "question_text" not in question:
92
133
  raise ValueError(f"Question {i+1} is missing 'question_text' field")
93
134
 
94
- def _build_prompt(self):
135
+ def _build_prompt(self, mode="static"):
95
136
  """Build a minimal prompt with just the objective"""
96
- self.prompt_add_section(
97
- "Objective",
98
- body="Your role is to get answers to a series of questions. Begin by asking the user if they are ready to answer some questions. If they confirm they are ready, call the start_questions function to begin the process."
99
- )
137
+ if mode == "dynamic":
138
+ # Generic prompt for dynamic mode - will be customized later
139
+ self.prompt_add_section(
140
+ "Objective",
141
+ body="Your role is to gather information by asking questions. Begin by asking the user if they are ready to answer some questions. If they confirm they are ready, call the start_questions function to begin the process."
142
+ )
143
+ else:
144
+ # Original static prompt
145
+ self.prompt_add_section(
146
+ "Objective",
147
+ body="Your role is to get answers to a series of questions. Begin by asking the user if they are ready to answer some questions. If they confirm they are ready, call the start_questions function to begin the process."
148
+ )
100
149
 
101
150
  def _configure_agent_settings(self):
102
151
  """Configure additional agent settings"""
@@ -106,6 +155,77 @@ class InfoGathererAgent(AgentBase):
106
155
  "speech_event_timeout": 1000 # Slightly longer for thoughtful responses
107
156
  })
108
157
 
158
+ def on_swml_request(self, request_data=None, callback_path=None, request=None):
159
+ """
160
+ Handle dynamic configuration using the callback function
161
+
162
+ This method is called when SWML is requested and allows us to configure
163
+ the agent just-in-time using the provided callback.
164
+ """
165
+ # Only process if we're in dynamic mode (no static questions)
166
+ if self._static_questions is not None:
167
+ return None
168
+
169
+ # If no callback is set, provide a basic fallback
170
+ if self._question_callback is None:
171
+ fallback_questions = [
172
+ {"key_name": "name", "question_text": "What is your name?"},
173
+ {"key_name": "message", "question_text": "How can I help you today?"}
174
+ ]
175
+ return {
176
+ "global_data": {
177
+ "questions": fallback_questions,
178
+ "question_index": 0,
179
+ "answers": []
180
+ }
181
+ }
182
+
183
+ # Extract request information for callback
184
+ query_params = {}
185
+ body_params = request_data or {}
186
+ headers = {}
187
+
188
+ if request and hasattr(request, 'query_params'):
189
+ query_params = dict(request.query_params)
190
+
191
+ if request and hasattr(request, 'headers'):
192
+ headers = dict(request.headers)
193
+
194
+ try:
195
+ # Call the user-provided callback to get questions
196
+ print(f"Calling question callback with query_params: {query_params}")
197
+ questions = self._question_callback(query_params, body_params, headers)
198
+ print(f"Callback returned {len(questions)} questions")
199
+
200
+ # Validate the returned questions
201
+ self._validate_questions(questions)
202
+
203
+ # Return global data modifications
204
+ return {
205
+ "global_data": {
206
+ "questions": questions,
207
+ "question_index": 0,
208
+ "answers": []
209
+ }
210
+ }
211
+
212
+ except Exception as e:
213
+ # Log error and fall back to basic questions
214
+ print(f"Error in question callback: {e}")
215
+ fallback_questions = [
216
+ {"key_name": "name", "question_text": "What is your name?"},
217
+ {"key_name": "message", "question_text": "How can I help you today?"}
218
+ ]
219
+ return {
220
+ "global_data": {
221
+ "questions": fallback_questions,
222
+ "question_index": 0,
223
+ "answers": []
224
+ }
225
+ }
226
+
227
+
228
+
109
229
  def _generate_question_instruction(self, question_text: str, needs_confirmation: bool, is_first_question: bool = False) -> str:
110
230
  """
111
231
  Generate the instruction text for asking a question
@@ -236,13 +356,11 @@ class InfoGathererAgent(AgentBase):
236
356
  # Create response with the global data update and next question
237
357
  result = SwaigFunctionResult(instruction)
238
358
 
239
- # Add actions to update global data
240
- result.add_actions([
241
- {"set_global_data": {
242
- "answers": new_answers,
243
- "question_index": new_question_index
244
- }}
245
- ])
359
+ # Use the helper method to update global data
360
+ result.update_global_data({
361
+ "answers": new_answers,
362
+ "question_index": new_question_index
363
+ })
246
364
 
247
365
  return result
248
366
  else:
@@ -251,13 +369,11 @@ class InfoGathererAgent(AgentBase):
251
369
  "Thank you! All questions have been answered. You can now summarize the information collected or ask if there's anything else the user would like to discuss."
252
370
  )
253
371
 
254
- # Add actions to update global data
255
- result.add_actions([
256
- {"set_global_data": {
257
- "answers": new_answers,
258
- "question_index": new_question_index
259
- }}
260
- ])
372
+ # Use the helper method to update global data
373
+ result.update_global_data({
374
+ "answers": new_answers,
375
+ "question_index": new_question_index
376
+ })
261
377
 
262
378
  return result
263
379
 
@@ -40,7 +40,7 @@ class ReceptionistAgent(AgentBase):
40
40
  name: str = "receptionist",
41
41
  route: str = "/receptionist",
42
42
  greeting: str = "Thank you for calling. How can I help you today?",
43
- voice: str = "elevenlabs.josh",
43
+ voice: str = "rime.spore",
44
44
  **kwargs
45
45
  ):
46
46
  """
@@ -258,28 +258,20 @@ class ReceptionistAgent(AgentBase):
258
258
  # Get transfer number
259
259
  transfer_number = department.get("number", "")
260
260
 
261
- # Create result with transfer SWML
262
- result = SwaigFunctionResult(f"I'll transfer you to our {department_name} department now. Thank you for calling, {name}!")
261
+ # Create result with transfer using the connect helper method
262
+ # post_process=True allows the AI to speak the response before executing the transfer
263
+ result = SwaigFunctionResult(
264
+ f"I'll transfer you to our {department_name} department now. Thank you for calling, {name}!",
265
+ post_process=True
266
+ )
263
267
 
264
- # Add the SWML to execute the transfer
265
- # Add actions to update global data
266
- result.add_actions([
267
- {
268
- "SWML": {
269
- "sections": {
270
- "main": [
271
- {
272
- "connect": {
273
- "to": transfer_number
274
- }
275
- }
276
- ]
277
- },
278
- "version": "1.0.0"
279
- },
280
- "transfer": "true"
281
- }
282
- ])
268
+ # Use the connect helper instead of manually constructing SWML
269
+ # final=True means this is a permanent transfer (call exits the agent)
270
+ result.connect(transfer_number, final=True)
271
+
272
+ # Alternative: Immediate transfer without AI speaking (faster but less friendly)
273
+ # result = SwaigFunctionResult() # No response text needed
274
+ # result.connect(transfer_number, final=True) # Executes immediately from function call
283
275
 
284
276
  return result
285
277
 
@@ -60,6 +60,8 @@ class SurveyAgent(AgentBase):
60
60
  conclusion: Optional[str] = None,
61
61
  brand_name: Optional[str] = None,
62
62
  max_retries: int = 2,
63
+ name: str = "survey",
64
+ route: str = "/survey",
63
65
  **kwargs
64
66
  ):
65
67
  """
@@ -78,12 +80,14 @@ class SurveyAgent(AgentBase):
78
80
  conclusion: Optional custom conclusion message
79
81
  brand_name: Optional brand or company name
80
82
  max_retries: Maximum number of times to retry invalid answers
83
+ name: Name for the agent (default: "survey")
84
+ route: HTTP route for the agent (default: "/survey")
81
85
  **kwargs: Additional arguments for AgentBase
82
86
  """
83
87
  # Initialize the base agent
84
88
  super().__init__(
85
- name="survey",
86
- route="/survey",
89
+ name=name,
90
+ route=route,
87
91
  use_pom=True,
88
92
  **kwargs
89
93
  )