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 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
+ ]