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.
- signalwire_agents/__init__.py +1 -1
- signalwire_agents/agent_server.py +2 -1
- signalwire_agents/cli/config.py +61 -0
- signalwire_agents/cli/core/__init__.py +1 -0
- signalwire_agents/cli/core/agent_loader.py +254 -0
- signalwire_agents/cli/core/argparse_helpers.py +164 -0
- signalwire_agents/cli/core/dynamic_config.py +62 -0
- signalwire_agents/cli/execution/__init__.py +1 -0
- signalwire_agents/cli/execution/datamap_exec.py +437 -0
- signalwire_agents/cli/execution/webhook_exec.py +125 -0
- signalwire_agents/cli/output/__init__.py +1 -0
- signalwire_agents/cli/output/output_formatter.py +132 -0
- signalwire_agents/cli/output/swml_dump.py +177 -0
- signalwire_agents/cli/simulation/__init__.py +1 -0
- signalwire_agents/cli/simulation/data_generation.py +365 -0
- signalwire_agents/cli/simulation/data_overrides.py +187 -0
- signalwire_agents/cli/simulation/mock_env.py +271 -0
- signalwire_agents/cli/test_swaig.py +522 -2539
- signalwire_agents/cli/types.py +72 -0
- signalwire_agents/core/agent/__init__.py +1 -3
- signalwire_agents/core/agent/config/__init__.py +1 -3
- signalwire_agents/core/agent/prompt/manager.py +25 -7
- signalwire_agents/core/agent/tools/decorator.py +2 -0
- signalwire_agents/core/agent/tools/registry.py +8 -0
- signalwire_agents/core/agent_base.py +492 -3053
- signalwire_agents/core/function_result.py +31 -42
- signalwire_agents/core/mixins/__init__.py +28 -0
- signalwire_agents/core/mixins/ai_config_mixin.py +373 -0
- signalwire_agents/core/mixins/auth_mixin.py +287 -0
- signalwire_agents/core/mixins/prompt_mixin.py +345 -0
- signalwire_agents/core/mixins/serverless_mixin.py +368 -0
- signalwire_agents/core/mixins/skill_mixin.py +55 -0
- signalwire_agents/core/mixins/state_mixin.py +219 -0
- signalwire_agents/core/mixins/tool_mixin.py +295 -0
- signalwire_agents/core/mixins/web_mixin.py +1130 -0
- signalwire_agents/core/skill_manager.py +3 -1
- signalwire_agents/core/swaig_function.py +10 -1
- signalwire_agents/core/swml_service.py +140 -58
- signalwire_agents/skills/README.md +452 -0
- signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
- signalwire_agents/skills/datasphere/README.md +210 -0
- signalwire_agents/skills/datasphere_serverless/README.md +258 -0
- signalwire_agents/skills/datetime/README.md +132 -0
- signalwire_agents/skills/joke/README.md +149 -0
- signalwire_agents/skills/math/README.md +161 -0
- signalwire_agents/skills/native_vector_search/skill.py +33 -13
- signalwire_agents/skills/play_background_file/README.md +218 -0
- signalwire_agents/skills/spider/README.md +236 -0
- signalwire_agents/skills/spider/__init__.py +4 -0
- signalwire_agents/skills/spider/skill.py +479 -0
- signalwire_agents/skills/swml_transfer/README.md +395 -0
- signalwire_agents/skills/swml_transfer/__init__.py +1 -0
- signalwire_agents/skills/swml_transfer/skill.py +257 -0
- signalwire_agents/skills/weather_api/README.md +178 -0
- signalwire_agents/skills/web_search/README.md +163 -0
- signalwire_agents/skills/wikipedia_search/README.md +228 -0
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/METADATA +47 -2
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/RECORD +62 -22
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/entry_points.txt +1 -1
- signalwire_agents/core/agent/config/ephemeral.py +0 -176
- signalwire_agents-0.1.23.data/data/schema.json +0 -5611
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/licenses/LICENSE +0 -0
- {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}
|