netra-sdk 0.1.38__tar.gz → 0.1.40__tar.gz

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.

Files changed (56) hide show
  1. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/PKG-INFO +1 -1
  2. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/__init__.py +11 -40
  3. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/config.py +7 -0
  4. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/processors/instrumentation_span_processor.py +0 -1
  5. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/session_manager.py +53 -80
  6. netra_sdk-0.1.40/netra/version.py +1 -0
  7. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/pyproject.toml +1 -1
  8. netra_sdk-0.1.38/netra/version.py +0 -1
  9. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/LICENCE +0 -0
  10. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/README.md +0 -0
  11. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/anonymizer/__init__.py +0 -0
  12. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/anonymizer/anonymizer.py +0 -0
  13. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/anonymizer/base.py +0 -0
  14. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/anonymizer/fp_anonymizer.py +0 -0
  15. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/decorators.py +0 -0
  16. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/exceptions/__init__.py +0 -0
  17. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/exceptions/injection.py +0 -0
  18. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/exceptions/pii.py +0 -0
  19. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/input_scanner.py +0 -0
  20. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/__init__.py +0 -0
  21. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/aiohttp/__init__.py +0 -0
  22. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/aiohttp/version.py +0 -0
  23. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/cohere/__init__.py +0 -0
  24. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/cohere/version.py +0 -0
  25. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/fastapi/__init__.py +0 -0
  26. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/fastapi/version.py +0 -0
  27. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/__init__.py +0 -0
  28. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/config.py +0 -0
  29. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/utils.py +0 -0
  30. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/version.py +0 -0
  31. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/httpx/__init__.py +0 -0
  32. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/httpx/version.py +0 -0
  33. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/instruments.py +0 -0
  34. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/litellm/__init__.py +0 -0
  35. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/litellm/version.py +0 -0
  36. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/litellm/wrappers.py +0 -0
  37. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/__init__.py +0 -0
  38. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/config.py +0 -0
  39. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/utils.py +0 -0
  40. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/version.py +0 -0
  41. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/openai/__init__.py +0 -0
  42. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/openai/version.py +0 -0
  43. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/openai/wrappers.py +0 -0
  44. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/__init__.py +0 -0
  45. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/utils.py +0 -0
  46. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/version.py +0 -0
  47. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/wrappers.py +0 -0
  48. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/weaviate/__init__.py +0 -0
  49. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/weaviate/version.py +0 -0
  50. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/pii.py +0 -0
  51. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/processors/__init__.py +0 -0
  52. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/processors/scrubbing_span_processor.py +0 -0
  53. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/processors/session_span_processor.py +0 -0
  54. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/scanner.py +0 -0
  55. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/span_wrapper.py +0 -0
  56. {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/tracer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: netra-sdk
3
- Version: 0.1.38
3
+ Version: 0.1.40
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: Apache-2.0
6
6
  Keywords: netra,tracing,observability,sdk,ai,llm,vector,database
@@ -9,11 +9,11 @@ from opentelemetry.trace import SpanKind
9
9
 
10
10
  from netra.instrumentation.instruments import InstrumentSet, NetraInstruments
11
11
 
12
- from .config import Config
12
+ from .config import Config, ConversationType
13
13
 
14
14
  # Instrumentor functions
15
15
  from .instrumentation import init_instrumentations
16
- from .session_manager import SessionManager
16
+ from .session_manager import ConversationType, SessionManager
17
17
  from .span_wrapper import ActionModel, SpanWrapper, UsageModel
18
18
  from .tracer import Tracer
19
19
 
@@ -248,6 +248,15 @@ class Netra:
248
248
  else:
249
249
  logger.warning("Both event_name and attributes must be provided for custom events.")
250
250
 
251
+ @classmethod
252
+ def add_conversation(cls, type: ConversationType, field_name: str, value: Any) -> None:
253
+ """
254
+ Append a conversation entry and set span attribute 'conversation' as an array.
255
+ If a conversation array already exists for the current active span, this appends
256
+ to it; otherwise, it initializes a new array.
257
+ """
258
+ SessionManager.add_conversation(type=type, field_name=field_name, value=value)
259
+
251
260
  @classmethod
252
261
  def start_span(
253
262
  cls,
@@ -260,43 +269,5 @@ class Netra:
260
269
  """
261
270
  return SpanWrapper(name, attributes, module_name)
262
271
 
263
- @classmethod
264
- def set_input(cls, value: Any, span_name: Optional[str] = None) -> None:
265
- """
266
- Set custom attribute `netra.span.input` on a target span.
267
-
268
- Args:
269
- value: Input payload to record (string or JSON-serializable object)
270
- span_name: Optional. When provided, sets the attribute on the span registered
271
- with this name. Otherwise sets on the active span.
272
- """
273
- SessionManager.set_attribute_on_target_span(f"{Config.LIBRARY_NAME}.span.input", value, span_name)
274
-
275
- @classmethod
276
- def set_output(cls, value: Any, span_name: Optional[str] = None) -> None:
277
- """
278
- Set custom attribute `netra.span.output` on a target span.
279
-
280
- Args:
281
- value: Output payload to record (string or JSON-serializable object)
282
- span_name: Optional. When provided, sets the attribute on the span registered
283
- with this name. Otherwise sets on the active span.
284
- """
285
- if value:
286
- SessionManager.set_attribute_on_target_span(f"{Config.LIBRARY_NAME}.span.output", value, span_name)
287
-
288
- @classmethod
289
- def set_prompt(cls, value: Any, span_name: Optional[str] = None) -> None:
290
- """
291
- Set custom attribute `netra.span.prompt` on a target span.
292
-
293
- Args:
294
- value: Prompt payload to record (string or JSON-serializable object)
295
- span_name: Optional. When provided, sets the attribute on the span registered
296
- with this name. Otherwise sets on the active span.
297
- """
298
- if value:
299
- SessionManager.set_attribute_on_target_span(f"{Config.LIBRARY_NAME}.span.prompt", value, span_name)
300
-
301
272
 
302
273
  __all__ = ["Netra", "UsageModel", "ActionModel"]
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import os
3
+ from enum import Enum
3
4
  from typing import Any, Dict, List, Optional
4
5
 
5
6
  from opentelemetry.util.re import parse_env_headers
@@ -7,6 +8,12 @@ from opentelemetry.util.re import parse_env_headers
7
8
  from netra.version import __version__
8
9
 
9
10
 
11
+ class ConversationType(str, Enum):
12
+ INPUT = "input"
13
+ OUTPUT = "output"
14
+ SYSTEM = "system"
15
+
16
+
10
17
  class Config:
11
18
  """
12
19
  Holds configuration options for the tracer:
@@ -89,7 +89,6 @@ class InstrumentationSpanProcessor(SpanProcessor): # type: ignore[misc]
89
89
 
90
90
  # Set this span's instrumentation name
91
91
  name = self._detect_raw_instrumentation_name(span)
92
- print(name)
93
92
  if name in ALLOWED_INSTRUMENTATION_NAMES:
94
93
  span.set_attribute(f"{Config.LIBRARY_NAME}.instrumentation.name", name)
95
94
  except Exception:
@@ -5,17 +5,24 @@ Handles automatic session and user ID management for applications.
5
5
 
6
6
  import logging
7
7
  from datetime import datetime
8
+ from enum import Enum
8
9
  from typing import Any, Dict, List, Optional, Union
9
10
 
10
11
  from opentelemetry import baggage
11
12
  from opentelemetry import context as otel_context
12
13
  from opentelemetry import trace
13
14
 
14
- from .config import Config
15
+ from netra.config import Config
15
16
 
16
17
  logger = logging.getLogger(__name__)
17
18
 
18
19
 
20
+ class ConversationType(str, Enum):
21
+ INPUT = "input"
22
+ OUTPUT = "output"
23
+ SYSTEM = "system"
24
+
25
+
19
26
  class SessionManager:
20
27
  """Manages session and user context for applications."""
21
28
 
@@ -225,26 +232,7 @@ class SessionManager:
225
232
  ctx = baggage.set_baggage("user_id", value, ctx)
226
233
  elif session_key == "tenant_id":
227
234
  ctx = baggage.set_baggage("tenant_id", value, ctx)
228
- elif isinstance(value, dict) and value:
229
- if session_key == "custom_attributes":
230
- custom_keys = list(value.keys())
231
- ctx = baggage.set_baggage("custom_keys", ",".join(custom_keys), ctx)
232
- for key, val in value.items():
233
- ctx = baggage.set_baggage(f"custom.{key}", str(val), ctx)
234
-
235
- # Decide whether to attach globally. We only attach if there is no
236
- # active recording span (safe point) or if the caller forces it.
237
- current_span = trace.get_current_span()
238
- has_active_span = bool(current_span and getattr(current_span, "is_recording", lambda: False)())
239
- if attach_globally or not has_active_span:
240
235
  otel_context.attach(ctx)
241
- else:
242
- # Best-effort: annotate the current span for observability
243
- if isinstance(value, str) and value:
244
- current_span.set_attribute(f"{Config.LIBRARY_NAME}.session.{session_key}", value)
245
- elif isinstance(value, dict) and value and session_key == "custom_attributes":
246
- for key, val in value.items():
247
- current_span.set_attribute(f"{Config.LIBRARY_NAME}.session.custom.{key}", str(val))
248
236
  except Exception as e:
249
237
  logger.exception(f"Failed to set session context for key={session_key}: {e}")
250
238
 
@@ -273,79 +261,64 @@ class SessionManager:
273
261
  except Exception as e:
274
262
  logger.exception(f"Failed to add custom event: {name} - {e}")
275
263
 
276
- @classmethod
277
- def set_attribute_on_target_span(cls, attr_key: str, attr_value: Any, span_name: Optional[str] = None) -> None:
264
+ @staticmethod
265
+ def add_conversation(type: ConversationType, field_name: str, value: Any) -> None:
278
266
  """
279
- Best-effort setter to annotate a target span with the provided attribute.
267
+ Append a conversation entry and set span attribute 'conversation' as an array.
280
268
 
281
- Behavior:
282
- - If span_name is provided, set the attribute on the span registered with that name.
283
- - If no span_name is provided, attempt to set the attribute on the SDK root span
284
- (created when Netra.init(enable_root_span=True)). If the root span is unavailable,
285
- fall back to the currently active span (OTel current span or SDK-managed current span).
269
+ Stored attribute format:
270
+ conversation: [
271
+ { "type": "input/output/system", "field_name": "sample_name", "value": "sample_value" },
272
+ ...
273
+ ]
286
274
  """
287
- try:
288
- # Convert attribute value to a JSON-safe string representation
289
- try:
290
- if isinstance(attr_value, str):
291
- attr_str = attr_value
292
- else:
293
- import json
294
275
 
295
- attr_str = json.dumps(attr_value)
296
- except Exception:
297
- attr_str = str(attr_value)
298
-
299
- # If a target span name is provided, use the registry for explicit lookup
300
- if span_name is not None:
301
- target = cls.get_span_by_name(span_name)
302
- if target is None:
303
- logger.debug("No span found with name '%s' to set attribute %s", span_name, attr_key)
304
- return
305
- target.set_attribute(attr_key, attr_str)
276
+ # Hard runtime validation of input types and values
277
+ if not isinstance(type, ConversationType):
278
+ raise TypeError("type must be a ConversationType enum value (input, output, system)")
279
+ normalized_type = type.value
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")
285
+
286
+ if value is None:
287
+ raise ValueError("value must not be None")
288
+
289
+ try:
290
+ span = trace.get_current_span()
291
+ if not (span and getattr(span, "is_recording", lambda: False)()):
292
+ logger.warning("No active span to add conversation attribute.")
306
293
  return
307
294
 
308
- # Otherwise, attempt to set on the root-most span in the current trace
309
- candidate = None
295
+ existing: List[Dict[str, Any]] = []
296
+ raw_data = None
310
297
 
311
- # Determine current trace_id from the active/current span
312
- current_span = trace.get_current_span()
313
- has_valid_current = getattr(current_span, "is_recording", None) is not None and current_span.is_recording()
314
- base_span = current_span if has_valid_current else cls.get_current_span()
315
- trace_id: Optional[int] = None
316
298
  try:
317
- if base_span is not None and hasattr(base_span, "get_span_context"):
318
- sc = base_span.get_span_context()
319
- trace_id = getattr(sc, "trace_id", None)
299
+ attrs = getattr(span, "_attributes", None)
300
+ if attrs is not None and hasattr(attrs, "get"):
301
+ raw_data = attrs.get("conversation")
320
302
  except Exception:
321
- trace_id = None
322
-
323
- # Find the earliest active span in this process that belongs to the same trace
324
- if trace_id is not None:
303
+ logger.exception("Failed to retrieve conversation attribute")
304
+ if raw_data:
325
305
  try:
326
- for s in cls._active_spans:
327
- if s is None:
328
- continue
329
- if not getattr(s, "is_recording", lambda: False)():
330
- continue
331
- sc = getattr(s, "get_span_context", lambda: None)()
332
- if sc is None:
333
- continue
334
- if getattr(sc, "trace_id", None) == trace_id:
335
- candidate = s
336
- break
306
+ import json
307
+
308
+ if isinstance(raw_data, str):
309
+ parsed = json.loads(raw_data)
310
+ if isinstance(parsed, list):
311
+ existing = parsed
337
312
  except Exception:
338
- candidate = None
313
+ existing = []
339
314
 
340
- # Fallback to the current active span if no root-most could be found
341
- if candidate is None:
342
- candidate = base_span
343
- if candidate is None:
344
- logger.debug("No active span found to set attribute %s", attr_key)
345
- return
346
- candidate.set_attribute(attr_key, attr_str)
315
+ # Append new entry
316
+ entry: Dict[str, Any] = {"type": normalized_type, "field_name": field_name, "value": value}
317
+ existing.append(entry)
318
+
319
+ SessionManager.set_attribute_on_active_span("conversation", existing)
347
320
  except Exception as e:
348
- logger.exception("Failed setting attribute %s: %s", attr_key, e)
321
+ logger.exception("Failed to add conversation attribute: %s", e)
349
322
 
350
323
  @staticmethod
351
324
  def set_attribute_on_active_span(attr_key: str, attr_value: Any) -> None:
@@ -0,0 +1 @@
1
+ __version__ = "0.1.40"
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "netra-sdk"
7
- version = "0.1.38"
7
+ version = "0.1.40"
8
8
  description = "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."
9
9
  authors = [
10
10
  {name = "Sooraj Thomas",email = "sooraj@keyvalue.systems"}
@@ -1 +0,0 @@
1
- __version__ = "0.1.38"
File without changes
File without changes
File without changes
File without changes
File without changes