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.
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/PKG-INFO +1 -1
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/__init__.py +11 -40
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/config.py +7 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/processors/instrumentation_span_processor.py +0 -1
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/session_manager.py +53 -80
- netra_sdk-0.1.40/netra/version.py +1 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/pyproject.toml +1 -1
- netra_sdk-0.1.38/netra/version.py +0 -1
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/LICENCE +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/README.md +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/anonymizer/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/anonymizer/anonymizer.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/anonymizer/base.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/anonymizer/fp_anonymizer.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/decorators.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/exceptions/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/exceptions/injection.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/exceptions/pii.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/input_scanner.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/aiohttp/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/aiohttp/version.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/cohere/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/cohere/version.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/fastapi/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/fastapi/version.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/config.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/utils.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/google_genai/version.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/httpx/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/httpx/version.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/instruments.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/litellm/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/litellm/version.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/litellm/wrappers.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/config.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/utils.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/mistralai/version.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/openai/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/openai/version.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/openai/wrappers.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/utils.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/version.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/pydantic_ai/wrappers.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/weaviate/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/instrumentation/weaviate/version.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/pii.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/processors/__init__.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/processors/scrubbing_span_processor.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/processors/session_span_processor.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/scanner.py +0 -0
- {netra_sdk-0.1.38 → netra_sdk-0.1.40}/netra/span_wrapper.py +0 -0
- {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.
|
|
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
|
-
@
|
|
277
|
-
def
|
|
264
|
+
@staticmethod
|
|
265
|
+
def add_conversation(type: ConversationType, field_name: str, value: Any) -> None:
|
|
278
266
|
"""
|
|
279
|
-
|
|
267
|
+
Append a conversation entry and set span attribute 'conversation' as an array.
|
|
280
268
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
313
|
+
existing = []
|
|
339
314
|
|
|
340
|
-
#
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|