Skip to content

Commit 293239a

Browse files
committed
fix(evaluation): preserve trace boundaries in eval sets
1 parent 3be05c4 commit 293239a

2 files changed

Lines changed: 90 additions & 16 deletions

File tree

tests/test_evaluator.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,69 @@ def test_tracing_file_to_evalset():
160160
)
161161

162162
os.remove(tracing_file_path)
163+
164+
165+
def test_tracing_file_creates_isolated_eval_case_per_sorted_trace(tmp_path):
166+
trace_a = 101
167+
trace_b = 202
168+
169+
def call_llm(trace_id, start_time, app_name, user_id, prompt="", completion=""):
170+
return {
171+
"name": "call_llm",
172+
"trace_id": trace_id,
173+
"start_time": start_time,
174+
"attributes": {
175+
"gen_ai.app.name": app_name,
176+
"gen_ai.user.id": user_id,
177+
"gen_ai.prompt.0.content": prompt,
178+
"gen_ai.completion.0.content": completion,
179+
},
180+
}
181+
182+
def execute_tool(start_time, tool_name):
183+
return {
184+
"name": f"execute_tool {tool_name}",
185+
"trace_id": trace_a,
186+
"start_time": start_time,
187+
"attributes": {
188+
"gen_ai.tool.name": tool_name,
189+
"gen_ai.tool.input": json.dumps({"parameters": {"order": tool_name}}),
190+
"gen_ai.tool.output": json.dumps({"id": f"call-{tool_name}"}),
191+
},
192+
}
193+
194+
tracing_data = [
195+
call_llm(trace_a, 40, "app-a", "user-a", completion="answer-a"),
196+
call_llm(trace_b, 60, "app-b", "user-b", completion="answer-b"),
197+
execute_tool(30, "second"),
198+
call_llm(trace_b, 50, "app-b", "user-b", prompt="question-b"),
199+
call_llm(trace_a, 10, "app-a", "user-a", prompt="question-a"),
200+
execute_tool(20, "first"),
201+
]
202+
tracing_file_path = tmp_path / "tracing.json"
203+
tracing_file_path.write_text(json.dumps(tracing_data))
204+
205+
eval_set = BaseEvaluator(
206+
agent=None, name="test_evaluator"
207+
)._build_eval_set_from_tracing_json(str(tracing_file_path))
208+
209+
assert len(eval_set.eval_cases) == 2
210+
eval_cases = {
211+
eval_case.session_input.app_name: eval_case for eval_case in eval_set.eval_cases
212+
}
213+
214+
case_a = eval_cases["app-a"]
215+
assert case_a.session_input.user_id == "user-a"
216+
assert case_a.creation_timestamp == 10 / 1e9
217+
assert case_a.conversation[0].user_content.parts[0].text == "question-a"
218+
assert case_a.conversation[0].final_response.parts[0].text == "answer-a"
219+
assert [
220+
tool.name for tool in case_a.conversation[0].intermediate_data.tool_uses
221+
] == ["first", "second"]
222+
223+
case_b = eval_cases["app-b"]
224+
assert case_b.session_input.user_id == "user-b"
225+
assert case_b.creation_timestamp == 50 / 1e9
226+
assert case_b.conversation[0].user_content.parts[0].text == "question-b"
227+
assert case_b.conversation[0].final_response.parts[0].text == "answer-b"
228+
assert case_b.conversation[0].intermediate_data.tool_uses == []

veadk/evaluation/base_evaluator.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,11 @@ def _build_eval_set_from_tracing_json(self, tracing_json_path: str) -> EvalSet:
270270
trace_groups[trace_id] = []
271271
trace_groups[trace_id].append(span)
272272

273-
# Convert to evalset format
274-
eval_cases, conversation = [], []
275-
app_name, user_id = "", ""
276-
creation_timestamp = 0
273+
# Convert each trace to an isolated eval case.
274+
eval_cases = []
277275
for trace_id, spans in trace_groups.items():
276+
spans = sorted(spans, key=lambda span: span["start_time"])
277+
conversation = []
278278
tool_uses = []
279279

280280
# Extract tool_uses from spans with name starting with "execute_tool"
@@ -308,6 +308,9 @@ def _build_eval_set_from_tracing_json(self, tracing_json_path: str) -> EvalSet:
308308
# Extract conversation data from call_llm spans
309309
user_input = ""
310310
final_output = ""
311+
app_name = ""
312+
user_id = ""
313+
creation_timestamp = 0
311314

312315
# Find the first call_llm span for user input and the last one for final output
313316
call_llm_spans = [span for span in spans if span["name"] == "call_llm"]
@@ -347,25 +350,30 @@ def _build_eval_set_from_tracing_json(self, tracing_json_path: str) -> EvalSet:
347350
}
348351
)
349352

350-
eval_cases.append(
351-
{
352-
"eval_id": f"veadk_eval_{formatted_timestamp()}",
353-
"conversation": conversation,
354-
"session_input": {
355-
"app_name": app_name,
356-
"user_id": user_id,
357-
"state": {},
358-
},
359-
"creation_timestamp": creation_timestamp,
360-
}
353+
eval_cases.append(
354+
{
355+
"eval_id": (f"veadk_eval_{formatted_timestamp()}_{trace_id}"),
356+
"conversation": conversation,
357+
"session_input": {
358+
"app_name": app_name,
359+
"user_id": user_id,
360+
"state": {},
361+
},
362+
"creation_timestamp": creation_timestamp,
363+
}
364+
)
365+
366+
eval_set_creation_timestamp = min(
367+
(eval_case["creation_timestamp"] for eval_case in eval_cases),
368+
default=0,
361369
)
362370

363371
evalset = EvalSet(
364372
eval_set_id="default",
365373
name="default",
366374
description=None,
367375
eval_cases=eval_cases,
368-
creation_timestamp=creation_timestamp,
376+
creation_timestamp=eval_set_creation_timestamp,
369377
)
370378

371379
return evalset

0 commit comments

Comments
 (0)