sector8-sdk 1.0.0__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.
- sector8/__init__.py +349 -0
- sector8/config_wizard.py +407 -0
- sector8/errors.py +442 -0
- sector8/py.typed +2 -0
- sector8/simple_client.py +456 -0
- sector8_sdk-1.0.0.dist-info/METADATA +529 -0
- sector8_sdk-1.0.0.dist-info/RECORD +11 -0
- sector8_sdk-1.0.0.dist-info/WHEEL +5 -0
- sector8_sdk-1.0.0.dist-info/entry_points.txt +13 -0
- sector8_sdk-1.0.0.dist-info/licenses/LICENSE +21 -0
- sector8_sdk-1.0.0.dist-info/top_level.txt +1 -0
sector8/__init__.py
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sector8 SDK - Enterprise LLM Observability
|
|
3
|
+
|
|
4
|
+
A comprehensive SDK for monitoring and observability of Large Language Model applications.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "1.0.0"
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import functools
|
|
11
|
+
import inspect
|
|
12
|
+
import time
|
|
13
|
+
from typing import Optional, Callable, Any, Dict
|
|
14
|
+
|
|
15
|
+
from .simple_client import Sector8SimpleClient
|
|
16
|
+
from .config_wizard import run_configuration_wizard
|
|
17
|
+
|
|
18
|
+
# Advanced features removed for enterprise v1.0.0 simplicity
|
|
19
|
+
_advanced_features = False
|
|
20
|
+
|
|
21
|
+
# Instrumentation removed for enterprise v1.0.0 simplicity
|
|
22
|
+
_instrumentation_available = False
|
|
23
|
+
|
|
24
|
+
# Global client instances
|
|
25
|
+
_client = None
|
|
26
|
+
_simple_client = None
|
|
27
|
+
|
|
28
|
+
# DECORATOR PATTERN - Dashboard Integration (Tier 1)
|
|
29
|
+
def track(
|
|
30
|
+
provider: str = None,
|
|
31
|
+
model: str = None,
|
|
32
|
+
cost_per_token: float = None,
|
|
33
|
+
auto_detect: bool = True
|
|
34
|
+
):
|
|
35
|
+
"""
|
|
36
|
+
Decorator for automatic LLM function tracking and telemetry.
|
|
37
|
+
|
|
38
|
+
Matches the dashboard integration pattern:
|
|
39
|
+
|
|
40
|
+
@sector8.track()
|
|
41
|
+
async def analyze_text(text: str) -> str:
|
|
42
|
+
client = AsyncOpenAI()
|
|
43
|
+
response = await client.chat.completions.create(...)
|
|
44
|
+
return response.choices[0].message.content
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
provider: LLM provider ('openai', 'anthropic', etc.) - auto-detected if not provided
|
|
48
|
+
model: Model name ('gpt-4', 'claude-3', etc.) - auto-detected if not provided
|
|
49
|
+
cost_per_token: Custom pricing - calculated automatically if not provided
|
|
50
|
+
auto_detect: Enable automatic detection of provider/model from function calls
|
|
51
|
+
|
|
52
|
+
Environment Variables:
|
|
53
|
+
SECTOR8_API_KEY: Required - Your Sector8 API key
|
|
54
|
+
OPENAI_API_KEY: Optional - For OpenAI integration
|
|
55
|
+
ANTHROPIC_API_KEY: Optional - For Anthropic integration
|
|
56
|
+
"""
|
|
57
|
+
def decorator(func: Callable) -> Callable:
|
|
58
|
+
@functools.wraps(func)
|
|
59
|
+
async def async_wrapper(*args, **kwargs):
|
|
60
|
+
# Get or initialize the client
|
|
61
|
+
global _simple_client
|
|
62
|
+
if _simple_client is None:
|
|
63
|
+
# Auto-initialize from environment
|
|
64
|
+
api_key = os.getenv('SECTOR8_API_KEY')
|
|
65
|
+
if not api_key:
|
|
66
|
+
print("Warning: SECTOR8_API_KEY not found. Set environment variable for telemetry.")
|
|
67
|
+
return await func(*args, **kwargs) # Run function without tracking
|
|
68
|
+
|
|
69
|
+
_simple_client = Sector8SimpleClient(api_key=api_key)
|
|
70
|
+
|
|
71
|
+
# Track function execution
|
|
72
|
+
start_time = time.time()
|
|
73
|
+
function_name = func.__name__
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
# Execute the original function
|
|
77
|
+
result = await func(*args, **kwargs)
|
|
78
|
+
end_time = time.time()
|
|
79
|
+
latency_ms = int((end_time - start_time) * 1000)
|
|
80
|
+
|
|
81
|
+
# Auto-detect provider and model from result/context
|
|
82
|
+
detected_provider = provider or _detect_provider_from_function(func, result)
|
|
83
|
+
detected_model = model or _detect_model_from_function(func, result, args, kwargs)
|
|
84
|
+
|
|
85
|
+
# Log to Sector8
|
|
86
|
+
_simple_client.log_llm_call(
|
|
87
|
+
provider=detected_provider or "unknown",
|
|
88
|
+
model=detected_model or "unknown",
|
|
89
|
+
tokens=_estimate_tokens_from_result(result),
|
|
90
|
+
cost=_estimate_cost_from_result(result, detected_provider, detected_model),
|
|
91
|
+
latency_ms=latency_ms,
|
|
92
|
+
prompt=_extract_prompt_from_args(args, kwargs) or "N/A",
|
|
93
|
+
completion=_extract_completion_from_result(result) or "N/A",
|
|
94
|
+
success=True
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
except Exception as e:
|
|
100
|
+
end_time = time.time()
|
|
101
|
+
latency_ms = int((end_time - start_time) * 1000)
|
|
102
|
+
|
|
103
|
+
# Log failed attempt
|
|
104
|
+
if _simple_client:
|
|
105
|
+
_simple_client.log_llm_call(
|
|
106
|
+
provider=provider or "unknown",
|
|
107
|
+
model=model or "unknown",
|
|
108
|
+
tokens=0,
|
|
109
|
+
cost=0.0,
|
|
110
|
+
latency_ms=latency_ms,
|
|
111
|
+
success=False,
|
|
112
|
+
prompt="N/A",
|
|
113
|
+
completion=f"Error: {str(e)}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
raise # Re-raise the original exception
|
|
117
|
+
|
|
118
|
+
@functools.wraps(func)
|
|
119
|
+
def sync_wrapper(*args, **kwargs):
|
|
120
|
+
# Get or initialize the client
|
|
121
|
+
global _simple_client
|
|
122
|
+
if _simple_client is None:
|
|
123
|
+
api_key = os.getenv('SECTOR8_API_KEY')
|
|
124
|
+
if not api_key:
|
|
125
|
+
print("Warning: SECTOR8_API_KEY not found. Set environment variable for telemetry.")
|
|
126
|
+
return func(*args, **kwargs)
|
|
127
|
+
|
|
128
|
+
_simple_client = Sector8SimpleClient(api_key=api_key)
|
|
129
|
+
|
|
130
|
+
# Track function execution
|
|
131
|
+
start_time = time.time()
|
|
132
|
+
function_name = func.__name__
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
result = func(*args, **kwargs)
|
|
136
|
+
end_time = time.time()
|
|
137
|
+
latency_ms = int((end_time - start_time) * 1000)
|
|
138
|
+
|
|
139
|
+
# Auto-detect and log
|
|
140
|
+
detected_provider = provider or _detect_provider_from_function(func, result)
|
|
141
|
+
detected_model = model or _detect_model_from_function(func, result, args, kwargs)
|
|
142
|
+
|
|
143
|
+
_simple_client.log_llm_call(
|
|
144
|
+
provider=detected_provider or "unknown",
|
|
145
|
+
model=detected_model or "unknown",
|
|
146
|
+
tokens=_estimate_tokens_from_result(result),
|
|
147
|
+
cost=_estimate_cost_from_result(result, detected_provider, detected_model),
|
|
148
|
+
latency_ms=latency_ms,
|
|
149
|
+
success=True,
|
|
150
|
+
prompt=_extract_prompt_from_args(args, kwargs) or "N/A",
|
|
151
|
+
completion=_extract_completion_from_result(result) or "N/A"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return result
|
|
155
|
+
|
|
156
|
+
except Exception as e:
|
|
157
|
+
end_time = time.time()
|
|
158
|
+
latency_ms = int((end_time - start_time) * 1000)
|
|
159
|
+
|
|
160
|
+
if _simple_client:
|
|
161
|
+
_simple_client.log_llm_call(
|
|
162
|
+
provider=provider or "unknown",
|
|
163
|
+
model=model or "unknown",
|
|
164
|
+
tokens=0,
|
|
165
|
+
cost=0.0,
|
|
166
|
+
latency_ms=latency_ms,
|
|
167
|
+
success=False,
|
|
168
|
+
prompt="N/A",
|
|
169
|
+
completion=f"Error: {str(e)}"
|
|
170
|
+
)
|
|
171
|
+
raise
|
|
172
|
+
|
|
173
|
+
# Return appropriate wrapper based on function type
|
|
174
|
+
if inspect.iscoroutinefunction(func):
|
|
175
|
+
return async_wrapper
|
|
176
|
+
else:
|
|
177
|
+
return sync_wrapper
|
|
178
|
+
|
|
179
|
+
return decorator
|
|
180
|
+
|
|
181
|
+
def _detect_provider_from_function(func, result):
|
|
182
|
+
"""Auto-detect LLM provider from function context."""
|
|
183
|
+
func_source = inspect.getsource(func) if hasattr(func, '__code__') else ""
|
|
184
|
+
|
|
185
|
+
if "openai" in func_source.lower() or "OpenAI" in func_source:
|
|
186
|
+
return "openai"
|
|
187
|
+
elif "anthropic" in func_source.lower() or "claude" in func_source.lower():
|
|
188
|
+
return "anthropic"
|
|
189
|
+
elif "google" in func_source.lower() or "gemini" in func_source.lower():
|
|
190
|
+
return "google"
|
|
191
|
+
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
def _detect_model_from_function(func, result, args, kwargs):
|
|
195
|
+
"""Auto-detect model from function arguments or result."""
|
|
196
|
+
# Check function arguments for model parameter
|
|
197
|
+
for arg in kwargs.values():
|
|
198
|
+
if isinstance(arg, str):
|
|
199
|
+
if "gpt-4" in arg.lower():
|
|
200
|
+
return "gpt-4"
|
|
201
|
+
elif "gpt-3.5" in arg.lower():
|
|
202
|
+
return "gpt-3.5-turbo"
|
|
203
|
+
elif "claude" in arg.lower():
|
|
204
|
+
return "claude-3"
|
|
205
|
+
elif "gemini" in arg.lower():
|
|
206
|
+
return "gemini-pro"
|
|
207
|
+
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
def _estimate_tokens_from_result(result):
|
|
211
|
+
"""Estimate token count from result."""
|
|
212
|
+
if isinstance(result, str):
|
|
213
|
+
# Rough estimate: 1 token ≈ 4 characters
|
|
214
|
+
return max(1, len(result) // 4)
|
|
215
|
+
return 1
|
|
216
|
+
|
|
217
|
+
def _estimate_cost_from_result(result, provider, model):
|
|
218
|
+
"""Estimate cost from result."""
|
|
219
|
+
tokens = _estimate_tokens_from_result(result)
|
|
220
|
+
|
|
221
|
+
# Basic cost estimation (these would be more accurate in production)
|
|
222
|
+
cost_per_1k = {
|
|
223
|
+
("openai", "gpt-4"): 0.03,
|
|
224
|
+
("openai", "gpt-3.5-turbo"): 0.002,
|
|
225
|
+
("anthropic", "claude-3"): 0.015,
|
|
226
|
+
("google", "gemini-pro"): 0.001,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
rate = cost_per_1k.get((provider, model), 0.001)
|
|
230
|
+
return (tokens / 1000) * rate
|
|
231
|
+
|
|
232
|
+
def _extract_prompt_from_args(args, kwargs):
|
|
233
|
+
"""Extract prompt from function arguments."""
|
|
234
|
+
# Look for common prompt parameter names
|
|
235
|
+
for key in ['text', 'prompt', 'message', 'content', 'input']:
|
|
236
|
+
if key in kwargs and isinstance(kwargs[key], str):
|
|
237
|
+
return kwargs[key][:500] # Limit length
|
|
238
|
+
|
|
239
|
+
# Check positional args for strings
|
|
240
|
+
for arg in args:
|
|
241
|
+
if isinstance(arg, str) and len(arg) > 10:
|
|
242
|
+
return arg[:500]
|
|
243
|
+
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
def _extract_completion_from_result(result):
|
|
247
|
+
"""Extract completion from function result."""
|
|
248
|
+
if isinstance(result, str):
|
|
249
|
+
return result[:1000] # Limit length
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
# 3-LINE INTEGRATION - Primary API (Tier 2)
|
|
253
|
+
def setup(api_key: str = None, debug: bool = False, **kwargs) -> Sector8SimpleClient:
|
|
254
|
+
"""
|
|
255
|
+
Quick 3-line setup for Sector8 monitoring.
|
|
256
|
+
|
|
257
|
+
Usage:
|
|
258
|
+
import sector8
|
|
259
|
+
client = sector8.setup(api_key="your-key")
|
|
260
|
+
client.log_llm_call("openai", "gpt-4", tokens=150, cost=0.003)
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
api_key: Your Sector8 API key (or set SECTOR8_API_KEY env var)
|
|
264
|
+
debug: Enable debug output for troubleshooting
|
|
265
|
+
**kwargs: Additional options (base_url, client_id)
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Ready-to-use Sector8SimpleClient
|
|
269
|
+
"""
|
|
270
|
+
global _simple_client
|
|
271
|
+
|
|
272
|
+
# Auto-detect from environment
|
|
273
|
+
api_key = api_key or os.getenv('SECTOR8_API_KEY')
|
|
274
|
+
|
|
275
|
+
if not api_key:
|
|
276
|
+
from .errors import ConfigurationError
|
|
277
|
+
raise ConfigurationError.missing_api_key()
|
|
278
|
+
|
|
279
|
+
_simple_client = Sector8SimpleClient(api_key=api_key, debug=debug, **kwargs)
|
|
280
|
+
return _simple_client
|
|
281
|
+
|
|
282
|
+
def get_simple_client() -> Sector8SimpleClient:
|
|
283
|
+
"""Get the configured simple client."""
|
|
284
|
+
global _simple_client
|
|
285
|
+
if _simple_client is None:
|
|
286
|
+
raise RuntimeError("Sector8 SDK not initialized. Call sector8.setup() first.")
|
|
287
|
+
return _simple_client
|
|
288
|
+
|
|
289
|
+
# Configuration wizard for guided setup
|
|
290
|
+
def configure_wizard(save_to_file: bool = True) -> Sector8SimpleClient:
|
|
291
|
+
"""
|
|
292
|
+
Run interactive configuration wizard for guided setup.
|
|
293
|
+
|
|
294
|
+
Usage:
|
|
295
|
+
import sector8
|
|
296
|
+
client = sector8.configure_wizard()
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Configured Sector8SimpleClient ready to use
|
|
300
|
+
"""
|
|
301
|
+
config = run_configuration_wizard(save_to_file=save_to_file)
|
|
302
|
+
|
|
303
|
+
# Create simple client from wizard configuration
|
|
304
|
+
return setup(
|
|
305
|
+
api_key=config.get('api_key'),
|
|
306
|
+
base_url=config.get('base_url')
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# LEGACY/ADVANCED API (backwards compatibility)
|
|
310
|
+
def configure(
|
|
311
|
+
client_id: str = None,
|
|
312
|
+
api_key: str = None,
|
|
313
|
+
endpoint: str = None,
|
|
314
|
+
**kwargs
|
|
315
|
+
):
|
|
316
|
+
"""Configure Sector8 SDK - redirects to simple client for enterprise v1.0.0."""
|
|
317
|
+
print("Advanced SDK features not available in enterprise v1.0.0. Using simple client.")
|
|
318
|
+
return setup(api_key=api_key, base_url=endpoint, client_id=client_id, **kwargs)
|
|
319
|
+
|
|
320
|
+
def get_client():
|
|
321
|
+
"""Get the configured SDK client instance - redirects to simple client."""
|
|
322
|
+
global _simple_client
|
|
323
|
+
if _simple_client is None:
|
|
324
|
+
raise RuntimeError("SDK not configured. Call sector8.setup() first.")
|
|
325
|
+
return _simple_client
|
|
326
|
+
|
|
327
|
+
def auto_instrument() -> None:
|
|
328
|
+
"""Auto-instrumentation not available in enterprise v1.0.0 - use decorator pattern instead."""
|
|
329
|
+
print("Auto-instrumentation not available in enterprise v1.0.0.")
|
|
330
|
+
print("Use @sector8.track() decorator or client.log_llm_call() for monitoring instead.")
|
|
331
|
+
print("Example: @sector8.track() above your LLM functions.")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
__all__ = [
|
|
335
|
+
# Enterprise Integration - Primary API for v1.0.0
|
|
336
|
+
"track", # @sector8.track() decorator
|
|
337
|
+
"setup", # 3-line integration
|
|
338
|
+
"get_simple_client",
|
|
339
|
+
"configure_wizard",
|
|
340
|
+
|
|
341
|
+
# Client Classes
|
|
342
|
+
"Sector8SimpleClient",
|
|
343
|
+
|
|
344
|
+
# Legacy API (redirects to simple client)
|
|
345
|
+
"configure",
|
|
346
|
+
"get_client",
|
|
347
|
+
"auto_instrument",
|
|
348
|
+
"run_configuration_wizard"
|
|
349
|
+
]
|