netra-sdk 0.1.13__py3-none-any.whl → 0.1.15__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/instrumentation/openai/wrappers.py +32 -21
- netra/span_wrapper.py +17 -1
- netra/version.py +1 -1
- {netra_sdk-0.1.13.dist-info → netra_sdk-0.1.15.dist-info}/METADATA +3 -3
- {netra_sdk-0.1.13.dist-info → netra_sdk-0.1.15.dist-info}/RECORD +7 -7
- {netra_sdk-0.1.13.dist-info → netra_sdk-0.1.15.dist-info}/LICENCE +0 -0
- {netra_sdk-0.1.13.dist-info → netra_sdk-0.1.15.dist-info}/WHEEL +0 -0
|
@@ -12,6 +12,9 @@ from typing import Any, AsyncIterator, Callable, Dict, Iterator, Tuple
|
|
|
12
12
|
|
|
13
13
|
from opentelemetry import context as context_api
|
|
14
14
|
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
|
15
|
+
from opentelemetry.semconv_ai import (
|
|
16
|
+
SpanAttributes,
|
|
17
|
+
)
|
|
15
18
|
from opentelemetry.trace import Span, SpanKind, Tracer
|
|
16
19
|
from opentelemetry.trace.status import Status, StatusCode
|
|
17
20
|
from wrapt import ObjectProxy
|
|
@@ -55,34 +58,39 @@ def set_request_attributes(span: Span, kwargs: Dict[str, Any], operation_type: s
|
|
|
55
58
|
return
|
|
56
59
|
|
|
57
60
|
# Set operation type
|
|
58
|
-
span.set_attribute("
|
|
61
|
+
span.set_attribute(f"{SpanAttributes.LLM_REQUEST_TYPE}", operation_type)
|
|
59
62
|
|
|
60
63
|
# Common attributes
|
|
61
64
|
if kwargs.get("model"):
|
|
62
|
-
span.set_attribute("
|
|
65
|
+
span.set_attribute(f"{SpanAttributes.LLM_REQUEST_MODEL}", kwargs["model"])
|
|
63
66
|
|
|
64
67
|
if kwargs.get("temperature") is not None:
|
|
65
|
-
span.set_attribute("
|
|
68
|
+
span.set_attribute(f"{SpanAttributes.LLM_REQUEST_TEMPERATURE}", kwargs["temperature"])
|
|
66
69
|
|
|
67
70
|
if kwargs.get("max_tokens") is not None:
|
|
68
|
-
span.set_attribute("
|
|
71
|
+
span.set_attribute(f"{SpanAttributes.LLM_REQUEST_MAX_TOKENS}", kwargs["max_tokens"])
|
|
69
72
|
|
|
70
73
|
if kwargs.get("stream") is not None:
|
|
71
|
-
span.set_attribute("
|
|
74
|
+
span.set_attribute("gen_ai.stream", kwargs["stream"])
|
|
72
75
|
|
|
73
76
|
# Chat-specific attributes
|
|
74
77
|
if operation_type == "chat" and kwargs.get("messages"):
|
|
75
78
|
messages = kwargs["messages"]
|
|
76
79
|
if isinstance(messages, list) and len(messages) > 0:
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
for index, message in enumerate(messages):
|
|
81
|
+
if hasattr(message, "content"):
|
|
82
|
+
span.set_attribute(f"{SpanAttributes.LLM_PROMPTS}.{index}.role", "user")
|
|
83
|
+
span.set_attribute(f"{SpanAttributes.LLM_PROMPTS}.{index}.content", message.content)
|
|
84
|
+
elif isinstance(message, dict):
|
|
85
|
+
span.set_attribute(f"{SpanAttributes.LLM_PROMPTS}.{index}.role", message.get("role", "user"))
|
|
86
|
+
span.set_attribute(f"{SpanAttributes.LLM_PROMPTS}.{index}.content", str(message.get("content", "")))
|
|
79
87
|
|
|
80
88
|
# Response-specific attributes
|
|
81
89
|
if operation_type == "response":
|
|
82
90
|
if kwargs.get("instructions"):
|
|
83
|
-
span.set_attribute("
|
|
91
|
+
span.set_attribute("gen_ai.instructions", kwargs["instructions"])
|
|
84
92
|
if kwargs.get("input"):
|
|
85
|
-
span.set_attribute("
|
|
93
|
+
span.set_attribute("gen_ai.input", kwargs["input"])
|
|
86
94
|
|
|
87
95
|
|
|
88
96
|
def set_response_attributes(span: Span, response_dict: Dict[str, Any]) -> None:
|
|
@@ -91,33 +99,36 @@ def set_response_attributes(span: Span, response_dict: Dict[str, Any]) -> None:
|
|
|
91
99
|
return
|
|
92
100
|
|
|
93
101
|
if response_dict.get("model"):
|
|
94
|
-
span.set_attribute("
|
|
102
|
+
span.set_attribute(f"{SpanAttributes.LLM_RESPONSE_MODEL}", response_dict["model"])
|
|
95
103
|
|
|
96
104
|
if response_dict.get("id"):
|
|
97
|
-
span.set_attribute("
|
|
105
|
+
span.set_attribute("gen_ai.response.id", response_dict["id"])
|
|
98
106
|
|
|
99
107
|
# Usage information
|
|
100
108
|
usage = response_dict.get("usage", {})
|
|
101
109
|
if usage:
|
|
102
110
|
if usage.get("prompt_tokens"):
|
|
103
|
-
span.set_attribute("
|
|
111
|
+
span.set_attribute(f"{SpanAttributes.LLM_USAGE_PROMPT_TOKENS}", usage["prompt_tokens"])
|
|
104
112
|
if usage.get("completion_tokens"):
|
|
105
|
-
span.set_attribute("
|
|
113
|
+
span.set_attribute(f"{SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}", usage["completion_tokens"])
|
|
114
|
+
if usage.get("cache_read_input_token"):
|
|
115
|
+
span.set_attribute(f"{SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS}", usage["cache_read_input_token"])
|
|
106
116
|
if usage.get("total_tokens"):
|
|
107
|
-
span.set_attribute("
|
|
117
|
+
span.set_attribute(f"{SpanAttributes.LLM_USAGE_TOTAL_TOKENS}", usage["total_tokens"])
|
|
108
118
|
|
|
109
119
|
# Response content
|
|
110
120
|
choices = response_dict.get("choices", [])
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
for index, choice in enumerate(choices):
|
|
122
|
+
if choice.get("message", {}).get("role"):
|
|
123
|
+
span.set_attribute(f"{SpanAttributes.LLM_COMPLETIONS}.{index}.role", choice["message"]["role"])
|
|
124
|
+
if choice.get("message", {}).get("content"):
|
|
125
|
+
span.set_attribute(f"{SpanAttributes.LLM_COMPLETIONS}.{index}.content", choice["message"]["content"])
|
|
126
|
+
if choice.get("finish_reason"):
|
|
127
|
+
span.set_attribute(f"{SpanAttributes.LLM_COMPLETIONS}.{index}.finish_reason", choice["finish_reason"])
|
|
117
128
|
|
|
118
129
|
# For responses.create
|
|
119
130
|
if response_dict.get("output_text"):
|
|
120
|
-
span.set_attribute("
|
|
131
|
+
span.set_attribute("gen_ai.response.output_text", response_dict["output_text"])
|
|
121
132
|
|
|
122
133
|
|
|
123
134
|
def chat_wrapper(tracer: Tracer) -> Callable[..., Any]:
|
netra/span_wrapper.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
+
import re
|
|
3
4
|
import time
|
|
4
5
|
from datetime import datetime, timezone
|
|
5
6
|
from typing import Any, Dict, List, Literal, Optional
|
|
@@ -8,7 +9,7 @@ from opentelemetry import context as context_api
|
|
|
8
9
|
from opentelemetry import trace
|
|
9
10
|
from opentelemetry.trace import SpanKind, Status, StatusCode
|
|
10
11
|
from opentelemetry.trace.propagation import set_span_in_context
|
|
11
|
-
from pydantic import BaseModel
|
|
12
|
+
from pydantic import BaseModel, field_validator
|
|
12
13
|
|
|
13
14
|
from netra.config import Config
|
|
14
15
|
|
|
@@ -25,6 +26,21 @@ class ActionModel(BaseModel): # type: ignore[misc]
|
|
|
25
26
|
affected_records: Optional[List[Dict[str, str]]] = None
|
|
26
27
|
metadata: Optional[Dict[str, str]] = None
|
|
27
28
|
|
|
29
|
+
@field_validator("start_time") # type: ignore[misc]
|
|
30
|
+
@classmethod
|
|
31
|
+
def validate_time_format(cls, value: str) -> str:
|
|
32
|
+
"""Validate that start_time is in ISO 8601 format with microseconds and Z suffix."""
|
|
33
|
+
# Pattern for ISO 8601 with microseconds: YYYY-MM-DDTHH:MM:SS.ffffffZ
|
|
34
|
+
pattern = r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$"
|
|
35
|
+
|
|
36
|
+
if not re.match(pattern, value):
|
|
37
|
+
raise ValueError(
|
|
38
|
+
f"start_time must be in ISO 8601 format with microseconds: "
|
|
39
|
+
f"YYYY-MM-DDTHH:MM:SS.ffffffZ (e.g., 2025-07-18T14:30:45.123456Z). "
|
|
40
|
+
f"Got: {value}"
|
|
41
|
+
)
|
|
42
|
+
return value
|
|
43
|
+
|
|
28
44
|
|
|
29
45
|
class UsageModel(BaseModel): # type: ignore[misc]
|
|
30
46
|
model: str
|
netra/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.15"
|
|
2
2
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: netra-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.15
|
|
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
|
|
@@ -67,7 +67,7 @@ Requires-Dist: opentelemetry-instrumentation-urllib (>=0.55b1,<1.0.0)
|
|
|
67
67
|
Requires-Dist: opentelemetry-instrumentation-urllib3 (>=0.55b1,<1.0.0)
|
|
68
68
|
Requires-Dist: opentelemetry-sdk (>=1.34.0,<2.0.0)
|
|
69
69
|
Requires-Dist: presidio-analyzer (>=2.2.358,<3.0.0)
|
|
70
|
-
Requires-Dist: traceloop-sdk (>=0.40.7,<0.
|
|
70
|
+
Requires-Dist: traceloop-sdk (>=0.40.7,<0.43.0)
|
|
71
71
|
Project-URL: Bug Tracker, https://github.com/KeyValueSoftwareSystems/netra-sdk-py/issues
|
|
72
72
|
Project-URL: Documentation, https://github.com/KeyValueSoftwareSystems/netra-sdk-py/blob/main/README.md
|
|
73
73
|
Project-URL: Homepage, https://github.com/KeyValueSoftwareSystems/netra-sdk-py
|
|
@@ -482,7 +482,7 @@ Action tracking follows this schema:
|
|
|
482
482
|
```python
|
|
483
483
|
[
|
|
484
484
|
{
|
|
485
|
-
"start_time": str, # Start time of the action
|
|
485
|
+
"start_time": str, # Start time of the action in ISO 8601 format with microseconds and Z suffix (e.g., 2025-07-18T14:30:45.123456Z)
|
|
486
486
|
"action": str, # Type of action (e.g., "DB", "API", "CACHE")
|
|
487
487
|
"action_type": str, # Action subtype (e.g., "INSERT", "SELECT", "CALL")
|
|
488
488
|
"affected_records": [ # Optional: List of records affected
|
|
@@ -29,7 +29,7 @@ netra/instrumentation/mistralai/utils.py,sha256=nhdIer5gJFxuGwg8FCT222hggDHeMQDh
|
|
|
29
29
|
netra/instrumentation/mistralai/version.py,sha256=d6593s-XBNvVxri9lr2qLUDZQ3Zk3-VXHEwdb4pj8qA,22
|
|
30
30
|
netra/instrumentation/openai/__init__.py,sha256=HztqLMw8Tf30-Ydqr4N7FcvAwj-5cnGZNqI-S3wIZ_4,5143
|
|
31
31
|
netra/instrumentation/openai/version.py,sha256=_J-N1qG50GykJDM356BSQf0E8LoLbB8AaC3RKho494A,23
|
|
32
|
-
netra/instrumentation/openai/wrappers.py,sha256=
|
|
32
|
+
netra/instrumentation/openai/wrappers.py,sha256=4VQwIBLYaGovO9gE5TSMC-Ot84IaDuDhGqHndgR-Am4,21637
|
|
33
33
|
netra/instrumentation/weaviate/__init__.py,sha256=EOlpWxobOLHYKqo_kMct_7nu26x1hr8qkeG5_h99wtg,4330
|
|
34
34
|
netra/instrumentation/weaviate/version.py,sha256=PiCZHjonujPbnIn0KmD3Yl68hrjPRG_oKe5vJF3mmG8,24
|
|
35
35
|
netra/pii.py,sha256=S7GnVzoNJEzKiUWnqN9bOCKPeNLsriztgB2E6Rx-yJU,27023
|
|
@@ -37,10 +37,10 @@ netra/processors/__init__.py,sha256=wfnSskRBtMT90hO7LqFJoEW374LgoH_gnTxhynqtByI,
|
|
|
37
37
|
netra/processors/session_span_processor.py,sha256=qcsBl-LnILWefsftI8NQhXDGb94OWPc8LvzhVA0JS_c,2432
|
|
38
38
|
netra/scanner.py,sha256=wqjMZnEbVvrGMiUSI352grUyHpkk94oBfHfMiXPhpGU,3866
|
|
39
39
|
netra/session_manager.py,sha256=EVcnWcSj4NdkH--HmqHx0mmzivQiM4GCyFLu6lwi33M,6252
|
|
40
|
-
netra/span_wrapper.py,sha256=
|
|
40
|
+
netra/span_wrapper.py,sha256=DA5jjXkHBUJ8_mdlYP06rcZzFoSih4gdP71Wwr3btcQ,8104
|
|
41
41
|
netra/tracer.py,sha256=In5QPVLz_6BxrolWpav9EuR9_hirD2UUIlyY75QUaKk,3450
|
|
42
|
-
netra/version.py,sha256=
|
|
43
|
-
netra_sdk-0.1.
|
|
44
|
-
netra_sdk-0.1.
|
|
45
|
-
netra_sdk-0.1.
|
|
46
|
-
netra_sdk-0.1.
|
|
42
|
+
netra/version.py,sha256=s5mBMy--qmLbYWG9g95dqUqvmR31MlOMaNfvabrDFlQ,24
|
|
43
|
+
netra_sdk-0.1.15.dist-info/LICENCE,sha256=8B_UoZ-BAl0AqiHAHUETCgd3I2B9yYJ1WEQtVb_qFMA,11359
|
|
44
|
+
netra_sdk-0.1.15.dist-info/METADATA,sha256=QkYln6aEvwGYpfto8eAE2rCV-7Lk-kgqWGWcWGFxfok,25352
|
|
45
|
+
netra_sdk-0.1.15.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
46
|
+
netra_sdk-0.1.15.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|