signalwire-agents 0.1.32__py3-none-any.whl → 0.1.33__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/cli/core/agent_loader.py +320 -113
- signalwire_agents/cli/core/argparse_helpers.py +7 -1
- signalwire_agents/cli/core/service_loader.py +303 -0
- signalwire_agents/cli/output/output_formatter.py +104 -2
- signalwire_agents/cli/output/swml_dump.py +13 -13
- signalwire_agents/cli/simulation/mock_env.py +2 -0
- signalwire_agents/cli/swaig_test_wrapper.py +52 -0
- signalwire_agents/cli/test_swaig.py +20 -6
- {signalwire_agents-0.1.32.dist-info → signalwire_agents-0.1.33.dist-info}/METADATA +84 -99
- {signalwire_agents-0.1.32.dist-info → signalwire_agents-0.1.33.dist-info}/RECORD +15 -13
- {signalwire_agents-0.1.32.dist-info → signalwire_agents-0.1.33.dist-info}/entry_points.txt +1 -1
- {signalwire_agents-0.1.32.dist-info → signalwire_agents-0.1.33.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.32.dist-info → signalwire_agents-0.1.33.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.32.dist-info → signalwire_agents-0.1.33.dist-info}/top_level.txt +0 -0
signalwire_agents/__init__.py
CHANGED
@@ -18,7 +18,7 @@ A package for building AI agents using SignalWire's AI and SWML capabilities.
|
|
18
18
|
from .core.logging_config import configure_logging
|
19
19
|
configure_logging()
|
20
20
|
|
21
|
-
__version__ = "0.1.
|
21
|
+
__version__ = "0.1.33"
|
22
22
|
|
23
23
|
# Import core classes for easier access
|
24
24
|
from .core.agent_base import AgentBase
|
@@ -19,96 +19,175 @@ from typing import List, Dict, Any, Optional
|
|
19
19
|
# Import after checking if available
|
20
20
|
try:
|
21
21
|
from signalwire_agents.core.agent_base import AgentBase
|
22
|
+
from signalwire_agents.core.swml_service import SWMLService
|
23
|
+
# Import the new service loader
|
24
|
+
from .service_loader import ServiceCapture, load_agent_from_file as new_load_agent
|
22
25
|
AGENT_BASE_AVAILABLE = True
|
26
|
+
SWML_SERVICE_AVAILABLE = True
|
27
|
+
NEW_LOADER_AVAILABLE = True
|
23
28
|
except ImportError:
|
24
29
|
AgentBase = None
|
30
|
+
SWMLService = None
|
31
|
+
ServiceCapture = None
|
32
|
+
new_load_agent = None
|
25
33
|
AGENT_BASE_AVAILABLE = False
|
34
|
+
SWML_SERVICE_AVAILABLE = False
|
35
|
+
NEW_LOADER_AVAILABLE = False
|
26
36
|
|
27
37
|
|
28
|
-
def
|
38
|
+
def discover_services_in_file(service_path: str) -> List[Dict[str, Any]]:
|
29
39
|
"""
|
30
|
-
Discover all available agents in a Python file without instantiating them
|
40
|
+
Discover all available SWML services (including agents) in a Python file without instantiating them
|
31
41
|
|
32
42
|
Args:
|
33
|
-
|
43
|
+
service_path: Path to the Python file containing services
|
34
44
|
|
35
45
|
Returns:
|
36
|
-
List of dictionaries with
|
46
|
+
List of dictionaries with service information
|
37
47
|
|
38
48
|
Raises:
|
39
49
|
ImportError: If the file cannot be imported
|
40
50
|
FileNotFoundError: If the file doesn't exist
|
41
51
|
"""
|
42
|
-
if not
|
43
|
-
raise ImportError("
|
52
|
+
if not SWML_SERVICE_AVAILABLE:
|
53
|
+
raise ImportError("SWMLService not available. Please install signalwire-agents package.")
|
54
|
+
|
55
|
+
# Keep backward compatibility
|
56
|
+
return _discover_services_impl(service_path)
|
57
|
+
|
58
|
+
|
59
|
+
def discover_agents_in_file(agent_path: str) -> List[Dict[str, Any]]:
|
60
|
+
"""
|
61
|
+
Backward compatibility wrapper - discovers agents in a file
|
62
|
+
|
63
|
+
Args:
|
64
|
+
agent_path: Path to the Python file containing agents
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
List of dictionaries with agent information
|
68
|
+
"""
|
69
|
+
# Filter to only return AgentBase instances/classes
|
70
|
+
all_services = discover_services_in_file(agent_path)
|
71
|
+
return [s for s in all_services if s.get('is_agent', False)]
|
72
|
+
|
73
|
+
|
74
|
+
def _discover_services_impl(service_path: str) -> List[Dict[str, Any]]:
|
75
|
+
"""
|
76
|
+
Internal implementation for discovering services
|
77
|
+
"""
|
78
|
+
service_path = Path(service_path).resolve()
|
44
79
|
|
45
|
-
|
80
|
+
if not service_path.exists():
|
81
|
+
raise FileNotFoundError(f"Service file not found: {service_path}")
|
46
82
|
|
47
|
-
if not
|
48
|
-
raise
|
83
|
+
if not service_path.suffix == '.py':
|
84
|
+
raise ValueError(f"Service file must be a Python file (.py): {service_path}")
|
49
85
|
|
50
|
-
|
51
|
-
|
86
|
+
# Add the module's directory to sys.path temporarily to allow local imports
|
87
|
+
import sys
|
88
|
+
module_dir = str(service_path.parent)
|
89
|
+
sys_path_added = False
|
90
|
+
if module_dir not in sys.path:
|
91
|
+
sys.path.insert(0, module_dir)
|
92
|
+
sys_path_added = True
|
52
93
|
|
53
94
|
# Load the module, but prevent main() execution by setting __name__ to something other than "__main__"
|
54
|
-
spec = importlib.util.spec_from_file_location("
|
95
|
+
spec = importlib.util.spec_from_file_location("service_module", service_path)
|
55
96
|
module = importlib.util.module_from_spec(spec)
|
56
97
|
|
57
98
|
try:
|
58
99
|
# Set __name__ to prevent if __name__ == "__main__": blocks from running
|
59
|
-
module.__name__ = "
|
100
|
+
module.__name__ = "service_module"
|
60
101
|
spec.loader.exec_module(module)
|
61
102
|
except Exception as e:
|
62
|
-
|
103
|
+
# Clean up sys.path if we added to it
|
104
|
+
if sys_path_added:
|
105
|
+
sys.path.remove(module_dir)
|
106
|
+
raise ImportError(f"Failed to load service module: {e}")
|
107
|
+
finally:
|
108
|
+
# Clean up sys.path after successful load too
|
109
|
+
if sys_path_added and module_dir in sys.path:
|
110
|
+
sys.path.remove(module_dir)
|
63
111
|
|
64
|
-
|
112
|
+
services_found = []
|
65
113
|
|
66
|
-
# Look for AgentBase
|
114
|
+
# Look for SWMLService instances (including AgentBase which inherits from it)
|
67
115
|
for name, obj in vars(module).items():
|
68
|
-
if isinstance(obj,
|
69
|
-
|
116
|
+
if isinstance(obj, SWMLService):
|
117
|
+
is_agent = isinstance(obj, AgentBase) if AgentBase else False
|
118
|
+
services_found.append({
|
70
119
|
'name': name,
|
71
120
|
'class_name': obj.__class__.__name__,
|
72
121
|
'type': 'instance',
|
73
|
-
'
|
122
|
+
'service_name': getattr(obj, 'name', 'Unknown'),
|
74
123
|
'route': getattr(obj, 'route', 'Unknown'),
|
75
124
|
'description': obj.__class__.__doc__,
|
76
|
-
'object': obj
|
125
|
+
'object': obj,
|
126
|
+
'is_agent': is_agent,
|
127
|
+
'has_tools': is_agent and hasattr(obj, '_tool_registry')
|
77
128
|
})
|
78
129
|
|
79
|
-
# Look for
|
130
|
+
# Look for SWMLService subclasses (that could be instantiated)
|
80
131
|
for name, obj in vars(module).items():
|
81
132
|
if (isinstance(obj, type) and
|
82
|
-
issubclass(obj,
|
83
|
-
obj
|
133
|
+
issubclass(obj, SWMLService) and
|
134
|
+
obj not in (SWMLService, AgentBase)): # Don't include base classes
|
84
135
|
# Check if we already found an instance of this class
|
85
|
-
instance_found = any(
|
136
|
+
instance_found = any(service['class_name'] == name for service in services_found)
|
86
137
|
if not instance_found:
|
138
|
+
is_agent = AgentBase and issubclass(obj, AgentBase)
|
87
139
|
try:
|
88
140
|
# Try to get class information without instantiating
|
89
|
-
|
141
|
+
service_info = {
|
90
142
|
'name': name,
|
91
143
|
'class_name': name,
|
92
144
|
'type': 'class',
|
93
|
-
'
|
145
|
+
'service_name': 'Unknown (not instantiated)',
|
94
146
|
'route': 'Unknown (not instantiated)',
|
95
147
|
'description': obj.__doc__,
|
96
|
-
'object': obj
|
148
|
+
'object': obj,
|
149
|
+
'is_agent': is_agent,
|
150
|
+
'has_tools': False # Can't determine without instantiation
|
97
151
|
}
|
98
|
-
|
152
|
+
services_found.append(service_info)
|
99
153
|
except Exception:
|
100
154
|
# If we can't get info, still record that the class exists
|
101
|
-
|
155
|
+
services_found.append({
|
102
156
|
'name': name,
|
103
157
|
'class_name': name,
|
104
158
|
'type': 'class',
|
105
|
-
'
|
159
|
+
'service_name': 'Unknown (not instantiated)',
|
106
160
|
'route': 'Unknown (not instantiated)',
|
107
161
|
'description': obj.__doc__ or 'No description available',
|
108
|
-
'object': obj
|
162
|
+
'object': obj,
|
163
|
+
'is_agent': is_agent,
|
164
|
+
'has_tools': False
|
109
165
|
})
|
110
166
|
|
111
|
-
return
|
167
|
+
return services_found
|
168
|
+
|
169
|
+
|
170
|
+
def load_service_from_file(service_path: str, service_identifier: Optional[str] = None, prefer_route: bool = True) -> 'SWMLService':
|
171
|
+
"""
|
172
|
+
Load a SWML service from a Python file
|
173
|
+
|
174
|
+
Args:
|
175
|
+
service_path: Path to the Python file containing the service
|
176
|
+
service_identifier: Optional service identifier - can be class name or route
|
177
|
+
prefer_route: If True, interpret identifier as route first, then class name
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
SWMLService instance (could be AgentBase or basic SWMLService)
|
181
|
+
|
182
|
+
Raises:
|
183
|
+
ImportError: If the file cannot be imported
|
184
|
+
ValueError: If no service is found in the file
|
185
|
+
"""
|
186
|
+
if not SWML_SERVICE_AVAILABLE:
|
187
|
+
raise ImportError("SWMLService not available. Please install signalwire-agents package.")
|
188
|
+
|
189
|
+
# Use the main implementation
|
190
|
+
return _load_service_impl(service_path, service_identifier, prefer_route)
|
112
191
|
|
113
192
|
|
114
193
|
def load_agent_from_file(agent_path: str, agent_class_name: Optional[str] = None) -> 'AgentBase':
|
@@ -129,135 +208,263 @@ def load_agent_from_file(agent_path: str, agent_class_name: Optional[str] = None
|
|
129
208
|
if not AGENT_BASE_AVAILABLE:
|
130
209
|
raise ImportError("AgentBase not available. Please install signalwire-agents package.")
|
131
210
|
|
132
|
-
|
211
|
+
# Check if we're being called from swaig-test --dump-swml (or similar)
|
212
|
+
# We can detect this by checking the call stack or environment
|
213
|
+
import inspect
|
214
|
+
suppress_output = False
|
215
|
+
|
216
|
+
# Check if we're in a context where output should be suppressed
|
217
|
+
frame = inspect.currentframe()
|
218
|
+
try:
|
219
|
+
# Walk up the call stack to see if we're being called from dump_swml
|
220
|
+
while frame:
|
221
|
+
if 'dump_swml' in frame.f_code.co_filename or 'swml_dump' in frame.f_code.co_filename:
|
222
|
+
suppress_output = True
|
223
|
+
break
|
224
|
+
frame = frame.f_back
|
225
|
+
finally:
|
226
|
+
del frame # Avoid reference cycles
|
227
|
+
|
228
|
+
# Use the old implementation but with a fix for the ordering
|
229
|
+
return _load_service_impl(agent_path, agent_class_name, prefer_route=False)
|
230
|
+
|
231
|
+
|
232
|
+
def _load_service_impl(service_path: str, service_identifier: Optional[str] = None, prefer_route: bool = True) -> 'SWMLService':
|
233
|
+
"""
|
234
|
+
Internal implementation for loading services with smart detection
|
235
|
+
"""
|
236
|
+
service_path = Path(service_path).resolve()
|
237
|
+
|
238
|
+
if not service_path.exists():
|
239
|
+
raise FileNotFoundError(f"Service file not found: {service_path}")
|
133
240
|
|
134
|
-
if not
|
135
|
-
raise
|
241
|
+
if not service_path.suffix == '.py':
|
242
|
+
raise ValueError(f"Service file must be a Python file (.py): {service_path}")
|
136
243
|
|
137
|
-
|
138
|
-
|
244
|
+
# Add the module's directory to sys.path temporarily to allow local imports
|
245
|
+
import sys
|
246
|
+
module_dir = str(service_path.parent)
|
247
|
+
sys_path_added = False
|
248
|
+
if module_dir not in sys.path:
|
249
|
+
sys.path.insert(0, module_dir)
|
250
|
+
sys_path_added = True
|
139
251
|
|
140
252
|
# Load the module, but prevent main() execution by setting __name__ to something other than "__main__"
|
141
|
-
spec = importlib.util.spec_from_file_location("
|
253
|
+
spec = importlib.util.spec_from_file_location("service_module", service_path)
|
142
254
|
module = importlib.util.module_from_spec(spec)
|
143
255
|
|
144
256
|
try:
|
145
257
|
# Set __name__ to prevent if __name__ == "__main__": blocks from running
|
146
|
-
module.__name__ = "
|
258
|
+
module.__name__ = "service_module"
|
147
259
|
spec.loader.exec_module(module)
|
148
260
|
except Exception as e:
|
149
|
-
|
261
|
+
# Clean up sys.path if we added to it
|
262
|
+
if sys_path_added:
|
263
|
+
sys.path.remove(module_dir)
|
264
|
+
raise ImportError(f"Failed to load service module: {e}")
|
265
|
+
finally:
|
266
|
+
# Clean up sys.path after successful load too
|
267
|
+
if sys_path_added and module_dir in sys.path:
|
268
|
+
sys.path.remove(module_dir)
|
150
269
|
|
151
|
-
# Find the
|
152
|
-
|
270
|
+
# Find the service instance
|
271
|
+
service = None
|
153
272
|
|
154
|
-
# If
|
155
|
-
if
|
156
|
-
|
157
|
-
|
158
|
-
if isinstance(obj,
|
273
|
+
# If service_identifier is specified and prefer_route is True, try to find by route first
|
274
|
+
if service_identifier and prefer_route:
|
275
|
+
# First, try to find an existing instance with matching route
|
276
|
+
for name, obj in vars(module).items():
|
277
|
+
if isinstance(obj, SWMLService) and hasattr(obj, 'route'):
|
278
|
+
if obj.route == service_identifier:
|
279
|
+
service = obj
|
280
|
+
break
|
281
|
+
|
282
|
+
# If not found, try instantiating classes to check their routes
|
283
|
+
if service is None:
|
284
|
+
for name, obj in vars(module).items():
|
285
|
+
if (isinstance(obj, type) and
|
286
|
+
issubclass(obj, SWMLService) and
|
287
|
+
obj not in (SWMLService, AgentBase)):
|
288
|
+
try:
|
289
|
+
temp_instance = obj()
|
290
|
+
if hasattr(temp_instance, 'route') and temp_instance.route == service_identifier:
|
291
|
+
service = temp_instance
|
292
|
+
break
|
293
|
+
except Exception:
|
294
|
+
# Skip classes that can't be instantiated
|
295
|
+
pass
|
296
|
+
|
297
|
+
# If still not found and service_identifier looks like a class name, fall back to class name
|
298
|
+
if service is None and hasattr(module, service_identifier):
|
299
|
+
obj = getattr(module, service_identifier)
|
300
|
+
if isinstance(obj, type) and issubclass(obj, SWMLService):
|
159
301
|
try:
|
160
|
-
|
161
|
-
|
162
|
-
|
302
|
+
service = obj()
|
303
|
+
except Exception as e:
|
304
|
+
raise ValueError(f"No service found with route '{service_identifier}' and failed to instantiate class '{service_identifier}': {e}")
|
305
|
+
elif isinstance(obj, SWMLService):
|
306
|
+
service = obj
|
307
|
+
|
308
|
+
if service is None:
|
309
|
+
raise ValueError(f"No service found with route '{service_identifier}'")
|
310
|
+
|
311
|
+
# If service_identifier is specified as a class name, try to instantiate that specific class first
|
312
|
+
elif service_identifier and not prefer_route:
|
313
|
+
if hasattr(module, service_identifier):
|
314
|
+
obj = getattr(module, service_identifier)
|
315
|
+
if isinstance(obj, type) and issubclass(obj, SWMLService):
|
316
|
+
try:
|
317
|
+
service = obj()
|
318
|
+
if service and not service.route.endswith('dummy'): # Avoid test services with dummy routes
|
319
|
+
pass # Successfully created specific service
|
163
320
|
else:
|
164
|
-
|
321
|
+
service = obj() # Create anyway if requested specifically
|
165
322
|
except Exception as e:
|
166
|
-
raise ValueError(f"Failed to instantiate
|
323
|
+
raise ValueError(f"Failed to instantiate service class '{service_identifier}': {e}")
|
324
|
+
elif isinstance(obj, SWMLService):
|
325
|
+
# It's already an instance
|
326
|
+
service = obj
|
167
327
|
else:
|
168
|
-
raise ValueError(f"'{
|
328
|
+
raise ValueError(f"'{service_identifier}' is not a valid SWMLService class or instance")
|
169
329
|
else:
|
170
|
-
raise ValueError(f"
|
330
|
+
raise ValueError(f"Service class '{service_identifier}' not found in {service_path}")
|
171
331
|
|
172
|
-
# Strategy 1: Look for 'agent' variable (most common pattern)
|
173
|
-
if
|
174
|
-
agent
|
332
|
+
# Strategy 1: Look for 'agent' or 'service' variable (most common pattern)
|
333
|
+
if service is None:
|
334
|
+
if hasattr(module, 'agent') and isinstance(module.agent, SWMLService):
|
335
|
+
service = module.agent
|
336
|
+
elif hasattr(module, 'service') and isinstance(module.service, SWMLService):
|
337
|
+
service = module.service
|
175
338
|
|
176
|
-
# Strategy 2: Look for any
|
177
|
-
if
|
178
|
-
|
339
|
+
# Strategy 2: Look for any SWMLService instance in module globals
|
340
|
+
if service is None:
|
341
|
+
services_found = []
|
179
342
|
for name, obj in vars(module).items():
|
180
|
-
if isinstance(obj,
|
181
|
-
|
343
|
+
if isinstance(obj, SWMLService):
|
344
|
+
services_found.append((name, obj))
|
182
345
|
|
183
|
-
if len(
|
184
|
-
|
185
|
-
elif len(
|
186
|
-
# Multiple
|
187
|
-
for name, obj in
|
188
|
-
if name
|
189
|
-
|
346
|
+
if len(services_found) == 1:
|
347
|
+
service = services_found[0][1]
|
348
|
+
elif len(services_found) > 1:
|
349
|
+
# Multiple services found, prefer one named 'agent' or 'service'
|
350
|
+
for name, obj in services_found:
|
351
|
+
if name in ('agent', 'service'):
|
352
|
+
service = obj
|
190
353
|
break
|
191
|
-
# If no
|
192
|
-
if
|
193
|
-
|
194
|
-
print(f"Warning: Multiple
|
195
|
-
print(f"Hint: Use --
|
196
|
-
|
197
|
-
# Strategy 3: Look for
|
198
|
-
if
|
199
|
-
|
354
|
+
# If no preferred variable, use the first one
|
355
|
+
if service is None:
|
356
|
+
service = services_found[0][1]
|
357
|
+
print(f"Warning: Multiple services found, using '{services_found[0][0]}'")
|
358
|
+
print(f"Hint: Use --route or service identifier to choose specific service")
|
359
|
+
|
360
|
+
# Strategy 3: Look for SWMLService subclass and try to instantiate it
|
361
|
+
if service is None and not hasattr(module, 'main'):
|
362
|
+
service_classes_found = []
|
200
363
|
for name, obj in vars(module).items():
|
201
364
|
if (isinstance(obj, type) and
|
202
|
-
issubclass(obj,
|
203
|
-
obj
|
204
|
-
|
365
|
+
issubclass(obj, SWMLService) and
|
366
|
+
obj not in (SWMLService, AgentBase)):
|
367
|
+
service_classes_found.append((name, obj))
|
205
368
|
|
206
|
-
if len(
|
369
|
+
if len(service_classes_found) == 1:
|
207
370
|
try:
|
208
|
-
|
371
|
+
service = service_classes_found[0][1]()
|
209
372
|
except Exception as e:
|
210
|
-
print(f"Warning: Failed to instantiate {
|
211
|
-
elif len(
|
212
|
-
# Multiple
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
373
|
+
print(f"Warning: Failed to instantiate {service_classes_found[0][0]}: {e}")
|
374
|
+
elif len(service_classes_found) > 1:
|
375
|
+
# Multiple service classes found - get detailed info
|
376
|
+
service_info = []
|
377
|
+
for name, cls in service_classes_found:
|
378
|
+
try:
|
379
|
+
# Try to instantiate temporarily to get route
|
380
|
+
temp_instance = cls()
|
381
|
+
route = getattr(temp_instance, 'route', 'Unknown')
|
382
|
+
service_name = getattr(temp_instance, 'name', 'Unknown')
|
383
|
+
service_info.append({
|
384
|
+
'class_name': name,
|
385
|
+
'route': route,
|
386
|
+
'service_name': service_name
|
387
|
+
})
|
388
|
+
except Exception:
|
389
|
+
# If instantiation fails, still include the class
|
390
|
+
service_info.append({
|
391
|
+
'class_name': name,
|
392
|
+
'route': 'Unknown (instantiation failed)',
|
393
|
+
'service_name': 'Unknown'
|
394
|
+
})
|
395
|
+
|
396
|
+
# Format error message with service details
|
397
|
+
error_lines = ["Multiple service classes found:"]
|
398
|
+
error_lines.append("")
|
399
|
+
for info in service_info:
|
400
|
+
error_lines.append(f" {info['class_name']}:")
|
401
|
+
error_lines.append(f" Route: {info['route']}")
|
402
|
+
error_lines.append(f" Name: {info['service_name']}")
|
403
|
+
error_lines.append("")
|
404
|
+
error_lines.append("Please specify which service to use:")
|
405
|
+
error_lines.append(f" swaig-test {service_path} --agent-class <ClassName>")
|
406
|
+
error_lines.append(f" swaig-test {service_path} --route <route>")
|
407
|
+
|
408
|
+
raise ValueError("\n".join(error_lines))
|
217
409
|
else:
|
218
|
-
# Try instantiating any
|
410
|
+
# Try instantiating any SWMLService class we can find
|
219
411
|
for name, obj in vars(module).items():
|
220
412
|
if (isinstance(obj, type) and
|
221
|
-
issubclass(obj,
|
222
|
-
obj
|
413
|
+
issubclass(obj, SWMLService) and
|
414
|
+
obj not in (SWMLService, AgentBase)):
|
223
415
|
try:
|
224
|
-
|
416
|
+
service = obj()
|
225
417
|
break
|
226
418
|
except Exception as e:
|
227
419
|
print(f"Warning: Failed to instantiate {name}: {e}")
|
228
420
|
|
229
421
|
# Strategy 4: Try calling a modified main() function that doesn't start the server
|
230
|
-
if
|
422
|
+
if service is None and hasattr(module, 'main'):
|
231
423
|
print("Warning: No agent instance found, attempting to call main() without server startup")
|
232
424
|
try:
|
233
|
-
# Temporarily patch
|
234
|
-
|
235
|
-
|
425
|
+
# Temporarily patch serve() and run() on both SWMLService and AgentBase
|
426
|
+
captured_services = []
|
427
|
+
patches_applied = []
|
236
428
|
|
237
429
|
def mock_serve(self, *args, **kwargs):
|
238
|
-
|
239
|
-
print(f" (Intercepted serve() call,
|
430
|
+
captured_services.append(self)
|
431
|
+
print(f" (Intercepted serve() call, service captured for testing)")
|
432
|
+
return self
|
433
|
+
|
434
|
+
def mock_run(self, *args, **kwargs):
|
435
|
+
captured_services.append(self)
|
436
|
+
print(f" (Intercepted run() call, service captured for testing)")
|
240
437
|
return self
|
241
438
|
|
242
|
-
|
439
|
+
# Apply patches to both base classes
|
440
|
+
for base_class in [SWMLService, AgentBase]:
|
441
|
+
if base_class:
|
442
|
+
if hasattr(base_class, 'serve'):
|
443
|
+
patches_applied.append((base_class, 'serve', base_class.serve))
|
444
|
+
base_class.serve = mock_serve
|
445
|
+
if hasattr(base_class, 'run'):
|
446
|
+
patches_applied.append((base_class, 'run', base_class.run))
|
447
|
+
base_class.run = mock_run
|
243
448
|
|
244
449
|
try:
|
245
450
|
result = module.main()
|
246
|
-
if isinstance(result,
|
247
|
-
|
248
|
-
elif
|
249
|
-
|
451
|
+
if isinstance(result, SWMLService):
|
452
|
+
service = result
|
453
|
+
elif captured_services:
|
454
|
+
# Use the last captured service (most likely the configured one)
|
455
|
+
service = captured_services[-1]
|
250
456
|
finally:
|
251
|
-
# Restore original
|
252
|
-
|
457
|
+
# Restore original methods
|
458
|
+
for base_class, method_name, original_method in patches_applied:
|
459
|
+
setattr(base_class, method_name, original_method)
|
253
460
|
|
254
461
|
except Exception as e:
|
255
462
|
print(f"Warning: Failed to call main() function: {e}")
|
256
463
|
|
257
|
-
if
|
258
|
-
raise ValueError(f"No
|
259
|
-
f"-
|
260
|
-
f"-
|
261
|
-
f"- A main() function that creates and returns
|
464
|
+
if service is None:
|
465
|
+
raise ValueError(f"No service found in {service_path}. The file must contain either:\n"
|
466
|
+
f"- A SWMLService instance (e.g., agent = MyAgent() or service = MyService())\n"
|
467
|
+
f"- A SWMLService subclass that can be instantiated\n"
|
468
|
+
f"- A main() function that creates and returns a service")
|
262
469
|
|
263
|
-
return
|
470
|
+
return service
|
@@ -144,7 +144,13 @@ def parse_function_arguments(function_args_list: List[str], func_schema: Dict[st
|
|
144
144
|
else:
|
145
145
|
# Need a value
|
146
146
|
if i + 1 >= len(function_args_list):
|
147
|
-
|
147
|
+
# Check if this looks like a CLI flag that was misplaced
|
148
|
+
if param_name in ['verbose', 'raw', 'help', 'list-tools', 'list-agents', 'dump-swml',
|
149
|
+
'minimal', 'fake-full-data', 'simulate-serverless', 'agent-class', 'route']:
|
150
|
+
raise ValueError(f"CLI flag --{param_name} must come BEFORE --exec, not after.\n"
|
151
|
+
f"Example: swaig-test file.py --{param_name} --exec function_name")
|
152
|
+
else:
|
153
|
+
raise ValueError(f"Parameter --{param_name} requires a value")
|
148
154
|
|
149
155
|
value = function_args_list[i + 1]
|
150
156
|
|