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 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, field_name: str, value: Any) -> None:
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, field_name=field_name, value=value)
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 = 1000
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, field_name: str, value: Any) -> None:
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(field_name, str):
282
- raise TypeError(f"field_name must be a string, got {type(field_name)}")
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 value:
287
- raise ValueError("value must not be empty")
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
- # Append new entry
316
- entry: Dict[str, Any] = {"type": normalized_type, "field_name": field_name, "value": value}
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
- SessionManager.set_attribute_on_active_span("conversation", existing)
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.41"
1
+ __version__ = "0.1.43"
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: netra-sdk
3
- Version: 0.1.41
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://github.com/KeyValueSoftwareSystems/netra-sdk-py/blob/main/README.md
77
- Project-URL: Homepage, https://github.com/KeyValueSoftwareSystems/netra-sdk-py
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=imPVPlcpM85yPJNXMexewJ7p30blBbbY6hn8_lOrjlA,10365
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=51m8R0NoOrw58gMV7arOniEuFdJ7EIu3PNdFtIQ5xfg,6893
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=wRGaZZQr1-OeG2ZpuiOAT_A53sQaLa-KHECZCXEc6gg,12492
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/version.py,sha256=y9uk7vaddYq7x48m1qAG2qgbHu814vUzjs_fjnrQ3Es,23
52
- netra_sdk-0.1.41.dist-info/LICENCE,sha256=8B_UoZ-BAl0AqiHAHUETCgd3I2B9yYJ1WEQtVb_qFMA,11359
53
- netra_sdk-0.1.41.dist-info/METADATA,sha256=nhjMP6HyrFA9ZgJ3sF_lOywJKX8InO5Y75MmhDUah0c,28210
54
- netra_sdk-0.1.41.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
55
- netra_sdk-0.1.41.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: poetry-core 2.2.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any