netra-sdk 0.1.41__py3-none-any.whl → 0.1.43__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 +2 -2
- netra/config.py +3 -1
- netra/session_manager.py +39 -11
- netra/utils.py +81 -0
- netra/version.py +1 -1
- {netra_sdk-0.1.41.dist-info → netra_sdk-0.1.43.dist-info}/METADATA +7 -5
- {netra_sdk-0.1.41.dist-info → netra_sdk-0.1.43.dist-info}/RECORD +9 -8
- {netra_sdk-0.1.41.dist-info → netra_sdk-0.1.43.dist-info}/WHEEL +1 -1
- {netra_sdk-0.1.41.dist-info → netra_sdk-0.1.43.dist-info/licenses}/LICENCE +0 -0
netra/__init__.py
CHANGED
|
@@ -249,13 +249,13 @@ class Netra:
|
|
|
249
249
|
logger.warning("Both event_name and attributes must be provided for custom events.")
|
|
250
250
|
|
|
251
251
|
@classmethod
|
|
252
|
-
def add_conversation(cls, conversation_type: ConversationType,
|
|
252
|
+
def add_conversation(cls, conversation_type: ConversationType, role: str, content: Any) -> None:
|
|
253
253
|
"""
|
|
254
254
|
Append a conversation entry and set span attribute 'conversation' as an array.
|
|
255
255
|
If a conversation array already exists for the current active span, this appends
|
|
256
256
|
to it; otherwise, it initializes a new array.
|
|
257
257
|
"""
|
|
258
|
-
SessionManager.add_conversation(conversation_type=conversation_type,
|
|
258
|
+
SessionManager.add_conversation(conversation_type=conversation_type, role=role, content=content)
|
|
259
259
|
|
|
260
260
|
@classmethod
|
|
261
261
|
def start_span(
|
netra/config.py
CHANGED
|
@@ -28,7 +28,9 @@ class Config:
|
|
|
28
28
|
LIBRARY_NAME = "netra"
|
|
29
29
|
LIBRARY_VERSION = __version__
|
|
30
30
|
# Maximum length for any attribute value (strings and bytes). Processors should honor this.
|
|
31
|
-
ATTRIBUTE_MAX_LEN =
|
|
31
|
+
ATTRIBUTE_MAX_LEN = 2000
|
|
32
|
+
# Maximum length specifically for conversation entry content (strings or JSON when serialized)
|
|
33
|
+
CONVERSATION_CONTENT_MAX_LEN = 1000
|
|
32
34
|
|
|
33
35
|
def __init__(
|
|
34
36
|
self,
|
netra/session_manager.py
CHANGED
|
@@ -13,6 +13,7 @@ from opentelemetry import context as otel_context
|
|
|
13
13
|
from opentelemetry import trace
|
|
14
14
|
|
|
15
15
|
from netra.config import Config
|
|
16
|
+
from netra.utils import process_content_for_max_len
|
|
16
17
|
|
|
17
18
|
logger = logging.getLogger(__name__)
|
|
18
19
|
|
|
@@ -20,7 +21,6 @@ logger = logging.getLogger(__name__)
|
|
|
20
21
|
class ConversationType(str, Enum):
|
|
21
22
|
INPUT = "input"
|
|
22
23
|
OUTPUT = "output"
|
|
23
|
-
SYSTEM = "system"
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class SessionManager:
|
|
@@ -262,7 +262,7 @@ class SessionManager:
|
|
|
262
262
|
logger.exception(f"Failed to add custom event: {name} - {e}")
|
|
263
263
|
|
|
264
264
|
@staticmethod
|
|
265
|
-
def add_conversation(conversation_type: ConversationType,
|
|
265
|
+
def add_conversation(conversation_type: ConversationType, role: str, content: Any) -> None:
|
|
266
266
|
"""
|
|
267
267
|
Append a conversation entry and set span attribute 'conversation' as an array.
|
|
268
268
|
|
|
@@ -278,20 +278,27 @@ class SessionManager:
|
|
|
278
278
|
raise TypeError("conversation_type must be a ConversationType enum value (input, output, system)")
|
|
279
279
|
normalized_type = conversation_type.value
|
|
280
280
|
|
|
281
|
-
if not isinstance(
|
|
282
|
-
raise TypeError(f"
|
|
283
|
-
if not field_name:
|
|
284
|
-
raise ValueError("field_name must be a non-empty string")
|
|
281
|
+
if not isinstance(role, str):
|
|
282
|
+
raise TypeError(f"role must be a string, got {type(role)}")
|
|
285
283
|
|
|
286
|
-
if not
|
|
287
|
-
raise
|
|
284
|
+
if not isinstance(content, (str, dict)):
|
|
285
|
+
raise TypeError(f"content must be a string or dict, got {type(content)}")
|
|
286
|
+
|
|
287
|
+
if not role:
|
|
288
|
+
raise ValueError("role must be a non-empty string")
|
|
289
|
+
|
|
290
|
+
if not content:
|
|
291
|
+
raise ValueError("content must not be empty")
|
|
288
292
|
|
|
289
293
|
try:
|
|
294
|
+
|
|
295
|
+
# Get active recording span
|
|
290
296
|
span = trace.get_current_span()
|
|
291
297
|
if not (span and getattr(span, "is_recording", lambda: False)()):
|
|
292
298
|
logger.warning("No active span to add conversation attribute.")
|
|
293
299
|
return
|
|
294
300
|
|
|
301
|
+
# Load existing conversation (JSON string -> list)
|
|
295
302
|
existing: List[Dict[str, Any]] = []
|
|
296
303
|
raw_data = None
|
|
297
304
|
|
|
@@ -301,10 +308,12 @@ class SessionManager:
|
|
|
301
308
|
raw_data = attrs.get("conversation")
|
|
302
309
|
except Exception:
|
|
303
310
|
logger.exception("Failed to retrieve conversation attribute")
|
|
311
|
+
|
|
304
312
|
if raw_data:
|
|
305
313
|
try:
|
|
306
314
|
import json
|
|
307
315
|
|
|
316
|
+
parsed: Any = None
|
|
308
317
|
if isinstance(raw_data, str):
|
|
309
318
|
parsed = json.loads(raw_data)
|
|
310
319
|
if isinstance(parsed, list):
|
|
@@ -312,11 +321,30 @@ class SessionManager:
|
|
|
312
321
|
except Exception:
|
|
313
322
|
existing = []
|
|
314
323
|
|
|
315
|
-
#
|
|
316
|
-
|
|
324
|
+
# Enforce per-entry content length limit without breaking the entire conversation structure
|
|
325
|
+
max_len = Config.CONVERSATION_CONTENT_MAX_LEN
|
|
326
|
+
processed_content = process_content_for_max_len(content, max_len)
|
|
327
|
+
|
|
328
|
+
# Create a conversation entry
|
|
329
|
+
entry: Dict[str, Any] = {"type": normalized_type, "role": role, "content": processed_content}
|
|
330
|
+
|
|
331
|
+
# Add format based on processed value type for backend parsing
|
|
332
|
+
if isinstance(processed_content, str):
|
|
333
|
+
entry["format"] = "text"
|
|
334
|
+
elif isinstance(processed_content, dict):
|
|
335
|
+
entry["format"] = "json"
|
|
317
336
|
existing.append(entry)
|
|
318
337
|
|
|
319
|
-
|
|
338
|
+
# Bypass global attribute value truncation by writing directly to the span's
|
|
339
|
+
# private attribute store. We intentionally avoid span.set_attribute here.
|
|
340
|
+
try:
|
|
341
|
+
import json
|
|
342
|
+
|
|
343
|
+
payload = json.dumps(existing, default=str)
|
|
344
|
+
attrs = getattr(span, "_attributes", None)
|
|
345
|
+
attrs["conversation"] = payload # type: ignore[index]
|
|
346
|
+
except Exception:
|
|
347
|
+
logger.exception("Failed to set conversation attribute directly on span")
|
|
320
348
|
except Exception as e:
|
|
321
349
|
logger.exception("Failed to add conversation attribute: %s", e)
|
|
322
350
|
|
netra/utils.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""General utility helpers for Netra SDK.
|
|
2
|
+
|
|
3
|
+
This module centralizes common helpers that can be reused across the codebase.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def truncate_string(value: str, max_len: int) -> str:
|
|
12
|
+
"""Truncate a string to max_len characters.
|
|
13
|
+
|
|
14
|
+
If value is not a string, it is returned unchanged.
|
|
15
|
+
"""
|
|
16
|
+
try:
|
|
17
|
+
if not isinstance(value, str):
|
|
18
|
+
return value
|
|
19
|
+
return value if len(value) <= max_len else value[:max_len]
|
|
20
|
+
except Exception:
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def truncate_and_repair_json(content: Any, max_len: int) -> Any:
|
|
25
|
+
"""Truncate a dict/list by JSON-serializing and hard-cutting, then attempt repair.
|
|
26
|
+
|
|
27
|
+
The function will:
|
|
28
|
+
- json.dumps(content, default=str)
|
|
29
|
+
- hard-cut the string to max_len
|
|
30
|
+
- try to repair using `json-repair` (optional dependency)
|
|
31
|
+
- parse back with json.loads
|
|
32
|
+
|
|
33
|
+
On failure, returns a minimal safe container with a preview of the truncated text.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
import json
|
|
37
|
+
|
|
38
|
+
json_str = json.dumps(content, default=str)
|
|
39
|
+
if len(json_str) <= max_len:
|
|
40
|
+
return content
|
|
41
|
+
|
|
42
|
+
truncated = json_str[:max_len]
|
|
43
|
+
|
|
44
|
+
# Try json_repair if available
|
|
45
|
+
repaired_obj: Any = None
|
|
46
|
+
try:
|
|
47
|
+
try:
|
|
48
|
+
from json_repair import repair_json as _repair_json
|
|
49
|
+
except Exception: # pragma: no cover - optional dependency not installed
|
|
50
|
+
_repair_json = None
|
|
51
|
+
|
|
52
|
+
if _repair_json is not None:
|
|
53
|
+
repaired_str = _repair_json(truncated)
|
|
54
|
+
repaired_obj = json.loads(repaired_str)
|
|
55
|
+
except Exception:
|
|
56
|
+
repaired_obj = None
|
|
57
|
+
|
|
58
|
+
if repaired_obj is not None:
|
|
59
|
+
return repaired_obj
|
|
60
|
+
|
|
61
|
+
# Fallback: safe container preserving a preview
|
|
62
|
+
return {"__truncated__": True, "preview": truncated}
|
|
63
|
+
except Exception:
|
|
64
|
+
# If anything goes wrong, return original content as-is
|
|
65
|
+
return content
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def process_content_for_max_len(content: Any, max_len: int) -> Any:
|
|
69
|
+
"""Ensure the content fits within max_len when serialized.
|
|
70
|
+
|
|
71
|
+
- If content is a string: truncate to max_len.
|
|
72
|
+
- If content is a dict or list: attempt truncate+repair to keep it valid JSON.
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
if isinstance(content, str):
|
|
76
|
+
return truncate_string(content, max_len)
|
|
77
|
+
if isinstance(content, (dict, list)):
|
|
78
|
+
return truncate_and_repair_json(content, max_len)
|
|
79
|
+
return content
|
|
80
|
+
except Exception:
|
|
81
|
+
return content
|
netra/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.43"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: netra-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.43
|
|
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
|
-
License: Apache-2.0
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
License-File: LICENCE
|
|
6
7
|
Keywords: netra,tracing,observability,sdk,ai,llm,vector,database
|
|
7
8
|
Author: Sooraj Thomas
|
|
8
9
|
Author-email: sooraj@keyvalue.systems
|
|
@@ -19,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
19
20
|
Classifier: Typing :: Typed
|
|
20
21
|
Provides-Extra: llm-guard
|
|
21
22
|
Provides-Extra: presidio
|
|
23
|
+
Requires-Dist: json-repair (==0.44.1)
|
|
22
24
|
Requires-Dist: llm-guard (==0.3.16) ; extra == "llm-guard"
|
|
23
25
|
Requires-Dist: opentelemetry-api (>=1.34.0,<2.0.0)
|
|
24
26
|
Requires-Dist: opentelemetry-instrumentation-aio-pika (>=0.55b1,<1.0.0)
|
|
@@ -73,8 +75,8 @@ Requires-Dist: stanza (>=1.10.1,<2.0.0) ; extra == "presidio"
|
|
|
73
75
|
Requires-Dist: traceloop-sdk (>=0.40.7,<0.43.0)
|
|
74
76
|
Requires-Dist: transformers (==4.51.3) ; extra == "presidio"
|
|
75
77
|
Project-URL: Changelog, https://github.com/KeyValueSoftwareSystems/netra-sdk-py/blob/main/CHANGELOG.md
|
|
76
|
-
Project-URL: Documentation, https://
|
|
77
|
-
Project-URL: Homepage, https://
|
|
78
|
+
Project-URL: Documentation, https://docs.getnetra.ai/introduction
|
|
79
|
+
Project-URL: Homepage, https://getnetra.ai/
|
|
78
80
|
Project-URL: Repository, https://github.com/KeyValueSoftwareSystems/netra-sdk-py
|
|
79
81
|
Description-Content-Type: text/markdown
|
|
80
82
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
netra/__init__.py,sha256
|
|
1
|
+
netra/__init__.py,sha256=-eEBq3ndX3JAWDo4_Ra0L19KrTfrJhNDjlAwChb3_IA,10353
|
|
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=
|
|
6
|
+
netra/config.py,sha256=MMSAKrX_HCZ7QECjTocs_jsNFgcOnbP8aAVfOdq9YEs,7032
|
|
7
7
|
netra/decorators.py,sha256=qZFHrwdj10FsTFqggo3XjdGB12aMxsrrDMMmslDqZ-0,17424
|
|
8
8
|
netra/exceptions/__init__.py,sha256=uDgcBxmC4WhdS7HRYQk_TtJyxH1s1o6wZmcsnSHLAcM,174
|
|
9
9
|
netra/exceptions/injection.py,sha256=ke4eUXRYUFJkMZgdSyPPkPt5PdxToTI6xLEBI0hTWUQ,1332
|
|
@@ -45,11 +45,12 @@ netra/processors/instrumentation_span_processor.py,sha256=VzurzwtGleFltxzKD_gjVk
|
|
|
45
45
|
netra/processors/scrubbing_span_processor.py,sha256=dJ86Ncmjvmrhm_uAdGTwcGvRpZbVVWqD9AOFwEMWHZY,6701
|
|
46
46
|
netra/processors/session_span_processor.py,sha256=qcsBl-LnILWefsftI8NQhXDGb94OWPc8LvzhVA0JS_c,2432
|
|
47
47
|
netra/scanner.py,sha256=kyDpeZiscCPb6pjuhS-sfsVj-dviBFRepdUWh0sLoEY,11554
|
|
48
|
-
netra/session_manager.py,sha256=
|
|
48
|
+
netra/session_manager.py,sha256=VzmSAiP63ODCuOWv-irsxyU2LvHoqjOBUuXtyxboBU0,13740
|
|
49
49
|
netra/span_wrapper.py,sha256=IygQX78xQRlL_Z1MfKfUbv0okihx92qNClnRlYFtRNc,8004
|
|
50
50
|
netra/tracer.py,sha256=FJO8Cine-WL9K_4wn6RVjQOgX6c1JCp_8QowUbRSVHk,7718
|
|
51
|
-
netra/
|
|
52
|
-
|
|
53
|
-
netra_sdk-0.1.
|
|
54
|
-
netra_sdk-0.1.
|
|
55
|
-
netra_sdk-0.1.
|
|
51
|
+
netra/utils.py,sha256=FblSzI8qMTfEbusakGBKE9CNELW0GEBHl09mPPxgI-w,2521
|
|
52
|
+
netra/version.py,sha256=PwEipAcIgfEks4UpVgOERoR8Az-FCfCGocVnb0rkncM,23
|
|
53
|
+
netra_sdk-0.1.43.dist-info/METADATA,sha256=3IMsH-b9Wcy9tMvG62MgOa3Zj76kidZbt1biWT3jyj0,28208
|
|
54
|
+
netra_sdk-0.1.43.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
|
|
55
|
+
netra_sdk-0.1.43.dist-info/licenses/LICENCE,sha256=8B_UoZ-BAl0AqiHAHUETCgd3I2B9yYJ1WEQtVb_qFMA,11359
|
|
56
|
+
netra_sdk-0.1.43.dist-info/RECORD,,
|
|
File without changes
|