deepeval 3.6.0__py3-none-any.whl → 3.6.1__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.
- deepeval/_version.py +1 -1
- deepeval/openai_agents/callback_handler.py +10 -3
- deepeval/tracing/otel/exporter.py +6 -0
- deepeval/tracing/otel/utils.py +62 -0
- deepeval/tracing/utils.py +54 -0
- {deepeval-3.6.0.dist-info → deepeval-3.6.1.dist-info}/METADATA +1 -1
- {deepeval-3.6.0.dist-info → deepeval-3.6.1.dist-info}/RECORD +10 -10
- {deepeval-3.6.0.dist-info → deepeval-3.6.1.dist-info}/LICENSE.md +0 -0
- {deepeval-3.6.0.dist-info → deepeval-3.6.1.dist-info}/WHEEL +0 -0
- {deepeval-3.6.0.dist-info → deepeval-3.6.1.dist-info}/entry_points.txt +0 -0
deepeval/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__: str = "3.6.
|
|
1
|
+
__version__: str = "3.6.1"
|
|
@@ -83,7 +83,9 @@ class DeepEvalTracingProcessor(TracingProcessor):
|
|
|
83
83
|
if not span.started_at:
|
|
84
84
|
return
|
|
85
85
|
current_span = current_span_context.get()
|
|
86
|
-
if current_span and isinstance(
|
|
86
|
+
if current_span and isinstance(
|
|
87
|
+
current_span, LlmSpan
|
|
88
|
+
): # llm span started by
|
|
87
89
|
return
|
|
88
90
|
|
|
89
91
|
span_type = self.get_span_kind(span.span_data)
|
|
@@ -101,10 +103,15 @@ class DeepEvalTracingProcessor(TracingProcessor):
|
|
|
101
103
|
current_trace_context.get(), span.span_data
|
|
102
104
|
)
|
|
103
105
|
|
|
106
|
+
span_type = self.get_span_kind(span.span_data)
|
|
104
107
|
current_span = current_span_context.get()
|
|
105
|
-
if
|
|
108
|
+
if (
|
|
109
|
+
current_span
|
|
110
|
+
and isinstance(current_span, LlmSpan)
|
|
111
|
+
and span_type == "llm"
|
|
112
|
+
): # addtional check if the span kind data is llm too
|
|
106
113
|
update_span_properties(current_span, span.span_data)
|
|
107
|
-
|
|
114
|
+
|
|
108
115
|
observer = self.span_observers.pop(span.span_id, None)
|
|
109
116
|
if observer:
|
|
110
117
|
observer.__exit__(None, None, None)
|
|
@@ -42,6 +42,7 @@ from deepeval.tracing.types import TraceAttributes
|
|
|
42
42
|
from deepeval.test_case import ToolCall
|
|
43
43
|
from dataclasses import dataclass
|
|
44
44
|
import deepeval
|
|
45
|
+
from deepeval.tracing.utils import make_json_serializable_for_metadata
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
@dataclass
|
|
@@ -360,6 +361,8 @@ class ConfidentSpanExporter(SpanExporter):
|
|
|
360
361
|
raw_trace_expected_tools
|
|
361
362
|
)
|
|
362
363
|
trace_metadata = self._parse_json_string(raw_trace_metadata)
|
|
364
|
+
if trace_metadata:
|
|
365
|
+
trace_metadata = make_json_serializable_for_metadata(trace_metadata)
|
|
363
366
|
trace_metric_collection = parse_string(raw_trace_metric_collection)
|
|
364
367
|
|
|
365
368
|
base_span_wrapper.trace_input = trace_input
|
|
@@ -426,6 +429,9 @@ class ConfidentSpanExporter(SpanExporter):
|
|
|
426
429
|
span_tools_called = self._parse_list_of_tools(raw_span_tools_called)
|
|
427
430
|
span_expected_tools = self._parse_list_of_tools(raw_span_expected_tools)
|
|
428
431
|
span_metadata = self._parse_json_string(raw_span_metadata)
|
|
432
|
+
if span_metadata:
|
|
433
|
+
span_metadata = make_json_serializable_for_metadata(span_metadata)
|
|
434
|
+
|
|
429
435
|
span_metric_collection = parse_string(raw_span_metric_collection)
|
|
430
436
|
|
|
431
437
|
# Set Span Attributes
|
deepeval/tracing/otel/utils.py
CHANGED
|
@@ -4,6 +4,8 @@ from deepeval.tracing import trace_manager, BaseSpan
|
|
|
4
4
|
from opentelemetry.sdk.trace.export import ReadableSpan
|
|
5
5
|
import json
|
|
6
6
|
|
|
7
|
+
from deepeval.tracing.utils import make_json_serializable
|
|
8
|
+
|
|
7
9
|
GEN_AI_OPERATION_NAMES = ["chat", "generate_content", "task_completion"]
|
|
8
10
|
|
|
9
11
|
|
|
@@ -103,10 +105,13 @@ def check_llm_input_from_gen_ai_attributes(
|
|
|
103
105
|
output = None
|
|
104
106
|
try:
|
|
105
107
|
input = json.loads(span.attributes.get("gen_ai.input.messages"))
|
|
108
|
+
input = _flatten_input(input)
|
|
109
|
+
|
|
106
110
|
except Exception as e:
|
|
107
111
|
pass
|
|
108
112
|
try:
|
|
109
113
|
output = json.loads(span.attributes.get("gen_ai.output.messages"))
|
|
114
|
+
output = _flatten_input(output)
|
|
110
115
|
except Exception as e:
|
|
111
116
|
pass
|
|
112
117
|
|
|
@@ -127,6 +132,61 @@ def check_llm_input_from_gen_ai_attributes(
|
|
|
127
132
|
return input, output
|
|
128
133
|
|
|
129
134
|
|
|
135
|
+
def _flatten_input(input: list) -> list:
|
|
136
|
+
if input and isinstance(input, list):
|
|
137
|
+
try:
|
|
138
|
+
result: List[dict] = []
|
|
139
|
+
for m in input:
|
|
140
|
+
if isinstance(m, dict):
|
|
141
|
+
role = m.get("role")
|
|
142
|
+
if not role:
|
|
143
|
+
role = "assistant"
|
|
144
|
+
parts = m.get("parts")
|
|
145
|
+
if parts:
|
|
146
|
+
for part in parts:
|
|
147
|
+
if isinstance(part, dict):
|
|
148
|
+
ptype = part.get("type")
|
|
149
|
+
if ptype == "text":
|
|
150
|
+
result.append(
|
|
151
|
+
{
|
|
152
|
+
"role": role,
|
|
153
|
+
"content": part.get("content"),
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
result.append(
|
|
158
|
+
{
|
|
159
|
+
"role": role,
|
|
160
|
+
"content": make_json_serializable(
|
|
161
|
+
part
|
|
162
|
+
),
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
result.append(
|
|
167
|
+
{
|
|
168
|
+
"role": role,
|
|
169
|
+
"content": make_json_serializable(part),
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
else:
|
|
173
|
+
result.append(
|
|
174
|
+
{"role": role, "content": m.get("content")}
|
|
175
|
+
) # no parts
|
|
176
|
+
else:
|
|
177
|
+
result.append(
|
|
178
|
+
{
|
|
179
|
+
"role": "assistant",
|
|
180
|
+
"content": make_json_serializable(m),
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
return result
|
|
184
|
+
except Exception as e:
|
|
185
|
+
return input
|
|
186
|
+
|
|
187
|
+
return input
|
|
188
|
+
|
|
189
|
+
|
|
130
190
|
def check_tool_name_from_gen_ai_attributes(span: ReadableSpan) -> Optional[str]:
|
|
131
191
|
try:
|
|
132
192
|
gen_ai_tool_name = span.attributes.get("gen_ai.tool.name")
|
|
@@ -374,6 +434,8 @@ def check_pydantic_ai_agent_input_output(
|
|
|
374
434
|
except Exception:
|
|
375
435
|
pass
|
|
376
436
|
|
|
437
|
+
input_val = _flatten_input(input_val)
|
|
438
|
+
output_val = _flatten_input(output_val)
|
|
377
439
|
return input_val, output_val
|
|
378
440
|
|
|
379
441
|
|
deepeval/tracing/utils.py
CHANGED
|
@@ -100,6 +100,60 @@ def make_json_serializable(obj):
|
|
|
100
100
|
return _serialize(obj)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
def make_json_serializable_for_metadata(obj):
|
|
104
|
+
"""
|
|
105
|
+
Recursively converts an object to a JSON‐serializable form,
|
|
106
|
+
replacing circular references with "<circular>".
|
|
107
|
+
"""
|
|
108
|
+
seen = set() # Store `id` of objects we've visited
|
|
109
|
+
|
|
110
|
+
def _serialize(o):
|
|
111
|
+
oid = id(o)
|
|
112
|
+
|
|
113
|
+
# strip Nulls
|
|
114
|
+
if isinstance(o, str):
|
|
115
|
+
return _strip_nul(o)
|
|
116
|
+
|
|
117
|
+
# Primitive types are already serializable
|
|
118
|
+
if isinstance(o, (str, int, float, bool)) or o is None:
|
|
119
|
+
return str(o)
|
|
120
|
+
|
|
121
|
+
# Detect circular reference
|
|
122
|
+
if oid in seen:
|
|
123
|
+
return "<circular>"
|
|
124
|
+
|
|
125
|
+
# Mark current object as seen
|
|
126
|
+
seen.add(oid)
|
|
127
|
+
|
|
128
|
+
# Handle containers
|
|
129
|
+
if isinstance(o, (list, tuple, set, deque)): # TODO: check if more
|
|
130
|
+
serialized = []
|
|
131
|
+
for item in o:
|
|
132
|
+
serialized.append(_serialize(item))
|
|
133
|
+
|
|
134
|
+
return serialized
|
|
135
|
+
|
|
136
|
+
if isinstance(o, dict):
|
|
137
|
+
result = {}
|
|
138
|
+
for key, value in o.items():
|
|
139
|
+
# Convert key to string (JSON only allows string keys)
|
|
140
|
+
result[str(key)] = _serialize(value)
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
# Handle objects with __dict__
|
|
144
|
+
if hasattr(o, "__dict__"):
|
|
145
|
+
result = {}
|
|
146
|
+
for key, value in vars(o).items():
|
|
147
|
+
if not key.startswith("_"):
|
|
148
|
+
result[key] = _serialize(value)
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
# Fallback: convert to string
|
|
152
|
+
return _strip_nul(str(o))
|
|
153
|
+
|
|
154
|
+
return _serialize(obj)
|
|
155
|
+
|
|
156
|
+
|
|
103
157
|
def to_zod_compatible_iso(
|
|
104
158
|
dt: datetime, microsecond_precision: bool = False
|
|
105
159
|
) -> str:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
deepeval/__init__.py,sha256=6fsb813LD_jNhqR-xZnSdE5E-KsBbC3tc4oIg5ZMgTw,2115
|
|
2
|
-
deepeval/_version.py,sha256=
|
|
2
|
+
deepeval/_version.py,sha256=60ky4ZrqXl83ooFzPWUHtPFcXD1XP6b9GQDnqw3EHOU,27
|
|
3
3
|
deepeval/annotation/__init__.py,sha256=ZFhUVNNuH_YgQSZJ-m5E9iUb9TkAkEV33a6ouMDZ8EI,111
|
|
4
4
|
deepeval/annotation/annotation.py,sha256=3j3-syeJepAcEj3u3e4T_BeRDzNr7yXGDIoNQGMKpwQ,2298
|
|
5
5
|
deepeval/annotation/api.py,sha256=EYN33ACVzVxsFleRYm60KB4Exvff3rPJKt1VBuuX970,2147
|
|
@@ -396,7 +396,7 @@ deepeval/openai/patch.py,sha256=tPDqXaBScBJveM9P5xLT_mVwkubw0bOey-efvdjZIfg,7466
|
|
|
396
396
|
deepeval/openai/utils.py,sha256=-84VZGUsnzRkYAFWc_DGaGuQTDCUItk0VtUTdjtSxg4,2748
|
|
397
397
|
deepeval/openai_agents/__init__.py,sha256=F4c6MtsdV7LWj0YamQcMGs4_u5sOYZJXWOQP8kV5xUg,314
|
|
398
398
|
deepeval/openai_agents/agent.py,sha256=_SQdd0JzZK-ZvpP7yPEi22Y7fVk16PC00ROahdDQdCQ,951
|
|
399
|
-
deepeval/openai_agents/callback_handler.py,sha256=
|
|
399
|
+
deepeval/openai_agents/callback_handler.py,sha256=4Tt2OAGfYd35C5LBMekxz0SDivYryKGm3lxls1WT7cY,4842
|
|
400
400
|
deepeval/openai_agents/extractors.py,sha256=jcV-IeWLIh64astJRy_dRBAbUOIab1vp0Wzda7AgVyk,13963
|
|
401
401
|
deepeval/openai_agents/patch.py,sha256=MNvbGe5NLq0rC7L-7lnqcxKhclQvLuBKZnZyAifSHLY,10241
|
|
402
402
|
deepeval/openai_agents/runner.py,sha256=WtHuzhYHgC571uJYGjbTz3R23VaKnlKybGJSRCxM9pY,12310
|
|
@@ -453,16 +453,16 @@ deepeval/tracing/offline_evals/span.py,sha256=pXqTVXs-WnjRVpCYYEbNe0zSM6Wz9GsKHs
|
|
|
453
453
|
deepeval/tracing/offline_evals/thread.py,sha256=bcSGFcZJKnszArOLIlWvnCyt0zSmsd7Xsw5rl4RTVFg,1981
|
|
454
454
|
deepeval/tracing/offline_evals/trace.py,sha256=vTflaTKysKRiYvKA-Nx6PUJ3J6NrRLXiIdWieVcm90E,1868
|
|
455
455
|
deepeval/tracing/otel/__init__.py,sha256=HQsaF5yLPwyW5qg8AOV81_nG_7pFHnatOTHi9Wx3HEk,88
|
|
456
|
-
deepeval/tracing/otel/exporter.py,sha256=
|
|
457
|
-
deepeval/tracing/otel/utils.py,sha256=
|
|
456
|
+
deepeval/tracing/otel/exporter.py,sha256=wPO1ITKpjueLOSNLO6nD2QL9LAd8Xcu6en8hRkB61Wo,28891
|
|
457
|
+
deepeval/tracing/otel/utils.py,sha256=THXOoqLau4w6Jlz0YJV3K3vQcVptxo14hcDQCJiPeks,14821
|
|
458
458
|
deepeval/tracing/patchers.py,sha256=DAPNkhrDtoeyJIVeQDUMhTz-xGcXu00eqjQZmov8FiU,3096
|
|
459
459
|
deepeval/tracing/perf_epoch_bridge.py,sha256=iyAPddB6Op7NpMtPHJ29lDm53Btz9yLaN6xSCfTRQm4,1825
|
|
460
460
|
deepeval/tracing/tracing.py,sha256=WFXfGLt58Ia9yCohDZBIUGX6mwieoF8489UziuC-NJI,42458
|
|
461
461
|
deepeval/tracing/types.py,sha256=l_utWKerNlE5H3mOKpeUJLsvpP3cMyjH7HRANNgTmSQ,5306
|
|
462
|
-
deepeval/tracing/utils.py,sha256=
|
|
462
|
+
deepeval/tracing/utils.py,sha256=RUcsDpS_aobK3zuNfZGNvjk7aBbBfHOj3aYu2hRZzg0,7993
|
|
463
463
|
deepeval/utils.py,sha256=-_o3W892u7naX4Y7a8if4mP0Rtkgtapg6Krr1ZBpj0o,17197
|
|
464
|
-
deepeval-3.6.
|
|
465
|
-
deepeval-3.6.
|
|
466
|
-
deepeval-3.6.
|
|
467
|
-
deepeval-3.6.
|
|
468
|
-
deepeval-3.6.
|
|
464
|
+
deepeval-3.6.1.dist-info/LICENSE.md,sha256=0ATkuLv6QgsJTBODUHC5Rak_PArA6gv2t7inJzNTP38,11352
|
|
465
|
+
deepeval-3.6.1.dist-info/METADATA,sha256=UrYM0bqzIvhmMlevcqO-Hcbbm2e5r26FwWEzz2rKua8,18743
|
|
466
|
+
deepeval-3.6.1.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
|
467
|
+
deepeval-3.6.1.dist-info/entry_points.txt,sha256=fVr8UphXTfJe9I2rObmUtfU3gkSrYeM0pLy-NbJYg10,94
|
|
468
|
+
deepeval-3.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|