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.
Files changed (24) hide show
  1. signalwire_agents/__init__.py +5 -1
  2. signalwire_agents/agent_server.py +222 -13
  3. signalwire_agents/cli/build_search.py +457 -0
  4. signalwire_agents/cli/test_swaig.py +177 -113
  5. signalwire_agents/core/agent_base.py +1 -1
  6. signalwire_agents/core/logging_config.py +232 -0
  7. signalwire_agents/search/__init__.py +131 -0
  8. signalwire_agents/search/document_processor.py +764 -0
  9. signalwire_agents/search/index_builder.py +534 -0
  10. signalwire_agents/search/query_processor.py +371 -0
  11. signalwire_agents/search/search_engine.py +383 -0
  12. signalwire_agents/search/search_service.py +251 -0
  13. signalwire_agents/skills/native_vector_search/__init__.py +1 -0
  14. signalwire_agents/skills/native_vector_search/skill.py +352 -0
  15. signalwire_agents/skills/registry.py +2 -15
  16. signalwire_agents/utils/__init__.py +13 -1
  17. {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/METADATA +110 -3
  18. {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/RECORD +23 -14
  19. {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/entry_points.txt +1 -0
  20. signalwire_agents/utils/serverless.py +0 -38
  21. {signalwire_agents-0.1.11.data → signalwire_agents-0.1.12.data}/data/schema.json +0 -0
  22. {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/WHEEL +0 -0
  23. {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/licenses/LICENSE +0 -0
  24. {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
+ ]