signalwire-agents 0.1.6__py3-none-any.whl → 1.0.7__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 +130 -4
- signalwire_agents/agent_server.py +438 -32
- signalwire_agents/agents/bedrock.py +296 -0
- signalwire_agents/cli/__init__.py +18 -0
- signalwire_agents/cli/build_search.py +1367 -0
- signalwire_agents/cli/config.py +80 -0
- signalwire_agents/cli/core/__init__.py +10 -0
- signalwire_agents/cli/core/agent_loader.py +470 -0
- signalwire_agents/cli/core/argparse_helpers.py +179 -0
- signalwire_agents/cli/core/dynamic_config.py +71 -0
- signalwire_agents/cli/core/service_loader.py +303 -0
- signalwire_agents/cli/execution/__init__.py +10 -0
- signalwire_agents/cli/execution/datamap_exec.py +446 -0
- signalwire_agents/cli/execution/webhook_exec.py +134 -0
- signalwire_agents/cli/init_project.py +1225 -0
- signalwire_agents/cli/output/__init__.py +10 -0
- signalwire_agents/cli/output/output_formatter.py +255 -0
- signalwire_agents/cli/output/swml_dump.py +186 -0
- signalwire_agents/cli/simulation/__init__.py +10 -0
- signalwire_agents/cli/simulation/data_generation.py +374 -0
- signalwire_agents/cli/simulation/data_overrides.py +200 -0
- signalwire_agents/cli/simulation/mock_env.py +282 -0
- signalwire_agents/cli/swaig_test_wrapper.py +52 -0
- signalwire_agents/cli/test_swaig.py +809 -0
- signalwire_agents/cli/types.py +81 -0
- signalwire_agents/core/__init__.py +2 -2
- signalwire_agents/core/agent/__init__.py +12 -0
- signalwire_agents/core/agent/config/__init__.py +12 -0
- signalwire_agents/core/agent/deployment/__init__.py +9 -0
- signalwire_agents/core/agent/deployment/handlers/__init__.py +9 -0
- signalwire_agents/core/agent/prompt/__init__.py +14 -0
- signalwire_agents/core/agent/prompt/manager.py +306 -0
- signalwire_agents/core/agent/routing/__init__.py +9 -0
- signalwire_agents/core/agent/security/__init__.py +9 -0
- signalwire_agents/core/agent/swml/__init__.py +9 -0
- signalwire_agents/core/agent/tools/__init__.py +15 -0
- signalwire_agents/core/agent/tools/decorator.py +97 -0
- signalwire_agents/core/agent/tools/registry.py +210 -0
- signalwire_agents/core/agent_base.py +959 -2166
- signalwire_agents/core/auth_handler.py +233 -0
- signalwire_agents/core/config_loader.py +259 -0
- signalwire_agents/core/contexts.py +707 -0
- signalwire_agents/core/data_map.py +487 -0
- signalwire_agents/core/function_result.py +1150 -1
- signalwire_agents/core/logging_config.py +376 -0
- signalwire_agents/core/mixins/__init__.py +28 -0
- signalwire_agents/core/mixins/ai_config_mixin.py +442 -0
- signalwire_agents/core/mixins/auth_mixin.py +287 -0
- signalwire_agents/core/mixins/prompt_mixin.py +358 -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 +153 -0
- signalwire_agents/core/mixins/tool_mixin.py +230 -0
- signalwire_agents/core/mixins/web_mixin.py +1134 -0
- signalwire_agents/core/security/session_manager.py +174 -86
- signalwire_agents/core/security_config.py +333 -0
- signalwire_agents/core/skill_base.py +200 -0
- signalwire_agents/core/skill_manager.py +244 -0
- signalwire_agents/core/swaig_function.py +33 -9
- signalwire_agents/core/swml_builder.py +212 -12
- signalwire_agents/core/swml_handler.py +43 -13
- signalwire_agents/core/swml_renderer.py +123 -297
- signalwire_agents/core/swml_service.py +277 -260
- signalwire_agents/prefabs/concierge.py +6 -2
- signalwire_agents/prefabs/info_gatherer.py +149 -33
- signalwire_agents/prefabs/receptionist.py +14 -22
- signalwire_agents/prefabs/survey.py +6 -2
- signalwire_agents/schema.json +9218 -5489
- signalwire_agents/search/__init__.py +137 -0
- signalwire_agents/search/document_processor.py +1223 -0
- signalwire_agents/search/index_builder.py +804 -0
- signalwire_agents/search/migration.py +418 -0
- signalwire_agents/search/models.py +30 -0
- signalwire_agents/search/pgvector_backend.py +752 -0
- signalwire_agents/search/query_processor.py +502 -0
- signalwire_agents/search/search_engine.py +1264 -0
- signalwire_agents/search/search_service.py +574 -0
- signalwire_agents/skills/README.md +452 -0
- signalwire_agents/skills/__init__.py +23 -0
- signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
- signalwire_agents/skills/api_ninjas_trivia/__init__.py +12 -0
- signalwire_agents/skills/api_ninjas_trivia/skill.py +237 -0
- signalwire_agents/skills/datasphere/README.md +210 -0
- signalwire_agents/skills/datasphere/__init__.py +12 -0
- signalwire_agents/skills/datasphere/skill.py +310 -0
- signalwire_agents/skills/datasphere_serverless/README.md +258 -0
- signalwire_agents/skills/datasphere_serverless/__init__.py +10 -0
- signalwire_agents/skills/datasphere_serverless/skill.py +237 -0
- signalwire_agents/skills/datetime/README.md +132 -0
- signalwire_agents/skills/datetime/__init__.py +10 -0
- signalwire_agents/skills/datetime/skill.py +126 -0
- signalwire_agents/skills/joke/README.md +149 -0
- signalwire_agents/skills/joke/__init__.py +10 -0
- signalwire_agents/skills/joke/skill.py +109 -0
- signalwire_agents/skills/math/README.md +161 -0
- signalwire_agents/skills/math/__init__.py +10 -0
- signalwire_agents/skills/math/skill.py +105 -0
- signalwire_agents/skills/mcp_gateway/README.md +230 -0
- signalwire_agents/skills/mcp_gateway/__init__.py +10 -0
- signalwire_agents/skills/mcp_gateway/skill.py +421 -0
- signalwire_agents/skills/native_vector_search/README.md +210 -0
- signalwire_agents/skills/native_vector_search/__init__.py +10 -0
- signalwire_agents/skills/native_vector_search/skill.py +820 -0
- signalwire_agents/skills/play_background_file/README.md +218 -0
- signalwire_agents/skills/play_background_file/__init__.py +12 -0
- signalwire_agents/skills/play_background_file/skill.py +242 -0
- signalwire_agents/skills/registry.py +459 -0
- signalwire_agents/skills/spider/README.md +236 -0
- signalwire_agents/skills/spider/__init__.py +13 -0
- signalwire_agents/skills/spider/skill.py +598 -0
- signalwire_agents/skills/swml_transfer/README.md +395 -0
- signalwire_agents/skills/swml_transfer/__init__.py +10 -0
- signalwire_agents/skills/swml_transfer/skill.py +359 -0
- signalwire_agents/skills/weather_api/README.md +178 -0
- signalwire_agents/skills/weather_api/__init__.py +12 -0
- signalwire_agents/skills/weather_api/skill.py +191 -0
- signalwire_agents/skills/web_search/README.md +163 -0
- signalwire_agents/skills/web_search/__init__.py +10 -0
- signalwire_agents/skills/web_search/skill.py +739 -0
- signalwire_agents/skills/wikipedia_search/README.md +228 -0
- signalwire_agents/{core/state → skills/wikipedia_search}/__init__.py +5 -4
- signalwire_agents/skills/wikipedia_search/skill.py +210 -0
- signalwire_agents/utils/__init__.py +14 -0
- signalwire_agents/utils/schema_utils.py +111 -44
- signalwire_agents/web/__init__.py +17 -0
- signalwire_agents/web/web_service.py +559 -0
- signalwire_agents-1.0.7.data/data/share/man/man1/sw-agent-init.1 +307 -0
- signalwire_agents-1.0.7.data/data/share/man/man1/sw-search.1 +483 -0
- signalwire_agents-1.0.7.data/data/share/man/man1/swaig-test.1 +308 -0
- signalwire_agents-1.0.7.dist-info/METADATA +992 -0
- signalwire_agents-1.0.7.dist-info/RECORD +142 -0
- {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/WHEEL +1 -1
- signalwire_agents-1.0.7.dist-info/entry_points.txt +4 -0
- signalwire_agents/core/state/file_state_manager.py +0 -219
- signalwire_agents/core/state/state_manager.py +0 -101
- signalwire_agents-0.1.6.data/data/schema.json +0 -5611
- signalwire_agents-0.1.6.dist-info/METADATA +0 -199
- signalwire_agents-0.1.6.dist-info/RECORD +0 -34
- {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
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
|
+
def bind(self, **kwargs) -> 'StructuredLoggerWrapper':
|
|
93
|
+
"""
|
|
94
|
+
Create a new logger instance with bound context data
|
|
95
|
+
|
|
96
|
+
This maintains compatibility with structlog's bind() method.
|
|
97
|
+
The bound data will be included in all subsequent log messages.
|
|
98
|
+
"""
|
|
99
|
+
# Create a new wrapper that includes the bound context
|
|
100
|
+
return BoundStructuredLoggerWrapper(self._logger, kwargs)
|
|
101
|
+
|
|
102
|
+
# Support direct access to underlying logger attributes if needed
|
|
103
|
+
def __getattr__(self, name: str) -> Any:
|
|
104
|
+
"""Delegate any unknown attributes to the underlying logger"""
|
|
105
|
+
return getattr(self._logger, name)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class BoundStructuredLoggerWrapper(StructuredLoggerWrapper):
|
|
109
|
+
"""
|
|
110
|
+
A structured logger wrapper that includes bound context data in all messages
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, logger: logging.Logger, bound_data: Dict[str, Any]):
|
|
114
|
+
super().__init__(logger)
|
|
115
|
+
self._bound_data = bound_data
|
|
116
|
+
|
|
117
|
+
def _format_structured_message(self, message: str, **kwargs) -> str:
|
|
118
|
+
"""Format a message with both bound data and additional keyword arguments"""
|
|
119
|
+
# Combine bound data with additional kwargs
|
|
120
|
+
all_kwargs = {**self._bound_data, **kwargs}
|
|
121
|
+
return super()._format_structured_message(message, **all_kwargs)
|
|
122
|
+
|
|
123
|
+
def bind(self, **kwargs) -> 'BoundStructuredLoggerWrapper':
|
|
124
|
+
"""Create a new logger with additional bound context"""
|
|
125
|
+
# Combine existing bound data with new data
|
|
126
|
+
new_bound_data = {**self._bound_data, **kwargs}
|
|
127
|
+
return BoundStructuredLoggerWrapper(self._logger, new_bound_data)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_execution_mode():
|
|
131
|
+
"""
|
|
132
|
+
Determine the execution mode based on environment variables
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
str: 'server', 'cgi', 'lambda', 'google_cloud_function', 'azure_function', or 'unknown'
|
|
136
|
+
"""
|
|
137
|
+
# Check for CGI environment
|
|
138
|
+
if os.getenv('GATEWAY_INTERFACE'):
|
|
139
|
+
return 'cgi'
|
|
140
|
+
|
|
141
|
+
# Check for AWS Lambda environment
|
|
142
|
+
if os.getenv('AWS_LAMBDA_FUNCTION_NAME') or os.getenv('LAMBDA_TASK_ROOT'):
|
|
143
|
+
return 'lambda'
|
|
144
|
+
|
|
145
|
+
# Check for Google Cloud Functions environment
|
|
146
|
+
if (os.getenv('FUNCTION_TARGET') or
|
|
147
|
+
os.getenv('K_SERVICE') or
|
|
148
|
+
os.getenv('GOOGLE_CLOUD_PROJECT')):
|
|
149
|
+
return 'google_cloud_function'
|
|
150
|
+
|
|
151
|
+
# Check for Azure Functions environment
|
|
152
|
+
if (os.getenv('AZURE_FUNCTIONS_ENVIRONMENT') or
|
|
153
|
+
os.getenv('FUNCTIONS_WORKER_RUNTIME') or
|
|
154
|
+
os.getenv('AzureWebJobsStorage')):
|
|
155
|
+
return 'azure_function'
|
|
156
|
+
|
|
157
|
+
# Default to server mode
|
|
158
|
+
return 'server'
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def reset_logging_configuration():
|
|
162
|
+
"""
|
|
163
|
+
Reset the logging configuration flag to allow reconfiguration
|
|
164
|
+
|
|
165
|
+
This is useful when environment variables change after initial configuration.
|
|
166
|
+
"""
|
|
167
|
+
global _logging_configured
|
|
168
|
+
_logging_configured = False
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def configure_logging():
|
|
172
|
+
"""
|
|
173
|
+
Configure logging system once, globally, based on environment variables
|
|
174
|
+
|
|
175
|
+
Environment Variables:
|
|
176
|
+
SIGNALWIRE_LOG_MODE: off, stderr, default, auto
|
|
177
|
+
SIGNALWIRE_LOG_LEVEL: debug, info, warning, error, critical
|
|
178
|
+
"""
|
|
179
|
+
global _logging_configured
|
|
180
|
+
|
|
181
|
+
if _logging_configured:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# Get configuration from environment
|
|
185
|
+
log_mode = os.getenv('SIGNALWIRE_LOG_MODE', '').lower()
|
|
186
|
+
log_level = os.getenv('SIGNALWIRE_LOG_LEVEL', 'info').lower()
|
|
187
|
+
|
|
188
|
+
# Determine log mode if auto or not specified
|
|
189
|
+
if not log_mode or log_mode == 'auto':
|
|
190
|
+
execution_mode = get_execution_mode()
|
|
191
|
+
if execution_mode == 'cgi':
|
|
192
|
+
log_mode = 'off'
|
|
193
|
+
else:
|
|
194
|
+
log_mode = 'default'
|
|
195
|
+
|
|
196
|
+
# Configure based on mode
|
|
197
|
+
if log_mode == 'off':
|
|
198
|
+
_configure_off_mode()
|
|
199
|
+
elif log_mode == 'stderr':
|
|
200
|
+
_configure_stderr_mode(log_level)
|
|
201
|
+
else: # default mode
|
|
202
|
+
_configure_default_mode(log_level)
|
|
203
|
+
|
|
204
|
+
_logging_configured = True
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _configure_off_mode():
|
|
208
|
+
"""Suppress all logging output"""
|
|
209
|
+
# Redirect to devnull
|
|
210
|
+
null_file = open(os.devnull, 'w')
|
|
211
|
+
|
|
212
|
+
# Clear existing handlers and configure to devnull
|
|
213
|
+
logging.getLogger().handlers.clear()
|
|
214
|
+
logging.basicConfig(
|
|
215
|
+
stream=null_file,
|
|
216
|
+
level=logging.CRITICAL,
|
|
217
|
+
format=''
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Set all known loggers to CRITICAL to prevent any output
|
|
221
|
+
logger_names = [
|
|
222
|
+
'', 'signalwire_agents', 'skill_registry', 'swml_service',
|
|
223
|
+
'agent_base', 'AgentServer', 'uvicorn', 'fastapi'
|
|
224
|
+
]
|
|
225
|
+
for name in logger_names:
|
|
226
|
+
logging.getLogger(name).setLevel(logging.CRITICAL)
|
|
227
|
+
|
|
228
|
+
# Configure structlog if available
|
|
229
|
+
try:
|
|
230
|
+
import structlog
|
|
231
|
+
structlog.configure(
|
|
232
|
+
processors=[],
|
|
233
|
+
wrapper_class=structlog.make_filtering_bound_logger(logging.CRITICAL),
|
|
234
|
+
logger_factory=structlog.PrintLoggerFactory(file=null_file),
|
|
235
|
+
cache_logger_on_first_use=True,
|
|
236
|
+
)
|
|
237
|
+
except ImportError:
|
|
238
|
+
pass
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _configure_stderr_mode(log_level: str):
|
|
242
|
+
"""Configure logging to stderr with colored formatting"""
|
|
243
|
+
# Clear existing handlers
|
|
244
|
+
logging.getLogger().handlers.clear()
|
|
245
|
+
|
|
246
|
+
# Convert log level
|
|
247
|
+
numeric_level = getattr(logging, log_level.upper(), logging.INFO)
|
|
248
|
+
|
|
249
|
+
# Create handler with colored formatter
|
|
250
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
251
|
+
handler.setFormatter(ColoredFormatter())
|
|
252
|
+
|
|
253
|
+
# Configure root logger
|
|
254
|
+
root_logger = logging.getLogger()
|
|
255
|
+
root_logger.setLevel(numeric_level)
|
|
256
|
+
root_logger.addHandler(handler)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _configure_default_mode(log_level: str):
|
|
260
|
+
"""Configure standard logging behavior with colored formatting"""
|
|
261
|
+
# Clear existing handlers
|
|
262
|
+
logging.getLogger().handlers.clear()
|
|
263
|
+
|
|
264
|
+
# Convert log level
|
|
265
|
+
numeric_level = getattr(logging, log_level.upper(), logging.INFO)
|
|
266
|
+
|
|
267
|
+
# Create handler with colored formatter
|
|
268
|
+
handler = logging.StreamHandler()
|
|
269
|
+
handler.setFormatter(ColoredFormatter())
|
|
270
|
+
|
|
271
|
+
# Configure root logger
|
|
272
|
+
root_logger = logging.getLogger()
|
|
273
|
+
root_logger.setLevel(numeric_level)
|
|
274
|
+
root_logger.addHandler(handler)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def get_logger(name: str) -> StructuredLoggerWrapper:
|
|
278
|
+
"""
|
|
279
|
+
Get a logger instance for the specified name with structured logging support
|
|
280
|
+
|
|
281
|
+
This is the single entry point for all logging in the SDK.
|
|
282
|
+
All modules should use this instead of direct logging module usage.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
name: Logger name, typically __name__
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
StructuredLoggerWrapper that supports both regular and structured logging
|
|
289
|
+
"""
|
|
290
|
+
# Ensure logging is configured
|
|
291
|
+
configure_logging()
|
|
292
|
+
|
|
293
|
+
# Get the standard Python logger
|
|
294
|
+
python_logger = logging.getLogger(name)
|
|
295
|
+
|
|
296
|
+
# Wrap it with our structured logging interface
|
|
297
|
+
return StructuredLoggerWrapper(python_logger)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class ColoredFormatter(logging.Formatter):
|
|
301
|
+
"""
|
|
302
|
+
A beautiful colored logging formatter that makes logs easy to read and visually appealing
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
# ANSI color codes
|
|
306
|
+
COLORS = {
|
|
307
|
+
'DEBUG': '\033[36m', # Cyan
|
|
308
|
+
'INFO': '\033[32m', # Green
|
|
309
|
+
'WARNING': '\033[33m', # Yellow
|
|
310
|
+
'ERROR': '\033[31m', # Red
|
|
311
|
+
'CRITICAL': '\033[35m', # Magenta
|
|
312
|
+
'RESET': '\033[0m', # Reset
|
|
313
|
+
'BOLD': '\033[1m', # Bold
|
|
314
|
+
'DIM': '\033[2m', # Dim
|
|
315
|
+
'WHITE': '\033[37m', # White
|
|
316
|
+
'BLUE': '\033[34m', # Blue
|
|
317
|
+
'BLACK': '\033[30m', # Black (for brackets)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
def __init__(self):
|
|
321
|
+
super().__init__()
|
|
322
|
+
|
|
323
|
+
def format(self, record):
|
|
324
|
+
# Check if we should use colors (not in raw mode, and stdout is a tty)
|
|
325
|
+
use_colors = (
|
|
326
|
+
hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() and
|
|
327
|
+
os.getenv('SIGNALWIRE_LOG_MODE') != 'off' and
|
|
328
|
+
'--raw' not in sys.argv and '--dump-swml' not in sys.argv
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
if use_colors:
|
|
332
|
+
# Get colors
|
|
333
|
+
level_color = self.COLORS.get(record.levelname, self.COLORS['WHITE'])
|
|
334
|
+
reset = self.COLORS['RESET']
|
|
335
|
+
dim = self.COLORS['DIM']
|
|
336
|
+
bold = self.COLORS['BOLD']
|
|
337
|
+
blue = self.COLORS['BLUE']
|
|
338
|
+
black = self.COLORS['BLACK']
|
|
339
|
+
|
|
340
|
+
# Format timestamp in a compact, readable way
|
|
341
|
+
timestamp = self.formatTime(record, '%H:%M:%S')
|
|
342
|
+
|
|
343
|
+
# Format level with appropriate color and consistent width
|
|
344
|
+
level_name = f"{level_color}{record.levelname:<8}{reset}"
|
|
345
|
+
|
|
346
|
+
# Format logger name - keep it short and readable
|
|
347
|
+
logger_name = record.name
|
|
348
|
+
if len(logger_name) > 15:
|
|
349
|
+
# Truncate long logger names but keep the end (most specific part)
|
|
350
|
+
logger_name = "..." + logger_name[-12:]
|
|
351
|
+
|
|
352
|
+
# Get function and line info if available
|
|
353
|
+
func_info = ""
|
|
354
|
+
if hasattr(record, 'funcName') and hasattr(record, 'lineno'):
|
|
355
|
+
func_name = getattr(record, 'funcName', '')
|
|
356
|
+
line_no = getattr(record, 'lineno', 0)
|
|
357
|
+
if func_name and func_name != '<module>':
|
|
358
|
+
func_info = f" {dim}({func_name}:{line_no}){reset}"
|
|
359
|
+
|
|
360
|
+
# Format the message
|
|
361
|
+
message = record.getMessage()
|
|
362
|
+
|
|
363
|
+
# Create the final formatted message with a clean, readable layout
|
|
364
|
+
formatted = (
|
|
365
|
+
f"{black}[{reset}{dim}{timestamp}{reset}{black}]{reset} "
|
|
366
|
+
f"{level_name} "
|
|
367
|
+
f"{blue}{logger_name:<15}{reset}"
|
|
368
|
+
f"{func_info} "
|
|
369
|
+
f"{message}"
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
return formatted
|
|
373
|
+
else:
|
|
374
|
+
# Non-colored format (fallback for files, pipes, etc.)
|
|
375
|
+
timestamp = self.formatTime(record, '%Y-%m-%d %H:%M:%S')
|
|
376
|
+
return f"{timestamp} {record.levelname:<8} {record.name} {record.getMessage()}"
|
|
@@ -0,0 +1,28 @@
|
|
|
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 .prompt_mixin import PromptMixin
|
|
11
|
+
from .tool_mixin import ToolMixin
|
|
12
|
+
from .web_mixin import WebMixin
|
|
13
|
+
from .auth_mixin import AuthMixin
|
|
14
|
+
from .skill_mixin import SkillMixin
|
|
15
|
+
from .ai_config_mixin import AIConfigMixin
|
|
16
|
+
from .serverless_mixin import ServerlessMixin
|
|
17
|
+
from .state_mixin import StateMixin
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
'PromptMixin',
|
|
21
|
+
'ToolMixin',
|
|
22
|
+
'WebMixin',
|
|
23
|
+
'AuthMixin',
|
|
24
|
+
'SkillMixin',
|
|
25
|
+
'AIConfigMixin',
|
|
26
|
+
'ServerlessMixin',
|
|
27
|
+
'StateMixin'
|
|
28
|
+
]
|