signalwire-agents 0.1.11__py3-none-any.whl → 0.1.13__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 +5 -1
- signalwire_agents/agent_server.py +222 -13
- signalwire_agents/cli/build_search.py +457 -0
- signalwire_agents/cli/test_swaig.py +177 -113
- signalwire_agents/core/agent_base.py +1 -3
- signalwire_agents/core/logging_config.py +232 -0
- signalwire_agents/core/swaig_function.py +2 -3
- signalwire_agents/core/swml_renderer.py +43 -28
- signalwire_agents/search/__init__.py +131 -0
- signalwire_agents/search/document_processor.py +764 -0
- signalwire_agents/search/index_builder.py +534 -0
- signalwire_agents/search/query_processor.py +371 -0
- signalwire_agents/search/search_engine.py +383 -0
- signalwire_agents/search/search_service.py +251 -0
- signalwire_agents/skills/native_vector_search/__init__.py +1 -0
- signalwire_agents/skills/native_vector_search/skill.py +352 -0
- signalwire_agents/skills/registry.py +2 -15
- signalwire_agents/utils/__init__.py +13 -1
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.13.dist-info}/METADATA +110 -3
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.13.dist-info}/RECORD +25 -16
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.13.dist-info}/entry_points.txt +1 -0
- signalwire_agents/utils/serverless.py +0 -38
- {signalwire_agents-0.1.11.data → signalwire_agents-0.1.13.data}/data/schema.json +0 -0
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.13.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.13.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,232 @@
|
|
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
|
+
"""
|
11
|
+
Central logging configuration for SignalWire Agents SDK
|
12
|
+
|
13
|
+
This module provides a single point of control for all logging across the SDK
|
14
|
+
and applications built with it. All components should use get_logger() instead
|
15
|
+
of direct logging module usage or print() statements.
|
16
|
+
|
17
|
+
The StructuredLoggerWrapper provides backward compatibility with existing
|
18
|
+
structured logging calls (e.g., log.info("message", key=value)) while using
|
19
|
+
standard Python logging underneath. This allows the entire codebase to work
|
20
|
+
without changes while providing centralized logging control.
|
21
|
+
"""
|
22
|
+
|
23
|
+
import logging
|
24
|
+
import os
|
25
|
+
import sys
|
26
|
+
from typing import Optional, Any, Dict
|
27
|
+
|
28
|
+
# Global flag to ensure configuration only happens once
|
29
|
+
_logging_configured = False
|
30
|
+
|
31
|
+
|
32
|
+
class StructuredLoggerWrapper:
|
33
|
+
"""
|
34
|
+
A wrapper that provides structured logging interface while using standard Python logging
|
35
|
+
|
36
|
+
This allows existing structured logging calls to work without changes while
|
37
|
+
giving us centralized control over logging behavior.
|
38
|
+
"""
|
39
|
+
|
40
|
+
def __init__(self, logger: logging.Logger):
|
41
|
+
self._logger = logger
|
42
|
+
|
43
|
+
def _format_structured_message(self, message: str, **kwargs) -> str:
|
44
|
+
"""Format a message with structured keyword arguments"""
|
45
|
+
if not kwargs:
|
46
|
+
return message
|
47
|
+
|
48
|
+
# Convert kwargs to readable string format
|
49
|
+
parts = []
|
50
|
+
for key, value in kwargs.items():
|
51
|
+
# Handle different value types appropriately
|
52
|
+
if isinstance(value, str):
|
53
|
+
parts.append(f"{key}={value}")
|
54
|
+
elif isinstance(value, (list, dict)):
|
55
|
+
parts.append(f"{key}={str(value)}")
|
56
|
+
else:
|
57
|
+
parts.append(f"{key}={value}")
|
58
|
+
|
59
|
+
if parts:
|
60
|
+
return f"{message} ({', '.join(parts)})"
|
61
|
+
else:
|
62
|
+
return message
|
63
|
+
|
64
|
+
def debug(self, message: str, **kwargs) -> None:
|
65
|
+
"""Log debug message with optional structured data"""
|
66
|
+
formatted = self._format_structured_message(message, **kwargs)
|
67
|
+
self._logger.debug(formatted)
|
68
|
+
|
69
|
+
def info(self, message: str, **kwargs) -> None:
|
70
|
+
"""Log info message with optional structured data"""
|
71
|
+
formatted = self._format_structured_message(message, **kwargs)
|
72
|
+
self._logger.info(formatted)
|
73
|
+
|
74
|
+
def warning(self, message: str, **kwargs) -> None:
|
75
|
+
"""Log warning message with optional structured data"""
|
76
|
+
formatted = self._format_structured_message(message, **kwargs)
|
77
|
+
self._logger.warning(formatted)
|
78
|
+
|
79
|
+
def error(self, message: str, **kwargs) -> None:
|
80
|
+
"""Log error message with optional structured data"""
|
81
|
+
formatted = self._format_structured_message(message, **kwargs)
|
82
|
+
self._logger.error(formatted)
|
83
|
+
|
84
|
+
def critical(self, message: str, **kwargs) -> None:
|
85
|
+
"""Log critical message with optional structured data"""
|
86
|
+
formatted = self._format_structured_message(message, **kwargs)
|
87
|
+
self._logger.critical(formatted)
|
88
|
+
|
89
|
+
# Also support the 'warn' alias
|
90
|
+
warn = warning
|
91
|
+
|
92
|
+
# Support direct access to underlying logger attributes if needed
|
93
|
+
def __getattr__(self, name: str) -> Any:
|
94
|
+
"""Delegate any unknown attributes to the underlying logger"""
|
95
|
+
return getattr(self._logger, name)
|
96
|
+
|
97
|
+
|
98
|
+
def get_execution_mode() -> str:
|
99
|
+
"""
|
100
|
+
Determine the execution mode based on environment variables
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
'cgi' if running in CGI mode
|
104
|
+
'lambda' if running in AWS Lambda
|
105
|
+
'server' for normal server mode
|
106
|
+
"""
|
107
|
+
if os.getenv('GATEWAY_INTERFACE'):
|
108
|
+
return 'cgi'
|
109
|
+
if os.getenv('AWS_LAMBDA_FUNCTION_NAME') or os.getenv('LAMBDA_TASK_ROOT'):
|
110
|
+
return 'lambda'
|
111
|
+
return 'server'
|
112
|
+
|
113
|
+
|
114
|
+
def configure_logging():
|
115
|
+
"""
|
116
|
+
Configure logging system once, globally, based on environment variables
|
117
|
+
|
118
|
+
Environment Variables:
|
119
|
+
SIGNALWIRE_LOG_MODE: off, stderr, default, auto
|
120
|
+
SIGNALWIRE_LOG_LEVEL: debug, info, warning, error, critical
|
121
|
+
"""
|
122
|
+
global _logging_configured
|
123
|
+
|
124
|
+
if _logging_configured:
|
125
|
+
return
|
126
|
+
|
127
|
+
# Get configuration from environment
|
128
|
+
log_mode = os.getenv('SIGNALWIRE_LOG_MODE', '').lower()
|
129
|
+
log_level = os.getenv('SIGNALWIRE_LOG_LEVEL', 'info').lower()
|
130
|
+
|
131
|
+
# Determine log mode if auto or not specified
|
132
|
+
if not log_mode or log_mode == 'auto':
|
133
|
+
execution_mode = get_execution_mode()
|
134
|
+
if execution_mode == 'cgi':
|
135
|
+
log_mode = 'off'
|
136
|
+
else:
|
137
|
+
log_mode = 'default'
|
138
|
+
|
139
|
+
# Configure based on mode
|
140
|
+
if log_mode == 'off':
|
141
|
+
_configure_off_mode()
|
142
|
+
elif log_mode == 'stderr':
|
143
|
+
_configure_stderr_mode(log_level)
|
144
|
+
else: # default mode
|
145
|
+
_configure_default_mode(log_level)
|
146
|
+
|
147
|
+
_logging_configured = True
|
148
|
+
|
149
|
+
|
150
|
+
def _configure_off_mode():
|
151
|
+
"""Suppress all logging output"""
|
152
|
+
# Redirect to devnull
|
153
|
+
null_file = open(os.devnull, 'w')
|
154
|
+
|
155
|
+
# Clear existing handlers and configure to devnull
|
156
|
+
logging.getLogger().handlers.clear()
|
157
|
+
logging.basicConfig(
|
158
|
+
stream=null_file,
|
159
|
+
level=logging.CRITICAL,
|
160
|
+
format=''
|
161
|
+
)
|
162
|
+
|
163
|
+
# Set all known loggers to CRITICAL to prevent any output
|
164
|
+
logger_names = [
|
165
|
+
'', 'signalwire_agents', 'skill_registry', 'swml_service',
|
166
|
+
'agent_base', 'AgentServer', 'uvicorn', 'fastapi'
|
167
|
+
]
|
168
|
+
for name in logger_names:
|
169
|
+
logging.getLogger(name).setLevel(logging.CRITICAL)
|
170
|
+
|
171
|
+
# Configure structlog if available
|
172
|
+
try:
|
173
|
+
import structlog
|
174
|
+
structlog.configure(
|
175
|
+
processors=[],
|
176
|
+
wrapper_class=structlog.make_filtering_bound_logger(logging.CRITICAL),
|
177
|
+
logger_factory=structlog.PrintLoggerFactory(file=null_file),
|
178
|
+
cache_logger_on_first_use=True,
|
179
|
+
)
|
180
|
+
except ImportError:
|
181
|
+
pass
|
182
|
+
|
183
|
+
|
184
|
+
def _configure_stderr_mode(log_level: str):
|
185
|
+
"""Configure logging to stderr"""
|
186
|
+
# Clear existing handlers
|
187
|
+
logging.getLogger().handlers.clear()
|
188
|
+
|
189
|
+
# Convert log level
|
190
|
+
numeric_level = getattr(logging, log_level.upper(), logging.INFO)
|
191
|
+
|
192
|
+
# Configure to stderr
|
193
|
+
logging.basicConfig(
|
194
|
+
stream=sys.stderr,
|
195
|
+
level=numeric_level,
|
196
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
197
|
+
)
|
198
|
+
|
199
|
+
|
200
|
+
def _configure_default_mode(log_level: str):
|
201
|
+
"""Configure standard logging behavior"""
|
202
|
+
# Convert log level
|
203
|
+
numeric_level = getattr(logging, log_level.upper(), logging.INFO)
|
204
|
+
|
205
|
+
# Configure standard logging
|
206
|
+
logging.basicConfig(
|
207
|
+
level=numeric_level,
|
208
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
209
|
+
)
|
210
|
+
|
211
|
+
|
212
|
+
def get_logger(name: str) -> StructuredLoggerWrapper:
|
213
|
+
"""
|
214
|
+
Get a logger instance for the specified name with structured logging support
|
215
|
+
|
216
|
+
This is the single entry point for all logging in the SDK.
|
217
|
+
All modules should use this instead of direct logging module usage.
|
218
|
+
|
219
|
+
Args:
|
220
|
+
name: Logger name, typically __name__
|
221
|
+
|
222
|
+
Returns:
|
223
|
+
StructuredLoggerWrapper that supports both regular and structured logging
|
224
|
+
"""
|
225
|
+
# Ensure logging is configured
|
226
|
+
configure_logging()
|
227
|
+
|
228
|
+
# Get the standard Python logger
|
229
|
+
python_logger = logging.getLogger(name)
|
230
|
+
|
231
|
+
# Wrap it with our structured logging interface
|
232
|
+
return StructuredLoggerWrapper(python_logger)
|
@@ -15,6 +15,8 @@ from typing import Dict, Any, Optional, Callable, List, Type, Union
|
|
15
15
|
import inspect
|
16
16
|
import logging
|
17
17
|
|
18
|
+
# Import here to avoid circular imports
|
19
|
+
from signalwire_agents.core.function_result import SwaigFunctionResult
|
18
20
|
|
19
21
|
class SWAIGFunction:
|
20
22
|
"""
|
@@ -101,9 +103,6 @@ class SWAIGFunction:
|
|
101
103
|
# Call the handler with both args and raw_data
|
102
104
|
result = self.handler(args, raw_data)
|
103
105
|
|
104
|
-
# Import here to avoid circular imports
|
105
|
-
from signalwire_agents.core.function_result import SwaigFunctionResult
|
106
|
-
|
107
106
|
# Handle different result types - everything must end up as a SwaigFunctionResult
|
108
107
|
if isinstance(result, SwaigFunctionResult):
|
109
108
|
# Already a SwaigFunctionResult - just convert to dict
|
@@ -269,6 +269,7 @@ class SwmlRenderer:
|
|
269
269
|
|
270
270
|
# Return in requested format
|
271
271
|
if format.lower() == "yaml":
|
272
|
+
import yaml
|
272
273
|
return yaml.dump(swml, sort_keys=False)
|
273
274
|
else:
|
274
275
|
return json.dumps(swml, indent=2)
|
@@ -305,17 +306,24 @@ class SwmlRenderer:
|
|
305
306
|
# Add any actions
|
306
307
|
if actions:
|
307
308
|
for action in actions:
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
]
|
316
|
-
|
317
|
-
|
318
|
-
|
309
|
+
# Support both type-based actions and direct SWML verbs
|
310
|
+
if "type" in action:
|
311
|
+
# Type-based action format
|
312
|
+
if action["type"] == "play":
|
313
|
+
service.add_verb("play", {
|
314
|
+
"url": action["url"]
|
315
|
+
})
|
316
|
+
elif action["type"] == "transfer":
|
317
|
+
service.add_verb("connect", [
|
318
|
+
{"to": action["dest"]}
|
319
|
+
])
|
320
|
+
elif action["type"] == "hang_up":
|
321
|
+
service.add_verb("hangup", {})
|
322
|
+
# Additional action types could be added here
|
323
|
+
else:
|
324
|
+
# Direct SWML verb format
|
325
|
+
for verb_name, verb_config in action.items():
|
326
|
+
service.add_verb(verb_name, verb_config)
|
319
327
|
|
320
328
|
# Return in requested format
|
321
329
|
if format.lower() == "yaml":
|
@@ -343,26 +351,33 @@ class SwmlRenderer:
|
|
343
351
|
# Add any actions
|
344
352
|
if actions:
|
345
353
|
for action in actions:
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
]
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
354
|
+
# Support both type-based actions and direct SWML verbs
|
355
|
+
if "type" in action:
|
356
|
+
# Type-based action format
|
357
|
+
if action["type"] == "play":
|
358
|
+
swml["sections"]["main"].append({
|
359
|
+
"play": {
|
360
|
+
"url": action["url"]
|
361
|
+
}
|
362
|
+
})
|
363
|
+
elif action["type"] == "transfer":
|
364
|
+
swml["sections"]["main"].append({
|
365
|
+
"connect": [
|
366
|
+
{"to": action["dest"]}
|
367
|
+
]
|
368
|
+
})
|
369
|
+
elif action["type"] == "hang_up":
|
370
|
+
swml["sections"]["main"].append({
|
371
|
+
"hangup": {}
|
372
|
+
})
|
373
|
+
# Additional action types could be added here
|
374
|
+
else:
|
375
|
+
# Direct SWML verb format - add the action as-is
|
376
|
+
swml["sections"]["main"].append(action)
|
363
377
|
|
364
378
|
# Return in requested format
|
365
379
|
if format.lower() == "yaml":
|
380
|
+
import yaml
|
366
381
|
return yaml.dump(swml, sort_keys=False)
|
367
382
|
else:
|
368
383
|
return json.dumps(swml)
|
@@ -0,0 +1,131 @@
|
|
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
|
+
"""SignalWire Agents Local Search Module
|
11
|
+
|
12
|
+
This module provides local search capabilities for the SignalWire Agents SDK.
|
13
|
+
It requires additional dependencies that can be installed with:
|
14
|
+
|
15
|
+
pip install signalwire-agents[search] # Basic search
|
16
|
+
pip install signalwire-agents[search-full] # + Document processing
|
17
|
+
pip install signalwire-agents[search-nlp] # + Advanced NLP
|
18
|
+
pip install signalwire-agents[search-all] # All features
|
19
|
+
"""
|
20
|
+
|
21
|
+
import warnings
|
22
|
+
|
23
|
+
# Check for core search dependencies
|
24
|
+
_SEARCH_AVAILABLE = True
|
25
|
+
_MISSING_DEPS = []
|
26
|
+
|
27
|
+
try:
|
28
|
+
import numpy
|
29
|
+
except ImportError:
|
30
|
+
_SEARCH_AVAILABLE = False
|
31
|
+
_MISSING_DEPS.append('numpy')
|
32
|
+
|
33
|
+
try:
|
34
|
+
import sklearn
|
35
|
+
except ImportError:
|
36
|
+
_SEARCH_AVAILABLE = False
|
37
|
+
_MISSING_DEPS.append('scikit-learn')
|
38
|
+
|
39
|
+
try:
|
40
|
+
import sentence_transformers
|
41
|
+
except ImportError:
|
42
|
+
_SEARCH_AVAILABLE = False
|
43
|
+
_MISSING_DEPS.append('sentence-transformers')
|
44
|
+
|
45
|
+
try:
|
46
|
+
import nltk
|
47
|
+
except ImportError:
|
48
|
+
_SEARCH_AVAILABLE = False
|
49
|
+
_MISSING_DEPS.append('nltk')
|
50
|
+
|
51
|
+
def _check_search_dependencies():
|
52
|
+
"""Check if search dependencies are available and provide helpful error message"""
|
53
|
+
if not _SEARCH_AVAILABLE:
|
54
|
+
missing = ', '.join(_MISSING_DEPS)
|
55
|
+
raise ImportError(
|
56
|
+
f"Search functionality requires additional dependencies: {missing}\n"
|
57
|
+
f"Install with: pip install signalwire-agents[search]\n"
|
58
|
+
f"For full features: pip install signalwire-agents[search-all]"
|
59
|
+
)
|
60
|
+
|
61
|
+
# Conditional imports based on available dependencies
|
62
|
+
__all__ = []
|
63
|
+
|
64
|
+
if _SEARCH_AVAILABLE:
|
65
|
+
try:
|
66
|
+
from .query_processor import preprocess_query, preprocess_document_content
|
67
|
+
from .document_processor import DocumentProcessor
|
68
|
+
from .index_builder import IndexBuilder
|
69
|
+
from .search_engine import SearchEngine
|
70
|
+
from .search_service import SearchService
|
71
|
+
|
72
|
+
__all__ = [
|
73
|
+
'preprocess_query',
|
74
|
+
'preprocess_document_content',
|
75
|
+
'DocumentProcessor',
|
76
|
+
'IndexBuilder',
|
77
|
+
'SearchEngine',
|
78
|
+
'SearchService'
|
79
|
+
]
|
80
|
+
except ImportError as e:
|
81
|
+
# Some search components failed to import
|
82
|
+
warnings.warn(
|
83
|
+
f"Some search components failed to import: {e}\n"
|
84
|
+
f"For full search functionality, install: pip install signalwire-agents[search-all]",
|
85
|
+
ImportWarning
|
86
|
+
)
|
87
|
+
|
88
|
+
# Try to import what we can
|
89
|
+
try:
|
90
|
+
from .query_processor import preprocess_query, preprocess_document_content
|
91
|
+
__all__.extend(['preprocess_query', 'preprocess_document_content'])
|
92
|
+
except ImportError:
|
93
|
+
pass
|
94
|
+
|
95
|
+
try:
|
96
|
+
from .document_processor import DocumentProcessor
|
97
|
+
__all__.append('DocumentProcessor')
|
98
|
+
except ImportError:
|
99
|
+
pass
|
100
|
+
else:
|
101
|
+
# Provide stub functions that give helpful error messages
|
102
|
+
def preprocess_query(*args, **kwargs):
|
103
|
+
_check_search_dependencies()
|
104
|
+
|
105
|
+
def preprocess_document_content(*args, **kwargs):
|
106
|
+
_check_search_dependencies()
|
107
|
+
|
108
|
+
class DocumentProcessor:
|
109
|
+
def __init__(self, *args, **kwargs):
|
110
|
+
_check_search_dependencies()
|
111
|
+
|
112
|
+
class IndexBuilder:
|
113
|
+
def __init__(self, *args, **kwargs):
|
114
|
+
_check_search_dependencies()
|
115
|
+
|
116
|
+
class SearchEngine:
|
117
|
+
def __init__(self, *args, **kwargs):
|
118
|
+
_check_search_dependencies()
|
119
|
+
|
120
|
+
class SearchService:
|
121
|
+
def __init__(self, *args, **kwargs):
|
122
|
+
_check_search_dependencies()
|
123
|
+
|
124
|
+
__all__ = [
|
125
|
+
'preprocess_query',
|
126
|
+
'preprocess_document_content',
|
127
|
+
'DocumentProcessor',
|
128
|
+
'IndexBuilder',
|
129
|
+
'SearchEngine',
|
130
|
+
'SearchService'
|
131
|
+
]
|