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
@@ -14,7 +14,12 @@ This module provides a fluent builder API for creating SWML documents.
14
14
  It allows for chaining method calls to build up a document step by step.
15
15
  """
16
16
 
17
- from typing import Dict, List, Any, Optional, Union, Self, TypeVar
17
+ import types
18
+ from typing import Dict, List, Any, Optional, Union, TypeVar
19
+ try:
20
+ from typing import Self # Python 3.11+
21
+ except ImportError:
22
+ from typing_extensions import Self # For Python 3.9-3.10
18
23
 
19
24
  from signalwire_agents.core.swml_service import SWMLService
20
25
 
@@ -39,6 +44,12 @@ class SWMLBuilder:
39
44
  service: The SWMLService to delegate to
40
45
  """
41
46
  self.service = service
47
+
48
+ # Dictionary to cache dynamically created methods
49
+ self._verb_methods_cache = {}
50
+
51
+ # Create auto-vivified methods for all verbs
52
+ self._create_verb_methods()
42
53
 
43
54
  def answer(self, max_duration: Optional[int] = None, codecs: Optional[str] = None) -> Self:
44
55
  """
@@ -51,7 +62,12 @@ class SWMLBuilder:
51
62
  Returns:
52
63
  Self for method chaining
53
64
  """
54
- self.service.add_answer_verb(max_duration, codecs)
65
+ config = {}
66
+ if max_duration is not None:
67
+ config["max_duration"] = max_duration
68
+ if codecs is not None:
69
+ config["codecs"] = codecs
70
+ self.service.add_verb("answer", config)
55
71
  return self
56
72
 
57
73
  def hangup(self, reason: Optional[str] = None) -> Self:
@@ -64,7 +80,10 @@ class SWMLBuilder:
64
80
  Returns:
65
81
  Self for method chaining
66
82
  """
67
- self.service.add_hangup_verb(reason)
83
+ config = {}
84
+ if reason is not None:
85
+ config["reason"] = reason
86
+ self.service.add_verb("hangup", config)
68
87
  return self
69
88
 
70
89
  def ai(self,
@@ -88,14 +107,26 @@ class SWMLBuilder:
88
107
  Returns:
89
108
  Self for method chaining
90
109
  """
91
- self.service.add_ai_verb(
92
- prompt_text=prompt_text,
93
- prompt_pom=prompt_pom,
94
- post_prompt=post_prompt,
95
- post_prompt_url=post_prompt_url,
96
- swaig=swaig,
97
- **kwargs
98
- )
110
+ config = {}
111
+
112
+ # Handle prompt (either text or POM, but not both)
113
+ if prompt_text is not None:
114
+ config["prompt"] = prompt_text
115
+ elif prompt_pom is not None:
116
+ config["prompt"] = prompt_pom
117
+
118
+ # Add optional parameters
119
+ if post_prompt is not None:
120
+ config["post_prompt"] = post_prompt
121
+ if post_prompt_url is not None:
122
+ config["post_prompt_url"] = post_prompt_url
123
+ if swaig is not None:
124
+ config["SWAIG"] = swaig
125
+
126
+ # Add any additional kwargs
127
+ config.update(kwargs)
128
+
129
+ self.service.add_verb("ai", config)
99
130
  return self
100
131
 
101
132
  def play(self, url: Optional[str] = None, urls: Optional[List[str]] = None,
@@ -211,4 +242,173 @@ class SWMLBuilder:
211
242
  Self for method chaining
212
243
  """
213
244
  self.service.reset_document()
214
- return self
245
+ return self
246
+
247
+ def _create_verb_methods(self) -> None:
248
+ """
249
+ Create auto-vivified methods for all verbs at initialization time
250
+
251
+ This creates methods for all SWML verbs defined in the schema,
252
+ allowing for fluent chaining like builder.denoise().goto().play()
253
+ """
254
+ # Get all verb names from the schema
255
+ if not hasattr(self.service, 'schema_utils') or not self.service.schema_utils:
256
+ return
257
+
258
+ verb_names = self.service.schema_utils.get_all_verb_names()
259
+
260
+ # Create a method for each verb
261
+ for verb_name in verb_names:
262
+ # Skip verbs that already have specific methods
263
+ if hasattr(self, verb_name):
264
+ continue
265
+
266
+ # Handle sleep verb specially since it takes an integer directly
267
+ if verb_name == "sleep":
268
+ def sleep_method(self_instance, duration=None, **kwargs):
269
+ """
270
+ Add the sleep verb to the document.
271
+
272
+ Args:
273
+ duration: The amount of time to sleep in milliseconds
274
+ """
275
+ # Sleep verb takes a direct integer parameter in SWML
276
+ if duration is not None:
277
+ self_instance.service.add_verb("sleep", duration)
278
+ elif kwargs:
279
+ # Try to get the value from kwargs
280
+ self_instance.service.add_verb("sleep", next(iter(kwargs.values())))
281
+ else:
282
+ raise TypeError("sleep() missing required argument: 'duration'")
283
+ return self_instance
284
+
285
+ # Set it as an attribute of self
286
+ setattr(self, verb_name, types.MethodType(sleep_method, self))
287
+
288
+ # Also cache it for later
289
+ self._verb_methods_cache[verb_name] = sleep_method
290
+ continue
291
+
292
+ # Generate the method implementation for normal verbs
293
+ def make_verb_method(name):
294
+ def verb_method(self_instance, **kwargs):
295
+ """
296
+ Dynamically generated method for SWML verb - returns self for chaining
297
+ """
298
+ config = {}
299
+ for key, value in kwargs.items():
300
+ if value is not None:
301
+ config[key] = value
302
+ self_instance.service.add_verb(name, config)
303
+ return self_instance
304
+
305
+ # Add docstring to the method
306
+ if hasattr(self.service.schema_utils, 'get_verb_properties'):
307
+ verb_properties = self.service.schema_utils.get_verb_properties(name)
308
+ if "description" in verb_properties:
309
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\n{verb_properties['description']}\n\nReturns: Self for method chaining"
310
+ else:
311
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\nReturns: Self for method chaining"
312
+ else:
313
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\nReturns: Self for method chaining"
314
+
315
+ return verb_method
316
+
317
+ # Create the method with closure over the verb name
318
+ method = make_verb_method(verb_name)
319
+
320
+ # Set it as an attribute of self
321
+ setattr(self, verb_name, types.MethodType(method, self))
322
+
323
+ # Also cache it for later
324
+ self._verb_methods_cache[verb_name] = method
325
+
326
+ def __getattr__(self, name: str) -> Any:
327
+ """
328
+ Dynamically generate and return SWML verb methods when accessed
329
+
330
+ This method is called when an attribute lookup fails through the normal
331
+ mechanisms. It checks if the attribute name corresponds to a SWML verb
332
+ defined in the schema, and if so, dynamically creates a method for that verb.
333
+
334
+ Args:
335
+ name: The name of the attribute being accessed
336
+
337
+ Returns:
338
+ The dynamically created verb method if name is a valid SWML verb,
339
+ otherwise raises AttributeError
340
+
341
+ Raises:
342
+ AttributeError: If name is not a valid SWML verb
343
+ """
344
+ # First check if this is a valid SWML verb
345
+ if not hasattr(self.service, 'schema_utils') or not self.service.schema_utils:
346
+ msg = f"'{self.__class__.__name__}' object has no attribute '{name}' (no schema available)"
347
+ raise AttributeError(msg)
348
+
349
+ verb_names = self.service.schema_utils.get_all_verb_names()
350
+
351
+ if name in verb_names:
352
+ # Check if we already have this method in the cache
353
+ if not hasattr(self, '_verb_methods_cache'):
354
+ self._verb_methods_cache = {}
355
+
356
+ if name in self._verb_methods_cache:
357
+ return types.MethodType(self._verb_methods_cache[name], self)
358
+
359
+ # Handle sleep verb specially since it takes an integer directly
360
+ if name == "sleep":
361
+ def sleep_method(self_instance, duration=None, **kwargs):
362
+ """
363
+ Add the sleep verb to the document.
364
+
365
+ Args:
366
+ duration: The amount of time to sleep in milliseconds
367
+ """
368
+ # Sleep verb takes a direct integer parameter in SWML
369
+ if duration is not None:
370
+ self_instance.service.add_verb("sleep", duration)
371
+ elif kwargs:
372
+ # Try to get the value from kwargs
373
+ self_instance.service.add_verb("sleep", next(iter(kwargs.values())))
374
+ else:
375
+ raise TypeError("sleep() missing required argument: 'duration'")
376
+ return self_instance
377
+
378
+ # Cache the method for future use
379
+ self._verb_methods_cache[name] = sleep_method
380
+
381
+ # Return the bound method
382
+ return types.MethodType(sleep_method, self)
383
+
384
+ # Generate the method implementation for normal verbs
385
+ def verb_method(self_instance, **kwargs):
386
+ """
387
+ Dynamically generated method for SWML verb - returns self for chaining
388
+ """
389
+ config = {}
390
+ for key, value in kwargs.items():
391
+ if value is not None:
392
+ config[key] = value
393
+ self_instance.service.add_verb(name, config)
394
+ return self_instance
395
+
396
+ # Add docstring to the method
397
+ if hasattr(self.service.schema_utils, 'get_verb_properties'):
398
+ verb_properties = self.service.schema_utils.get_verb_properties(name)
399
+ if "description" in verb_properties:
400
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\n{verb_properties['description']}\n\nReturns: Self for method chaining"
401
+ else:
402
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\nReturns: Self for method chaining"
403
+ else:
404
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\nReturns: Self for method chaining"
405
+
406
+ # Cache the method for future use
407
+ self._verb_methods_cache[name] = verb_method
408
+
409
+ # Return the bound method
410
+ return types.MethodType(verb_method, self)
411
+
412
+ # Not a valid verb
413
+ msg = f"'{self.__class__.__name__}' object has no attribute '{name}'"
414
+ raise AttributeError(msg)
@@ -93,17 +93,33 @@ class AIVerbHandler(SWMLVerbHandler):
93
93
  """
94
94
  errors = []
95
95
 
96
- # Check required fields
96
+ # Check that prompt is present
97
97
  if "prompt" not in config:
98
98
  errors.append("Missing required field 'prompt'")
99
+ return False, errors
99
100
 
100
- # Validate prompt structure if present
101
- if "prompt" in config:
102
- prompt = config["prompt"]
103
- if not isinstance(prompt, dict):
104
- errors.append("'prompt' must be an object")
105
- elif "text" not in prompt and "pom" not in prompt:
106
- errors.append("'prompt' must contain either 'text' or 'pom'")
101
+ prompt = config["prompt"]
102
+ if not isinstance(prompt, dict):
103
+ errors.append("'prompt' must be an object")
104
+ return False, errors
105
+
106
+ # Check that prompt contains either text or pom (required)
107
+ has_text = "text" in prompt
108
+ has_pom = "pom" in prompt
109
+ has_contexts = "contexts" in prompt
110
+
111
+ # Require either text or pom (mutually exclusive)
112
+ base_prompt_count = sum([has_text, has_pom])
113
+ if base_prompt_count == 0:
114
+ errors.append("'prompt' must contain either 'text' or 'pom' as base prompt")
115
+ elif base_prompt_count > 1:
116
+ errors.append("'prompt' can only contain one of: 'text' or 'pom' (mutually exclusive)")
117
+
118
+ # Contexts are optional and can be combined with text or pom
119
+ if has_contexts:
120
+ contexts = prompt["contexts"]
121
+ if not isinstance(contexts, dict):
122
+ errors.append("'prompt.contexts' must be an object")
107
123
 
108
124
  # Validate SWAIG structure if present
109
125
  if "SWAIG" in config:
@@ -116,6 +132,7 @@ class AIVerbHandler(SWMLVerbHandler):
116
132
  def build_config(self,
117
133
  prompt_text: Optional[str] = None,
118
134
  prompt_pom: Optional[List[Dict[str, Any]]] = None,
135
+ contexts: Optional[Dict[str, Any]] = None,
119
136
  post_prompt: Optional[str] = None,
120
137
  post_prompt_url: Optional[str] = None,
121
138
  swaig: Optional[Dict[str, Any]] = None,
@@ -126,6 +143,7 @@ class AIVerbHandler(SWMLVerbHandler):
126
143
  Args:
127
144
  prompt_text: Text prompt for the AI (mutually exclusive with prompt_pom)
128
145
  prompt_pom: POM structure for the AI prompt (mutually exclusive with prompt_text)
146
+ contexts: Optional contexts and steps configuration (can be combined with text or pom)
129
147
  post_prompt: Optional post-prompt text
130
148
  post_prompt_url: Optional URL for post-prompt processing
131
149
  swaig: Optional SWAIG configuration
@@ -136,13 +154,25 @@ class AIVerbHandler(SWMLVerbHandler):
136
154
  """
137
155
  config = {}
138
156
 
139
- # Add prompt (either text or POM)
157
+ # Require either text or pom as base prompt (mutually exclusive)
158
+ base_prompt_count = sum(x is not None for x in [prompt_text, prompt_pom])
159
+ if base_prompt_count == 0:
160
+ raise ValueError("Either prompt_text or prompt_pom must be provided as base prompt")
161
+ elif base_prompt_count > 1:
162
+ raise ValueError("prompt_text and prompt_pom are mutually exclusive")
163
+
164
+ # Build prompt object with base prompt
165
+ prompt_config = {}
140
166
  if prompt_text is not None:
141
- config["prompt"] = {"text": prompt_text}
167
+ prompt_config["text"] = prompt_text
142
168
  elif prompt_pom is not None:
143
- config["prompt"] = {"pom": prompt_pom}
144
- else:
145
- raise ValueError("Either prompt_text or prompt_pom must be provided")
169
+ prompt_config["pom"] = prompt_pom
170
+
171
+ # Add contexts if provided (optional, activates steps feature)
172
+ if contexts is not None:
173
+ prompt_config["contexts"] = contexts
174
+
175
+ config["prompt"] = prompt_config
146
176
 
147
177
  # Add post-prompt if provided
148
178
  if post_prompt is not None: