signalwire-agents 0.1.20__py3-none-any.whl → 0.1.23__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.
- signalwire_agents/__init__.py +1 -1
- signalwire_agents/agent_server.py +50 -11
- signalwire_agents/core/__init__.py +2 -2
- signalwire_agents/core/agent/__init__.py +14 -0
- signalwire_agents/core/agent/config/__init__.py +14 -0
- signalwire_agents/core/agent/config/ephemeral.py +176 -0
- signalwire_agents/core/agent/deployment/__init__.py +0 -0
- signalwire_agents/core/agent/deployment/handlers/__init__.py +0 -0
- signalwire_agents/core/agent/prompt/__init__.py +14 -0
- signalwire_agents/core/agent/prompt/manager.py +288 -0
- signalwire_agents/core/agent/routing/__init__.py +0 -0
- signalwire_agents/core/agent/security/__init__.py +0 -0
- signalwire_agents/core/agent/swml/__init__.py +0 -0
- signalwire_agents/core/agent/tools/__init__.py +15 -0
- signalwire_agents/core/agent/tools/decorator.py +95 -0
- signalwire_agents/core/agent/tools/registry.py +192 -0
- signalwire_agents/core/agent_base.py +131 -413
- signalwire_agents/core/data_map.py +3 -15
- signalwire_agents/core/skill_manager.py +0 -17
- signalwire_agents/core/swaig_function.py +0 -2
- signalwire_agents/core/swml_builder.py +207 -11
- signalwire_agents/core/swml_renderer.py +123 -312
- signalwire_agents/core/swml_service.py +25 -94
- signalwire_agents/search/index_builder.py +1 -1
- signalwire_agents/skills/api_ninjas_trivia/__init__.py +3 -0
- signalwire_agents/skills/api_ninjas_trivia/skill.py +192 -0
- signalwire_agents/skills/play_background_file/__init__.py +3 -0
- signalwire_agents/skills/play_background_file/skill.py +197 -0
- signalwire_agents/skills/weather_api/__init__.py +3 -0
- signalwire_agents/skills/weather_api/skill.py +154 -0
- {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/METADATA +5 -8
- {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/RECORD +37 -18
- {signalwire_agents-0.1.20.data → signalwire_agents-0.1.23.data}/data/schema.json +0 -0
- {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/entry_points.txt +0 -0
- {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/top_level.txt +0 -0
@@ -257,7 +257,6 @@ class DataMap:
|
|
257
257
|
|
258
258
|
Args:
|
259
259
|
foreach_config: Either:
|
260
|
-
- String: JSON path to array in response (deprecated, kept for compatibility)
|
261
260
|
- Dict: Foreach configuration with keys:
|
262
261
|
- input_key: Key in API response containing the array
|
263
262
|
- output_key: Name for the built string variable
|
@@ -278,20 +277,7 @@ class DataMap:
|
|
278
277
|
if not self._webhooks:
|
279
278
|
raise ValueError("Must add webhook before setting foreach")
|
280
279
|
|
281
|
-
if isinstance(foreach_config,
|
282
|
-
# Legacy support - convert string to basic foreach config
|
283
|
-
# Extract the key from "${response.key}" format if present
|
284
|
-
if foreach_config.startswith("${response.") and foreach_config.endswith("}"):
|
285
|
-
key = foreach_config[12:-1] # Remove "${response." and "}"
|
286
|
-
else:
|
287
|
-
key = foreach_config
|
288
|
-
|
289
|
-
foreach_data = {
|
290
|
-
"input_key": key,
|
291
|
-
"output_key": "foreach_output",
|
292
|
-
"append": "${this}"
|
293
|
-
}
|
294
|
-
else:
|
280
|
+
if isinstance(foreach_config, dict):
|
295
281
|
# New format - validate required keys
|
296
282
|
required_keys = ["input_key", "output_key", "append"]
|
297
283
|
missing_keys = [key for key in required_keys if key not in foreach_config]
|
@@ -299,6 +285,8 @@ class DataMap:
|
|
299
285
|
raise ValueError(f"foreach config missing required keys: {missing_keys}")
|
300
286
|
|
301
287
|
foreach_data = foreach_config
|
288
|
+
else:
|
289
|
+
raise ValueError("foreach_config must be a dictionary")
|
302
290
|
|
303
291
|
self._webhooks[-1]["foreach"] = foreach_data
|
304
292
|
return self
|
@@ -134,13 +134,6 @@ class SkillManager:
|
|
134
134
|
if skill_identifier in self.loaded_skills:
|
135
135
|
instance_key = skill_identifier
|
136
136
|
skill_instance = self.loaded_skills[skill_identifier]
|
137
|
-
else:
|
138
|
-
# Try to find by skill name (for backwards compatibility)
|
139
|
-
for key, instance in self.loaded_skills.items():
|
140
|
-
if instance.SKILL_NAME == skill_identifier:
|
141
|
-
instance_key = key
|
142
|
-
skill_instance = instance
|
143
|
-
break
|
144
137
|
|
145
138
|
if skill_instance is None:
|
146
139
|
self.logger.warning(f"Skill '{skill_identifier}' is not loaded")
|
@@ -173,11 +166,6 @@ class SkillManager:
|
|
173
166
|
if skill_identifier in self.loaded_skills:
|
174
167
|
return True
|
175
168
|
|
176
|
-
# Try to find by skill name (for backwards compatibility)
|
177
|
-
for instance in self.loaded_skills.values():
|
178
|
-
if instance.SKILL_NAME == skill_identifier:
|
179
|
-
return True
|
180
|
-
|
181
169
|
return False
|
182
170
|
|
183
171
|
def get_skill(self, skill_identifier: str) -> Optional[SkillBase]:
|
@@ -194,9 +182,4 @@ class SkillManager:
|
|
194
182
|
if skill_identifier in self.loaded_skills:
|
195
183
|
return self.loaded_skills[skill_identifier]
|
196
184
|
|
197
|
-
# Try to find by skill name (for backwards compatibility)
|
198
|
-
for instance in self.loaded_skills.values():
|
199
|
-
if instance.SKILL_NAME == skill_identifier:
|
200
|
-
return instance
|
201
|
-
|
202
185
|
return None
|
@@ -14,6 +14,7 @@ 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
|
+
import types
|
17
18
|
from typing import Dict, List, Any, Optional, Union, TypeVar
|
18
19
|
try:
|
19
20
|
from typing import Self # Python 3.11+
|
@@ -43,6 +44,12 @@ class SWMLBuilder:
|
|
43
44
|
service: The SWMLService to delegate to
|
44
45
|
"""
|
45
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()
|
46
53
|
|
47
54
|
def answer(self, max_duration: Optional[int] = None, codecs: Optional[str] = None) -> Self:
|
48
55
|
"""
|
@@ -55,7 +62,12 @@ class SWMLBuilder:
|
|
55
62
|
Returns:
|
56
63
|
Self for method chaining
|
57
64
|
"""
|
58
|
-
|
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)
|
59
71
|
return self
|
60
72
|
|
61
73
|
def hangup(self, reason: Optional[str] = None) -> Self:
|
@@ -68,7 +80,10 @@ class SWMLBuilder:
|
|
68
80
|
Returns:
|
69
81
|
Self for method chaining
|
70
82
|
"""
|
71
|
-
|
83
|
+
config = {}
|
84
|
+
if reason is not None:
|
85
|
+
config["reason"] = reason
|
86
|
+
self.service.add_verb("hangup", config)
|
72
87
|
return self
|
73
88
|
|
74
89
|
def ai(self,
|
@@ -92,14 +107,26 @@ class SWMLBuilder:
|
|
92
107
|
Returns:
|
93
108
|
Self for method chaining
|
94
109
|
"""
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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)
|
103
130
|
return self
|
104
131
|
|
105
132
|
def play(self, url: Optional[str] = None, urls: Optional[List[str]] = None,
|
@@ -215,4 +242,173 @@ class SWMLBuilder:
|
|
215
242
|
Self for method chaining
|
216
243
|
"""
|
217
244
|
self.service.reset_document()
|
218
|
-
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)
|