signalwire-agents 0.1.23__py3-none-any.whl → 0.1.24__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 (64) hide show
  1. signalwire_agents/__init__.py +1 -1
  2. signalwire_agents/agent_server.py +2 -1
  3. signalwire_agents/cli/config.py +61 -0
  4. signalwire_agents/cli/core/__init__.py +1 -0
  5. signalwire_agents/cli/core/agent_loader.py +254 -0
  6. signalwire_agents/cli/core/argparse_helpers.py +164 -0
  7. signalwire_agents/cli/core/dynamic_config.py +62 -0
  8. signalwire_agents/cli/execution/__init__.py +1 -0
  9. signalwire_agents/cli/execution/datamap_exec.py +437 -0
  10. signalwire_agents/cli/execution/webhook_exec.py +125 -0
  11. signalwire_agents/cli/output/__init__.py +1 -0
  12. signalwire_agents/cli/output/output_formatter.py +132 -0
  13. signalwire_agents/cli/output/swml_dump.py +177 -0
  14. signalwire_agents/cli/simulation/__init__.py +1 -0
  15. signalwire_agents/cli/simulation/data_generation.py +365 -0
  16. signalwire_agents/cli/simulation/data_overrides.py +187 -0
  17. signalwire_agents/cli/simulation/mock_env.py +271 -0
  18. signalwire_agents/cli/test_swaig.py +522 -2539
  19. signalwire_agents/cli/types.py +72 -0
  20. signalwire_agents/core/agent/__init__.py +1 -3
  21. signalwire_agents/core/agent/config/__init__.py +1 -3
  22. signalwire_agents/core/agent/prompt/manager.py +25 -7
  23. signalwire_agents/core/agent/tools/decorator.py +2 -0
  24. signalwire_agents/core/agent/tools/registry.py +8 -0
  25. signalwire_agents/core/agent_base.py +492 -3053
  26. signalwire_agents/core/function_result.py +31 -42
  27. signalwire_agents/core/mixins/__init__.py +28 -0
  28. signalwire_agents/core/mixins/ai_config_mixin.py +373 -0
  29. signalwire_agents/core/mixins/auth_mixin.py +287 -0
  30. signalwire_agents/core/mixins/prompt_mixin.py +345 -0
  31. signalwire_agents/core/mixins/serverless_mixin.py +368 -0
  32. signalwire_agents/core/mixins/skill_mixin.py +55 -0
  33. signalwire_agents/core/mixins/state_mixin.py +219 -0
  34. signalwire_agents/core/mixins/tool_mixin.py +295 -0
  35. signalwire_agents/core/mixins/web_mixin.py +1130 -0
  36. signalwire_agents/core/skill_manager.py +3 -1
  37. signalwire_agents/core/swaig_function.py +10 -1
  38. signalwire_agents/core/swml_service.py +140 -58
  39. signalwire_agents/skills/README.md +452 -0
  40. signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
  41. signalwire_agents/skills/datasphere/README.md +210 -0
  42. signalwire_agents/skills/datasphere_serverless/README.md +258 -0
  43. signalwire_agents/skills/datetime/README.md +132 -0
  44. signalwire_agents/skills/joke/README.md +149 -0
  45. signalwire_agents/skills/math/README.md +161 -0
  46. signalwire_agents/skills/native_vector_search/skill.py +33 -13
  47. signalwire_agents/skills/play_background_file/README.md +218 -0
  48. signalwire_agents/skills/spider/README.md +236 -0
  49. signalwire_agents/skills/spider/__init__.py +4 -0
  50. signalwire_agents/skills/spider/skill.py +479 -0
  51. signalwire_agents/skills/swml_transfer/README.md +395 -0
  52. signalwire_agents/skills/swml_transfer/__init__.py +1 -0
  53. signalwire_agents/skills/swml_transfer/skill.py +257 -0
  54. signalwire_agents/skills/weather_api/README.md +178 -0
  55. signalwire_agents/skills/web_search/README.md +163 -0
  56. signalwire_agents/skills/wikipedia_search/README.md +228 -0
  57. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/METADATA +47 -2
  58. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/RECORD +62 -22
  59. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/entry_points.txt +1 -1
  60. signalwire_agents/core/agent/config/ephemeral.py +0 -176
  61. signalwire_agents-0.1.23.data/data/schema.json +0 -5611
  62. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/WHEEL +0 -0
  63. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/licenses/LICENSE +0 -0
  64. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,295 @@
1
+ """
2
+ Copyright (c) 2025 SignalWire
3
+
4
+ This file is part of the SignalWire AI Agents SDK.
5
+
6
+ Licensed under the MIT License.
7
+ See LICENSE file in the project root for full license information.
8
+ """
9
+
10
+ from typing import Dict, Any, List, Optional, Callable
11
+ import json
12
+
13
+ from signalwire_agents.core.swaig_function import SWAIGFunction
14
+ from signalwire_agents.core.function_result import SwaigFunctionResult
15
+ from signalwire_agents.core.agent.tools.decorator import ToolDecorator
16
+
17
+
18
+ class ToolMixin:
19
+ """
20
+ Mixin class containing all tool/function-related methods for AgentBase
21
+ """
22
+
23
+ def define_tool(
24
+ self,
25
+ name: str,
26
+ description: str,
27
+ parameters: Dict[str, Any],
28
+ handler: Callable,
29
+ secure: bool = True,
30
+ fillers: Optional[Dict[str, List[str]]] = None,
31
+ webhook_url: Optional[str] = None,
32
+ required: Optional[List[str]] = None,
33
+ **swaig_fields
34
+ ) -> 'AgentBase':
35
+ """
36
+ Define a SWAIG function that the AI can call
37
+
38
+ Args:
39
+ name: Function name (must be unique)
40
+ description: Function description for the AI
41
+ parameters: JSON Schema of parameters
42
+ handler: Function to call when invoked
43
+ secure: Whether to require token validation
44
+ fillers: Optional dict mapping language codes to arrays of filler phrases
45
+ webhook_url: Optional external webhook URL to use instead of local handling
46
+ required: Optional list of required parameter names
47
+ **swaig_fields: Additional SWAIG fields to include in function definition
48
+
49
+ Returns:
50
+ Self for method chaining
51
+ """
52
+ self._tool_registry.define_tool(
53
+ name=name,
54
+ description=description,
55
+ parameters=parameters,
56
+ handler=handler,
57
+ secure=secure,
58
+ fillers=fillers,
59
+ webhook_url=webhook_url,
60
+ required=required,
61
+ **swaig_fields
62
+ )
63
+ return self
64
+
65
+ def register_swaig_function(self, function_dict: Dict[str, Any]) -> 'AgentBase':
66
+ """
67
+ Register a raw SWAIG function dictionary (e.g., from DataMap.to_swaig_function())
68
+
69
+ Args:
70
+ function_dict: Complete SWAIG function definition dictionary
71
+
72
+ Returns:
73
+ Self for method chaining
74
+ """
75
+ self._tool_registry.register_swaig_function(function_dict)
76
+ return self
77
+
78
+ def _tool_decorator(self, name=None, **kwargs):
79
+ """
80
+ Decorator for defining SWAIG tools in a class
81
+
82
+ Used as:
83
+
84
+ @agent.tool(name="example_function", parameters={...})
85
+ def example_function(self, param1):
86
+ # ...
87
+ """
88
+ return ToolDecorator.create_instance_decorator(self._tool_registry)(name, **kwargs)
89
+
90
+ @classmethod
91
+ def tool(cls, name=None, **kwargs):
92
+ """
93
+ Class method decorator for defining SWAIG tools
94
+
95
+ Used as:
96
+
97
+ @AgentBase.tool(name="example_function", parameters={...})
98
+ def example_function(self, param1):
99
+ # ...
100
+ """
101
+ return ToolDecorator.create_class_decorator()(name, **kwargs)
102
+
103
+ def define_tools(self) -> List[SWAIGFunction]:
104
+ """
105
+ Define the tools this agent can use
106
+
107
+ Returns:
108
+ List of SWAIGFunction objects or raw dictionaries (for data_map tools)
109
+
110
+ This method can be overridden by subclasses.
111
+ """
112
+ tools = []
113
+ for func in self._tool_registry._swaig_functions.values():
114
+ if isinstance(func, dict):
115
+ # Raw dictionary from register_swaig_function (e.g., DataMap)
116
+ tools.append(func)
117
+ else:
118
+ # SWAIGFunction object from define_tool
119
+ tools.append(func)
120
+ return tools
121
+
122
+ def on_function_call(self, name: str, args: Dict[str, Any], raw_data: Optional[Dict[str, Any]] = None) -> Any:
123
+ """
124
+ Called when a SWAIG function is invoked
125
+
126
+ Args:
127
+ name: Function name
128
+ args: Function arguments
129
+ raw_data: Raw request data
130
+
131
+ Returns:
132
+ Function result
133
+ """
134
+ # Check if the function is registered
135
+ if name not in self._tool_registry._swaig_functions:
136
+ # If the function is not found, return an error
137
+ return {"response": f"Function '{name}' not found"}
138
+
139
+ # Get the function
140
+ func = self._tool_registry._swaig_functions[name]
141
+
142
+ # Check if this is a data_map function (raw dictionary)
143
+ if isinstance(func, dict):
144
+ # Data_map functions execute on SignalWire's server, not here
145
+ # This should never be called, but if it is, return an error
146
+ return {"response": f"Data map function '{name}' should be executed by SignalWire server, not locally"}
147
+
148
+ # Check if this is an external webhook function
149
+ if hasattr(func, 'webhook_url') and func.webhook_url:
150
+ # External webhook functions should be called directly by SignalWire, not locally
151
+ return {"response": f"External webhook function '{name}' should be executed by SignalWire at {func.webhook_url}, not locally"}
152
+
153
+ # Call the handler for regular SWAIG functions
154
+ try:
155
+ result = func.handler(args, raw_data)
156
+ if result is None:
157
+ # If the handler returns None, create a default response
158
+ result = SwaigFunctionResult("Function executed successfully")
159
+ return result
160
+ except Exception as e:
161
+ # If the handler raises an exception, return an error response
162
+ return {"response": f"Error executing function '{name}': {str(e)}"}
163
+
164
+ def _register_state_tracking_tools(self):
165
+ """
166
+ Register special tools for state tracking
167
+
168
+ This adds startup_hook and hangup_hook SWAIG functions that automatically
169
+ activate and deactivate the session when called. These are useful for
170
+ tracking call state and cleaning up resources when a call ends.
171
+ """
172
+ # Register startup hook to activate session
173
+ self.define_tool(
174
+ name="startup_hook",
175
+ description="Called when a new conversation starts to initialize state",
176
+ parameters={},
177
+ handler=lambda args, raw_data: self._handle_startup_hook(args, raw_data),
178
+ secure=False # No auth needed for this system function
179
+ )
180
+
181
+ # Register hangup hook to end session
182
+ self.define_tool(
183
+ name="hangup_hook",
184
+ description="Called when conversation ends to clean up resources",
185
+ parameters={},
186
+ handler=lambda args, raw_data: self._handle_hangup_hook(args, raw_data),
187
+ secure=False # No auth needed for this system function
188
+ )
189
+
190
+ def _handle_startup_hook(self, args, raw_data):
191
+ """
192
+ Handle the startup hook function call
193
+
194
+ Args:
195
+ args: Function arguments (empty for this hook)
196
+ raw_data: Raw request data containing call_id
197
+
198
+ Returns:
199
+ Success response
200
+ """
201
+ call_id = raw_data.get("call_id") if raw_data else None
202
+ if call_id:
203
+ self.log.info("session_activated", call_id=call_id)
204
+ self._session_manager.activate_session(call_id)
205
+ return SwaigFunctionResult("Session activated")
206
+ else:
207
+ self.log.warning("session_activation_failed", error="No call_id provided")
208
+ return SwaigFunctionResult("Failed to activate session: No call_id provided")
209
+
210
+ def _handle_hangup_hook(self, args, raw_data):
211
+ """
212
+ Handle the hangup hook function call
213
+
214
+ Args:
215
+ args: Function arguments (empty for this hook)
216
+ raw_data: Raw request data containing call_id
217
+
218
+ Returns:
219
+ Success response
220
+ """
221
+ call_id = raw_data.get("call_id") if raw_data else None
222
+ if call_id:
223
+ self.log.info("session_ended", call_id=call_id)
224
+ self._session_manager.end_session(call_id)
225
+ return SwaigFunctionResult("Session ended")
226
+ else:
227
+ self.log.warning("session_end_failed", error="No call_id provided")
228
+ return SwaigFunctionResult("Failed to end session: No call_id provided")
229
+
230
+ def _execute_swaig_function(self, function_name: str, args: Optional[Dict[str, Any]] = None, call_id: Optional[str] = None, raw_data: Optional[Dict[str, Any]] = None):
231
+ """
232
+ Execute a SWAIG function in serverless context
233
+
234
+ Args:
235
+ function_name: Name of the function to execute
236
+ args: Function arguments dictionary
237
+ call_id: Optional call ID
238
+ raw_data: Optional raw request data
239
+
240
+ Returns:
241
+ Function execution result
242
+ """
243
+ # Use the existing logger
244
+ req_log = self.log.bind(
245
+ endpoint="serverless_swaig",
246
+ function=function_name
247
+ )
248
+
249
+ if call_id:
250
+ req_log = req_log.bind(call_id=call_id)
251
+
252
+ req_log.debug("serverless_function_call_received")
253
+
254
+ try:
255
+ # Validate function exists
256
+ if function_name not in self._tool_registry._swaig_functions:
257
+ req_log.warning("function_not_found", available_functions=list(self._tool_registry._swaig_functions.keys()))
258
+ return {"error": f"Function '{function_name}' not found"}
259
+
260
+ # Use empty args if not provided
261
+ if args is None:
262
+ args = {}
263
+
264
+ # Use empty raw_data if not provided, but include function call structure
265
+ if raw_data is None:
266
+ raw_data = {
267
+ "function": function_name,
268
+ "argument": {
269
+ "parsed": [args] if args else [],
270
+ "raw": json.dumps(args) if args else "{}"
271
+ }
272
+ }
273
+ if call_id:
274
+ raw_data["call_id"] = call_id
275
+
276
+ req_log.debug("executing_function", args=json.dumps(args))
277
+
278
+ # Call the function using the existing on_function_call method
279
+ result = self.on_function_call(function_name, args, raw_data)
280
+
281
+ # Convert result to dict if needed (same logic as in _handle_swaig_request)
282
+ if isinstance(result, SwaigFunctionResult):
283
+ result_dict = result.to_dict()
284
+ elif isinstance(result, dict):
285
+ result_dict = result
286
+ else:
287
+ result_dict = {"response": str(result)}
288
+
289
+ req_log.info("serverless_function_executed_successfully")
290
+ req_log.debug("function_result", result=json.dumps(result_dict))
291
+ return result_dict
292
+
293
+ except Exception as e:
294
+ req_log.error("serverless_function_execution_error", error=str(e))
295
+ return {"error": str(e), "function": function_name}