netra-sdk 0.1.45__py3-none-any.whl → 0.1.46__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.
Potentially problematic release.
This version of netra-sdk might be problematic. Click here for more details.
- netra/__init__.py +5 -4
- netra/config.py +1 -1
- netra/decorators.py +38 -8
- netra/processors/instrumentation_span_processor.py +0 -3
- netra/session_manager.py +12 -5
- netra/span_wrapper.py +21 -1
- netra/version.py +1 -1
- {netra_sdk-0.1.45.dist-info → netra_sdk-0.1.46.dist-info}/METADATA +1 -1
- {netra_sdk-0.1.45.dist-info → netra_sdk-0.1.46.dist-info}/RECORD +11 -11
- {netra_sdk-0.1.45.dist-info → netra_sdk-0.1.46.dist-info}/WHEEL +0 -0
- {netra_sdk-0.1.45.dist-info → netra_sdk-0.1.46.dist-info}/licenses/LICENCE +0 -0
netra/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import atexit
|
|
2
2
|
import logging
|
|
3
3
|
import threading
|
|
4
|
-
from typing import Any, Dict, List, Optional, Set
|
|
4
|
+
from typing import Any, Dict, List, Literal, Optional, Set
|
|
5
5
|
|
|
6
6
|
from opentelemetry import context as context_api
|
|
7
7
|
from opentelemetry import trace
|
|
@@ -14,7 +14,7 @@ from .config import Config
|
|
|
14
14
|
# Instrumentor functions
|
|
15
15
|
from .instrumentation import init_instrumentations
|
|
16
16
|
from .session_manager import ConversationType, SessionManager
|
|
17
|
-
from .span_wrapper import ActionModel, SpanWrapper, UsageModel
|
|
17
|
+
from .span_wrapper import ActionModel, SpanType, SpanWrapper, UsageModel
|
|
18
18
|
from .tracer import Tracer
|
|
19
19
|
|
|
20
20
|
# Package-level logger. Attach NullHandler by default so library does not emit logs
|
|
@@ -263,11 +263,12 @@ class Netra:
|
|
|
263
263
|
name: str,
|
|
264
264
|
attributes: Optional[Dict[str, str]] = None,
|
|
265
265
|
module_name: str = "combat_sdk",
|
|
266
|
+
as_type: Optional[SpanType] = SpanType.SPAN,
|
|
266
267
|
) -> SpanWrapper:
|
|
267
268
|
"""
|
|
268
269
|
Start a new session.
|
|
269
270
|
"""
|
|
270
|
-
return SpanWrapper(name, attributes, module_name)
|
|
271
|
+
return SpanWrapper(name, attributes, module_name, as_type=as_type)
|
|
271
272
|
|
|
272
273
|
|
|
273
|
-
__all__ = ["Netra", "UsageModel", "ActionModel"]
|
|
274
|
+
__all__ = ["Netra", "UsageModel", "ActionModel", "SpanType"]
|
netra/config.py
CHANGED
|
@@ -30,7 +30,7 @@ class Config:
|
|
|
30
30
|
# Maximum length for any attribute value (strings and bytes). Processors should honor this.
|
|
31
31
|
ATTRIBUTE_MAX_LEN = 2000
|
|
32
32
|
# Maximum length specifically for conversation entry content (strings or JSON when serialized)
|
|
33
|
-
CONVERSATION_CONTENT_MAX_LEN =
|
|
33
|
+
CONVERSATION_CONTENT_MAX_LEN = 2000
|
|
34
34
|
|
|
35
35
|
def __init__(
|
|
36
36
|
self,
|
netra/decorators.py
CHANGED
|
@@ -33,6 +33,7 @@ from opentelemetry import trace
|
|
|
33
33
|
|
|
34
34
|
from .config import Config
|
|
35
35
|
from .session_manager import SessionManager
|
|
36
|
+
from .span_wrapper import SpanType
|
|
36
37
|
|
|
37
38
|
logger = logging.getLogger(__name__)
|
|
38
39
|
|
|
@@ -262,7 +263,12 @@ def _wrap_streaming_response_with_span(
|
|
|
262
263
|
return resp
|
|
263
264
|
|
|
264
265
|
|
|
265
|
-
def _create_function_wrapper(
|
|
266
|
+
def _create_function_wrapper(
|
|
267
|
+
func: Callable[P, R],
|
|
268
|
+
entity_type: str,
|
|
269
|
+
name: Optional[str] = None,
|
|
270
|
+
as_type: Optional[SpanType] = SpanType.SPAN,
|
|
271
|
+
) -> Callable[P, R]:
|
|
266
272
|
module_name = func.__name__
|
|
267
273
|
is_async = inspect.iscoroutinefunction(func)
|
|
268
274
|
span_name = name if name is not None else func.__name__
|
|
@@ -276,6 +282,14 @@ def _create_function_wrapper(func: Callable[P, R], entity_type: str, name: Optio
|
|
|
276
282
|
|
|
277
283
|
tracer = trace.get_tracer(module_name)
|
|
278
284
|
span = tracer.start_span(span_name)
|
|
285
|
+
# Set span type if provided
|
|
286
|
+
|
|
287
|
+
if not isinstance(as_type, SpanType):
|
|
288
|
+
raise ValueError(f"Invalid span type: {as_type}")
|
|
289
|
+
try:
|
|
290
|
+
span.set_attribute("netra.span.type", as_type.value)
|
|
291
|
+
except Exception:
|
|
292
|
+
pass
|
|
279
293
|
# Register and activate span
|
|
280
294
|
try:
|
|
281
295
|
SessionManager.register_span(span_name, span)
|
|
@@ -327,6 +341,14 @@ def _create_function_wrapper(func: Callable[P, R], entity_type: str, name: Optio
|
|
|
327
341
|
|
|
328
342
|
tracer = trace.get_tracer(module_name)
|
|
329
343
|
span = tracer.start_span(span_name)
|
|
344
|
+
# Set span type if provided
|
|
345
|
+
if as_type is not None:
|
|
346
|
+
if not isinstance(as_type, SpanType):
|
|
347
|
+
raise ValueError(f"Invalid span type: {as_type}")
|
|
348
|
+
try:
|
|
349
|
+
span.set_attribute("netra.span.type", as_type.value)
|
|
350
|
+
except Exception:
|
|
351
|
+
pass
|
|
330
352
|
# Register and activate span
|
|
331
353
|
try:
|
|
332
354
|
SessionManager.register_span(span_name, span)
|
|
@@ -370,7 +392,12 @@ def _create_function_wrapper(func: Callable[P, R], entity_type: str, name: Optio
|
|
|
370
392
|
return cast(Callable[P, R], sync_wrapper)
|
|
371
393
|
|
|
372
394
|
|
|
373
|
-
def _wrap_class_methods(
|
|
395
|
+
def _wrap_class_methods(
|
|
396
|
+
cls: C,
|
|
397
|
+
entity_type: str,
|
|
398
|
+
name: Optional[str] = None,
|
|
399
|
+
as_type: Optional[SpanType] = SpanType.SPAN,
|
|
400
|
+
) -> C:
|
|
374
401
|
class_name = name if name is not None else cls.__name__
|
|
375
402
|
for attr_name in cls.__dict__:
|
|
376
403
|
attr = getattr(cls, attr_name)
|
|
@@ -378,7 +405,7 @@ def _wrap_class_methods(cls: C, entity_type: str, name: Optional[str] = None) ->
|
|
|
378
405
|
continue
|
|
379
406
|
if callable(attr) and inspect.isfunction(attr):
|
|
380
407
|
method_span_name = f"{class_name}.{attr_name}"
|
|
381
|
-
wrapped_method = _create_function_wrapper(attr, entity_type, method_span_name)
|
|
408
|
+
wrapped_method = _create_function_wrapper(attr, entity_type, method_span_name, as_type=as_type)
|
|
382
409
|
setattr(cls, attr_name, wrapped_method)
|
|
383
410
|
return cls
|
|
384
411
|
|
|
@@ -416,10 +443,10 @@ def task(
|
|
|
416
443
|
) -> Union[Callable[P, R], C, Callable[[Callable[P, R]], Callable[P, R]]]:
|
|
417
444
|
def decorator(obj: Union[Callable[P, R], C]) -> Union[Callable[P, R], C]:
|
|
418
445
|
if inspect.isclass(obj):
|
|
419
|
-
return _wrap_class_methods(cast(C, obj), "task", name)
|
|
446
|
+
return _wrap_class_methods(cast(C, obj), "task", name, as_type=SpanType.TOOL)
|
|
420
447
|
else:
|
|
421
448
|
# When obj is a function, it should be type Callable[P, R]
|
|
422
|
-
return _create_function_wrapper(cast(Callable[P, R], obj), "task", name)
|
|
449
|
+
return _create_function_wrapper(cast(Callable[P, R], obj), "task", name, as_type=SpanType.TOOL)
|
|
423
450
|
|
|
424
451
|
if target is not None:
|
|
425
452
|
return decorator(target)
|
|
@@ -427,14 +454,17 @@ def task(
|
|
|
427
454
|
|
|
428
455
|
|
|
429
456
|
def span(
|
|
430
|
-
target: Union[Callable[P, R], C, None] = None,
|
|
457
|
+
target: Union[Callable[P, R], C, None] = None,
|
|
458
|
+
*,
|
|
459
|
+
name: Optional[str] = None,
|
|
460
|
+
as_type: Optional[SpanType] = SpanType.SPAN,
|
|
431
461
|
) -> Union[Callable[P, R], C, Callable[[Callable[P, R]], Callable[P, R]]]:
|
|
432
462
|
def decorator(obj: Union[Callable[P, R], C]) -> Union[Callable[P, R], C]:
|
|
433
463
|
if inspect.isclass(obj):
|
|
434
|
-
return _wrap_class_methods(cast(C, obj), "span", name)
|
|
464
|
+
return _wrap_class_methods(cast(C, obj), "span", name, as_type=as_type)
|
|
435
465
|
else:
|
|
436
466
|
# When obj is a function, it should be type Callable[P, R]
|
|
437
|
-
return _create_function_wrapper(cast(Callable[P, R], obj), "span", name)
|
|
467
|
+
return _create_function_wrapper(cast(Callable[P, R], obj), "span", name, as_type=as_type)
|
|
438
468
|
|
|
439
469
|
if target is not None:
|
|
440
470
|
return decorator(target)
|
|
@@ -74,9 +74,6 @@ class InstrumentationSpanProcessor(SpanProcessor): # type: ignore[misc]
|
|
|
74
74
|
truncated = self._truncate_value(value)
|
|
75
75
|
# Forward to original
|
|
76
76
|
original_set_attribute(key, truncated)
|
|
77
|
-
# Special rule: if model key set, mark span as llm
|
|
78
|
-
if key == "gen_ai.request.model":
|
|
79
|
-
original_set_attribute(f"{Config.LIBRARY_NAME}.span.type", "llm")
|
|
80
77
|
except Exception:
|
|
81
78
|
# Best-effort; never break span
|
|
82
79
|
try:
|
netra/session_manager.py
CHANGED
|
@@ -275,20 +275,27 @@ class SessionManager:
|
|
|
275
275
|
|
|
276
276
|
# Hard runtime validation of input types and values
|
|
277
277
|
if not isinstance(conversation_type, ConversationType):
|
|
278
|
-
|
|
278
|
+
logger.error(
|
|
279
|
+
"add_conversation: conversation_type must be a ConversationType enum value (input, output, system)"
|
|
280
|
+
)
|
|
281
|
+
return
|
|
279
282
|
normalized_type = conversation_type.value
|
|
280
283
|
|
|
281
284
|
if not isinstance(role, str):
|
|
282
|
-
|
|
285
|
+
logger.error("add_conversation: role must be a string")
|
|
286
|
+
return
|
|
283
287
|
|
|
284
288
|
if not isinstance(content, (str, dict)):
|
|
285
|
-
|
|
289
|
+
logger.error("add_conversation: content must be a string or dict")
|
|
290
|
+
return
|
|
286
291
|
|
|
287
292
|
if not role:
|
|
288
|
-
|
|
293
|
+
logger.error("add_conversation: role must be a non-empty string")
|
|
294
|
+
return
|
|
289
295
|
|
|
290
296
|
if not content:
|
|
291
|
-
|
|
297
|
+
logger.error("add_conversation: content must not be empty")
|
|
298
|
+
return
|
|
292
299
|
|
|
293
300
|
try:
|
|
294
301
|
|
netra/span_wrapper.py
CHANGED
|
@@ -2,6 +2,7 @@ import json
|
|
|
2
2
|
import logging
|
|
3
3
|
import time
|
|
4
4
|
from datetime import datetime
|
|
5
|
+
from enum import Enum
|
|
5
6
|
from typing import Any, Dict, List, Literal, Optional
|
|
6
7
|
|
|
7
8
|
from opentelemetry import baggage
|
|
@@ -49,6 +50,13 @@ class ATTRIBUTE:
|
|
|
49
50
|
ACTION = "action"
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
class SpanType(str, Enum):
|
|
54
|
+
SPAN = "SPAN"
|
|
55
|
+
GENERATION = "GENERATION"
|
|
56
|
+
TOOL = "TOOL"
|
|
57
|
+
EMBEDDING = "EMBEDDING"
|
|
58
|
+
|
|
59
|
+
|
|
52
60
|
class SpanWrapper:
|
|
53
61
|
"""
|
|
54
62
|
Context manager for tracking observability data for external API calls.
|
|
@@ -63,9 +71,16 @@ class SpanWrapper:
|
|
|
63
71
|
span.set_usage(usage_data)
|
|
64
72
|
"""
|
|
65
73
|
|
|
66
|
-
def __init__(
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
name: str,
|
|
77
|
+
attributes: Optional[Dict[str, str]] = None,
|
|
78
|
+
module_name: str = "combat_sdk",
|
|
79
|
+
as_type: Optional[SpanType] = SpanType.SPAN,
|
|
80
|
+
):
|
|
67
81
|
self.name = name
|
|
68
82
|
self.attributes = attributes or {}
|
|
83
|
+
|
|
69
84
|
self.start_time: Optional[float] = None
|
|
70
85
|
self.end_time: Optional[float] = None
|
|
71
86
|
self.status = "pending"
|
|
@@ -80,6 +95,11 @@ class SpanWrapper:
|
|
|
80
95
|
# Token for locally attached baggage (if any)
|
|
81
96
|
self._local_block_token: Optional[object] = None
|
|
82
97
|
|
|
98
|
+
if isinstance(as_type, SpanType):
|
|
99
|
+
self.attributes["netra.span.type"] = as_type.value
|
|
100
|
+
else:
|
|
101
|
+
raise ValueError(f"Invalid span type: {as_type}")
|
|
102
|
+
|
|
83
103
|
def __enter__(self) -> "SpanWrapper":
|
|
84
104
|
"""Start the span wrapper, begin time tracking, and create OpenTelemetry span."""
|
|
85
105
|
self.start_time = time.time()
|
netra/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.46"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: netra-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.46
|
|
4
4
|
Summary: A Python SDK for AI application observability that provides OpenTelemetry-based monitoring, tracing, and PII protection for LLM and vector database applications. Enables easy instrumentation, session tracking, and privacy-focused data collection for AI systems in production environments.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENCE
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
netra/__init__.py,sha256
|
|
1
|
+
netra/__init__.py,sha256=tbj0UsUHKMG8RHJ58Y0tCvPVDS_XkGb7sF_xM1lxJEY,10454
|
|
2
2
|
netra/anonymizer/__init__.py,sha256=KeGPPZqKVZbtkbirEKYTYhj6aZHlakjdQhD7QHqBRio,133
|
|
3
3
|
netra/anonymizer/anonymizer.py,sha256=IcrYkdwWrFauGWUeAW-0RwrSUM8VSZCFNtoywZhvIqU,3778
|
|
4
4
|
netra/anonymizer/base.py,sha256=ytPxHCUD2OXlEY6fNTuMmwImNdIjgj294I41FIgoXpU,5946
|
|
5
5
|
netra/anonymizer/fp_anonymizer.py,sha256=_6svIYmE0eejdIMkhKBUWCNjGtGimtrGtbLvPSOp8W4,6493
|
|
6
|
-
netra/config.py,sha256=
|
|
7
|
-
netra/decorators.py,sha256=
|
|
6
|
+
netra/config.py,sha256=RqLul2vlNHHKGU31OQIJTUpcKMSmVse9IlGFwImW6sE,7032
|
|
7
|
+
netra/decorators.py,sha256=NHJZeZo3LEuOxCz-lfz3j74o6CS0jS8JBRw2LvoeeWE,18389
|
|
8
8
|
netra/exceptions/__init__.py,sha256=uDgcBxmC4WhdS7HRYQk_TtJyxH1s1o6wZmcsnSHLAcM,174
|
|
9
9
|
netra/exceptions/injection.py,sha256=ke4eUXRYUFJkMZgdSyPPkPt5PdxToTI6xLEBI0hTWUQ,1332
|
|
10
10
|
netra/exceptions/pii.py,sha256=MT4p_x-zH3VtYudTSxw1Z9qQZADJDspq64WrYqSWlZc,2438
|
|
@@ -43,17 +43,17 @@ netra/instrumentation/weaviate/__init__.py,sha256=EOlpWxobOLHYKqo_kMct_7nu26x1hr
|
|
|
43
43
|
netra/instrumentation/weaviate/version.py,sha256=PiCZHjonujPbnIn0KmD3Yl68hrjPRG_oKe5vJF3mmG8,24
|
|
44
44
|
netra/pii.py,sha256=Rn4SjgTJW_aw9LcbjLuMqF3fKd9b1ndlYt1CaK51Ge0,33125
|
|
45
45
|
netra/processors/__init__.py,sha256=56RO7xMoBx2aoVuESyQXll65o7IoO98nSHZ5KpR5iQk,471
|
|
46
|
-
netra/processors/instrumentation_span_processor.py,sha256=
|
|
46
|
+
netra/processors/instrumentation_span_processor.py,sha256=7iDnJUSBXyiRcWxoxk3uStoh0r4Yxjh7PBTbFDVMjlA,4150
|
|
47
47
|
netra/processors/local_filtering_span_processor.py,sha256=Vk_UP--ZxWwdrbJA7a95iRo853QfPCME2C5ChgjiHl8,6664
|
|
48
48
|
netra/processors/scrubbing_span_processor.py,sha256=dJ86Ncmjvmrhm_uAdGTwcGvRpZbVVWqD9AOFwEMWHZY,6701
|
|
49
49
|
netra/processors/session_span_processor.py,sha256=qcsBl-LnILWefsftI8NQhXDGb94OWPc8LvzhVA0JS_c,2432
|
|
50
50
|
netra/scanner.py,sha256=kyDpeZiscCPb6pjuhS-sfsVj-dviBFRepdUWh0sLoEY,11554
|
|
51
|
-
netra/session_manager.py,sha256=
|
|
52
|
-
netra/span_wrapper.py,sha256=
|
|
51
|
+
netra/session_manager.py,sha256=jxA62zAgiTsuTyV_UuPkVb2W1Uieb7I5jWsYiJ6jSxk,13897
|
|
52
|
+
netra/span_wrapper.py,sha256=80-0MkAw7AOQg0u7dj2YfXfnuUQDE8ymsGNzh6y6JtA,10102
|
|
53
53
|
netra/tracer.py,sha256=IPVYMc4x74XZJeKvCfV6b0X9mUHGvsssUzLnRvFPV6M,4629
|
|
54
54
|
netra/utils.py,sha256=FblSzI8qMTfEbusakGBKE9CNELW0GEBHl09mPPxgI-w,2521
|
|
55
|
-
netra/version.py,sha256=
|
|
56
|
-
netra_sdk-0.1.
|
|
57
|
-
netra_sdk-0.1.
|
|
58
|
-
netra_sdk-0.1.
|
|
59
|
-
netra_sdk-0.1.
|
|
55
|
+
netra/version.py,sha256=X3e_85I7oZGwZD8nW9SBKEUbQU7-_3W9FXuicrfxHjc,23
|
|
56
|
+
netra_sdk-0.1.46.dist-info/METADATA,sha256=QWfOewRPrRyNlamWqdvuS-1U01UocBvxcRZQ_H6Qmvw,28208
|
|
57
|
+
netra_sdk-0.1.46.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
58
|
+
netra_sdk-0.1.46.dist-info/licenses/LICENCE,sha256=8B_UoZ-BAl0AqiHAHUETCgd3I2B9yYJ1WEQtVb_qFMA,11359
|
|
59
|
+
netra_sdk-0.1.46.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|