signalwire-agents 0.1.11__py3-none-any.whl → 0.1.12__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 -1
- signalwire_agents/core/logging_config.py +232 -0
- 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.12.dist-info}/METADATA +110 -3
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/RECORD +23 -14
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/entry_points.txt +1 -0
- signalwire_agents/utils/serverless.py +0 -38
- {signalwire_agents-0.1.11.data → signalwire_agents-0.1.12.data}/data/schema.json +0 -0
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.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)
|
@@ -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
|
+
]
|