netra-sdk 0.1.39__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.39 → netra_sdk-0.1.40}/PKG-INFO +1 -1
  2. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/__init__.py +11 -40
  3. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/config.py +7 -0
  4. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/session_manager.py +53 -61
  5. netra_sdk-0.1.40/netra/version.py +1 -0
  6. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/pyproject.toml +1 -1
  7. netra_sdk-0.1.39/netra/version.py +0 -1
  8. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/LICENCE +0 -0
  9. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/README.md +0 -0
  10. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/anonymizer/__init__.py +0 -0
  11. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/anonymizer/anonymizer.py +0 -0
  12. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/anonymizer/base.py +0 -0
  13. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/anonymizer/fp_anonymizer.py +0 -0
  14. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/decorators.py +0 -0
  15. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/exceptions/__init__.py +0 -0
  16. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/exceptions/injection.py +0 -0
  17. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/exceptions/pii.py +0 -0
  18. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/input_scanner.py +0 -0
  19. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/__init__.py +0 -0
  20. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/aiohttp/__init__.py +0 -0
  21. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/aiohttp/version.py +0 -0
  22. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/cohere/__init__.py +0 -0
  23. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/cohere/version.py +0 -0
  24. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/fastapi/__init__.py +0 -0
  25. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/fastapi/version.py +0 -0
  26. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/__init__.py +0 -0
  27. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/config.py +0 -0
  28. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/utils.py +0 -0
  29. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/version.py +0 -0
  30. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/httpx/__init__.py +0 -0
  31. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/httpx/version.py +0 -0
  32. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/instruments.py +0 -0
  33. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/litellm/__init__.py +0 -0
  34. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/litellm/version.py +0 -0
  35. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/litellm/wrappers.py +0 -0
  36. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/__init__.py +0 -0
  37. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/config.py +0 -0
  38. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/utils.py +0 -0
  39. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/version.py +0 -0
  40. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/openai/__init__.py +0 -0
  41. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/openai/version.py +0 -0
  42. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/openai/wrappers.py +0 -0
  43. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/__init__.py +0 -0
  44. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/utils.py +0 -0
  45. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/version.py +0 -0
  46. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/wrappers.py +0 -0
  47. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/weaviate/__init__.py +0 -0
  48. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/instrumentation/weaviate/version.py +0 -0
  49. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/pii.py +0 -0
  50. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/processors/__init__.py +0 -0
  51. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/processors/instrumentation_span_processor.py +0 -0
  52. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/processors/scrubbing_span_processor.py +0 -0
  53. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/processors/session_span_processor.py +0 -0
  54. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/scanner.py +0 -0
  55. {netra_sdk-0.1.39 → netra_sdk-0.1.40}/netra/span_wrapper.py +0 -0
  56. {netra_sdk-0.1.39 → 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.39
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:
@@ -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
 
@@ -254,79 +261,64 @@ class SessionManager:
254
261
  except Exception as e:
255
262
  logger.exception(f"Failed to add custom event: {name} - {e}")
256
263
 
257
- @classmethod
258
- 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:
259
266
  """
260
- 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.
261
268
 
262
- Behavior:
263
- - If span_name is provided, set the attribute on the span registered with that name.
264
- - If no span_name is provided, attempt to set the attribute on the SDK root span
265
- (created when Netra.init(enable_root_span=True)). If the root span is unavailable,
266
- 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
+ ]
267
274
  """
268
- try:
269
- # Convert attribute value to a JSON-safe string representation
270
- try:
271
- if isinstance(attr_value, str):
272
- attr_str = attr_value
273
- else:
274
- import json
275
275
 
276
- attr_str = json.dumps(attr_value)
277
- except Exception:
278
- attr_str = str(attr_value)
279
-
280
- # If a target span name is provided, use the registry for explicit lookup
281
- if span_name is not None:
282
- target = cls.get_span_by_name(span_name)
283
- if target is None:
284
- logger.debug("No span found with name '%s' to set attribute %s", span_name, attr_key)
285
- return
286
- 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.")
287
293
  return
288
294
 
289
- # Otherwise, attempt to set on the root-most span in the current trace
290
- candidate = None
295
+ existing: List[Dict[str, Any]] = []
296
+ raw_data = None
291
297
 
292
- # Determine current trace_id from the active/current span
293
- current_span = trace.get_current_span()
294
- has_valid_current = getattr(current_span, "is_recording", None) is not None and current_span.is_recording()
295
- base_span = current_span if has_valid_current else cls.get_current_span()
296
- trace_id: Optional[int] = None
297
298
  try:
298
- if base_span is not None and hasattr(base_span, "get_span_context"):
299
- sc = base_span.get_span_context()
300
- 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")
301
302
  except Exception:
302
- trace_id = None
303
-
304
- # Find the earliest active span in this process that belongs to the same trace
305
- if trace_id is not None:
303
+ logger.exception("Failed to retrieve conversation attribute")
304
+ if raw_data:
306
305
  try:
307
- for s in cls._active_spans:
308
- if s is None:
309
- continue
310
- if not getattr(s, "is_recording", lambda: False)():
311
- continue
312
- sc = getattr(s, "get_span_context", lambda: None)()
313
- if sc is None:
314
- continue
315
- if getattr(sc, "trace_id", None) == trace_id:
316
- candidate = s
317
- 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
318
312
  except Exception:
319
- candidate = None
313
+ existing = []
320
314
 
321
- # Fallback to the current active span if no root-most could be found
322
- if candidate is None:
323
- candidate = base_span
324
- if candidate is None:
325
- logger.debug("No active span found to set attribute %s", attr_key)
326
- return
327
- 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)
328
320
  except Exception as e:
329
- logger.exception("Failed setting attribute %s: %s", attr_key, e)
321
+ logger.exception("Failed to add conversation attribute: %s", e)
330
322
 
331
323
  @staticmethod
332
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.39"
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.39"
File without changes
File without changes
File without changes
File without changes
File without changes