Skip to content

Commit b47e42c

Browse files
Tomkessclaude
andcommitted
feat(gooddata-eval): capture conversation_id/response_id through the agentic-CLI path
8010bd4 wired reasoning_steps through cli/agentic_runner.py -> evaluate_agentic_*, but conversation_id/response_id stayed unset on ItemReport for every agentic kind (agentic_alert_skill/agentic_metric_skill/agentic_conversation) -- each ChatResult already carries both, and conversation_id was already threaded up to the Alert/Metric/ConversationRunResult layer, but neither ever reached the top-level evaluate_agentic_* return value or its failure exception, so run_agentic_items had nothing to read. Mirrors the reasoning_steps idiom exactly: widens each evaluate_agentic_*'s return from list[str] to (reasoning_steps, conversation_id, response_id), attaches all three to the raised exception on failure, and has run_agentic_items unpack either form (tuple or the untouched kinds' bare list/None) onto ItemReport.conversation_id /response_id. response_id is new at the RunResult layer for all three kinds -- captured as the last non-null value across a run's turns, same pattern already used for reasoning_steps accumulation. general_question/guardrail/search_tool/visualization untouched (already populated via the single-turn runner.py path, not this one). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 018a619 commit b47e42c

8 files changed

Lines changed: 84 additions & 23 deletions

File tree

packages/gooddata-eval/src/gooddata_eval/cli/agentic_runner.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ def _dispatch_agentic(
8585
run_ts: str,
8686
model_version_override: str | None,
8787
reasoning_effort: ReasoningEffort | None = None,
88-
) -> list[str] | None:
88+
) -> tuple[list[str], str, str | None] | list[str] | None:
8989
"""Call the appropriate evaluate_agentic_* function for the item's test_kind.
9090
91-
Returns whatever that function returns -- only alert_skill/metric_skill/conversation
92-
currently return their reasoning_steps; the rest still return None (unchanged).
91+
Returns whatever that function returns -- alert_skill/metric_skill/conversation return
92+
their (reasoning_steps, conversation_id, response_id); the rest still return None
93+
(unchanged).
9394
"""
9495
kind = item.test_kind
9596
eo = item.expected_output
@@ -223,16 +224,23 @@ def run_agentic_items(
223224
)
224225
t0 = time.perf_counter()
225226
try:
226-
reasoning_steps = _dispatch_agentic(
227+
outcome = _dispatch_agentic(
227228
item, host, token, workspace_id, k, langfuse, run_ts, model_version, reasoning_effort
228229
)
230+
reasoning_steps, conversation_id, response_id = (
231+
outcome if isinstance(outcome, tuple) else (outcome, None, None)
232+
)
229233
item_report.pass_at_k = True
230234
item_report.runs = k
231235
item_report.reasoning_steps = reasoning_steps or []
236+
item_report.conversation_id = conversation_id
237+
item_report.response_id = response_id
232238
except AssertionError as exc:
233239
item_report.pass_at_k = False
234240
item_report.runs = k
235241
item_report.reasoning_steps = getattr(exc, "reasoning_steps", None) or []
242+
item_report.conversation_id = getattr(exc, "conversation_id", None)
243+
item_report.response_id = getattr(exc, "response_id", None)
236244
print(f"[agentic] {item.id} FAIL: {exc}", flush=True)
237245
except Exception as exc:
238246
item_report.error = f"{type(exc).__name__}: {exc}"

packages/gooddata-eval/src/gooddata_eval/core/agentic/alert_skill.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ class AlertRunResult:
303303
eval: AlertEvaluation
304304
actual_alert_arguments: dict
305305
reasoning_steps: list[str] = field(default_factory=list)
306+
response_id: str | None = None
306307

307308

308309
@dataclass
@@ -449,6 +450,7 @@ def _run_once(conv_id: str) -> AlertRunResult:
449450
actual_args: dict = {}
450451
tool_called = False
451452
reasoning_steps: list[str] = []
453+
response_id: str | None = None
452454
# conversation_history stores prior turns for GPT-4o context.
453455
# Roles follow GPT-4o's perspective: "assistant"=agent text, "user"=sim-user reply.
454456
conversation_history: list = []
@@ -457,6 +459,7 @@ def _run_once(conv_id: str) -> AlertRunResult:
457459
for _iteration in range(max_iterations):
458460
chat_result = client.send_message(conv_id, current_question)
459461
reasoning_steps.extend(chat_result.reasoning_steps or [])
462+
response_id = chat_result.response_id or response_id
460463
alert_id, actual_args, tool_called = _extract_alert_call(chat_result.tool_call_events or [])
461464
if tool_called:
462465
alert_id_to_delete = alert_id
@@ -493,6 +496,7 @@ def _run_once(conv_id: str) -> AlertRunResult:
493496
eval=ev,
494497
actual_alert_arguments=actual_args,
495498
reasoning_steps=reasoning_steps,
499+
response_id=response_id,
496500
)
497501
finally:
498502
if alert_id_to_delete:
@@ -544,6 +548,8 @@ class AlertSkillAssertionError(AssertionError):
544548

545549
__tracebackhide__ = True
546550
reasoning_steps: list[str]
551+
conversation_id: str
552+
response_id: str | None
547553

548554

549555
def evaluate_agentic_alert_skill(
@@ -562,12 +568,14 @@ def evaluate_agentic_alert_skill(
562568
model_version_override: str | None = None,
563569
run_metadata_extra: dict | None = None,
564570
reasoning_effort: ReasoningEffort | None = None,
565-
) -> list[str]:
571+
) -> tuple[list[str], str, str | None]:
566572
"""Run alert-skill evaluation, log to Langfuse, and raise AlertSkillAssertionError on failure.
567573
568-
Returns the best run's reasoning_steps on success; on failure the same list is attached
569-
to the raised exception as ``.reasoning_steps`` (mirrors the `conversation_id`-on-exception
570-
idiom in `ChatClient.ask()`) so callers can retrieve it either way.
574+
Returns the best run's (reasoning_steps, conversation_id, response_id) on success; on
575+
failure the same three values are attached to the raised exception as
576+
``.reasoning_steps``/``.conversation_id``/``.response_id`` (mirrors the
577+
`conversation_id`-on-exception idiom in `ChatClient.ask()`) so callers can retrieve them
578+
either way.
571579
"""
572580
from datetime import datetime as _dt # noqa: PLC0415
573581
from datetime import timezone as _tz # noqa: PLC0415
@@ -650,5 +658,7 @@ def evaluate_agentic_alert_skill(
650658
f"Actual args: {best.actual_alert_arguments}"
651659
)
652660
exc.reasoning_steps = best.reasoning_steps
661+
exc.conversation_id = best.conversation_id
662+
exc.response_id = best.response_id
653663
raise exc
654-
return summary.best.reasoning_steps
664+
return summary.best.reasoning_steps, summary.best.conversation_id, summary.best.response_id

packages/gooddata-eval/src/gooddata_eval/core/agentic/conversation.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ class ConversationResult:
266266
conversation_success: bool
267267
total_clarification_turns: int
268268
reasoning_steps: list[str] = field(default_factory=list)
269+
response_id: str | None = None
269270

270271

271272
def run_agentic_conversation(
@@ -295,6 +296,7 @@ def run_agentic_conversation(
295296
# the end — a later turn may $ref a metric an earlier turn created.
296297
created_metric_ids: list[str] = []
297298
reasoning_steps: list[str] = []
299+
response_id: str | None = None
298300

299301
try:
300302
if initial_conversation_id is not None:
@@ -332,6 +334,7 @@ def run_agentic_conversation(
332334
final_result = chat_result
333335
all_tool_calls.extend(chat_result.tool_call_events or [])
334336
reasoning_steps.extend(chat_result.reasoning_steps or [])
337+
response_id = chat_result.response_id or response_id
335338

336339
if _check_output_present(resolved_turn, chat_result):
337340
break
@@ -396,6 +399,7 @@ def run_agentic_conversation(
396399
conversation_success=conversation_success,
397400
total_clarification_turns=total_clarification_turns,
398401
reasoning_steps=reasoning_steps,
402+
response_id=response_id,
399403
)
400404

401405

@@ -404,6 +408,8 @@ class ConversationAssertionError(AssertionError):
404408

405409
__tracebackhide__ = True
406410
reasoning_steps: list[str]
411+
conversation_id: str
412+
response_id: str | None
407413

408414

409415
def evaluate_agentic_conversation(
@@ -420,12 +426,13 @@ def evaluate_agentic_conversation(
420426
model_version_override: str | None = None,
421427
run_metadata_extra: dict | None = None,
422428
reasoning_effort: ReasoningEffort | None = None,
423-
) -> list[str]:
429+
) -> tuple[list[str], str, str | None]:
424430
"""Run conversation evaluation, log to Langfuse, and raise on failure.
425431
426-
Returns the conversation's reasoning_steps on success; on failure the same list is
427-
attached to the raised exception as ``.reasoning_steps`` (mirrors the
428-
`conversation_id`-on-exception idiom in `ChatClient.ask()`) so callers can retrieve it
432+
Returns the conversation's (reasoning_steps, conversation_id, response_id) on success;
433+
on failure the same three values are attached to the raised exception as
434+
``.reasoning_steps``/``.conversation_id``/``.response_id`` (mirrors the
435+
`conversation_id`-on-exception idiom in `ChatClient.ask()`) so callers can retrieve them
429436
either way.
430437
"""
431438
from datetime import datetime as _dt # noqa: PLC0415
@@ -509,5 +516,7 @@ def evaluate_agentic_conversation(
509516
f"Failed turns: {[t.turn_id for t in failed_turns]}"
510517
)
511518
exc.reasoning_steps = result.reasoning_steps
519+
exc.conversation_id = result.conversation_id
520+
exc.response_id = result.response_id
512521
raise exc
513-
return result.reasoning_steps
522+
return result.reasoning_steps, result.conversation_id, result.response_id

packages/gooddata-eval/src/gooddata_eval/core/agentic/metric_skill.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ class MetricRunResult:
127127
maql_correct: bool
128128
total_turns: float
129129
reasoning_steps: list[str] = field(default_factory=list)
130+
response_id: str | None = None
130131

131132

132133
@dataclass
@@ -204,12 +205,14 @@ def _execute_single_metric_run(
204205
turns = 0
205206
current_question = question
206207
reasoning_steps: list[str] = []
208+
response_id: str | None = None
207209

208210
try:
209211
for _iteration in range(max_iterations):
210212
turns += 1
211213
chat_result = client.send_message(conversation_id, current_question)
212214
reasoning_steps.extend(chat_result.reasoning_steps or [])
215+
response_id = chat_result.response_id or response_id
213216
candidate = _extract_metric_result(chat_result.tool_call_events or [])
214217
if candidate is not None:
215218
metric_result = candidate
@@ -237,6 +240,7 @@ def _execute_single_metric_run(
237240
maql_correct=maql_correct,
238241
total_turns=float(turns),
239242
reasoning_steps=reasoning_steps,
243+
response_id=response_id,
240244
)
241245
finally:
242246
if metric_id_to_delete:
@@ -305,6 +309,8 @@ class MetricSkillAssertionError(AssertionError):
305309

306310
__tracebackhide__ = True
307311
reasoning_steps: list[str]
312+
conversation_id: str
313+
response_id: str | None
308314

309315

310316
def evaluate_agentic_metric_skill(
@@ -323,12 +329,14 @@ def evaluate_agentic_metric_skill(
323329
model_version_override: str | None = None,
324330
run_metadata_extra: dict | None = None,
325331
reasoning_effort: ReasoningEffort | None = None,
326-
) -> list[str]:
332+
) -> tuple[list[str], str, str | None]:
327333
"""Run metric-skill evaluation, log to Langfuse, and raise MetricSkillAssertionError on failure.
328334
329-
Returns the best run's reasoning_steps on success; on failure the same list is attached
330-
to the raised exception as ``.reasoning_steps`` (mirrors the `conversation_id`-on-exception
331-
idiom in `ChatClient.ask()`) so callers can retrieve it either way.
335+
Returns the best run's (reasoning_steps, conversation_id, response_id) on success; on
336+
failure the same three values are attached to the raised exception as
337+
``.reasoning_steps``/``.conversation_id``/``.response_id`` (mirrors the
338+
`conversation_id`-on-exception idiom in `ChatClient.ask()`) so callers can retrieve them
339+
either way.
332340
"""
333341
from datetime import datetime as _dt # noqa: PLC0415
334342
from datetime import timezone as _tz # noqa: PLC0415
@@ -400,5 +408,7 @@ def evaluate_agentic_metric_skill(
400408
f"Actual MAQL: {best.actual_maql}."
401409
)
402410
exc.reasoning_steps = best.reasoning_steps
411+
exc.conversation_id = best.conversation_id
412+
exc.response_id = best.response_id
403413
raise exc
404-
return summary.best.reasoning_steps
414+
return summary.best.reasoning_steps, summary.best.conversation_id, summary.best.response_id

packages/gooddata-eval/tests/test_agentic_alert_skill.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ def test_evaluate_agentic_alert_skill_returns_reasoning_steps_on_pass():
565565
patch("gooddata_eval.core.agentic.alert_skill.ChatClient", return_value=mock_client),
566566
patch("gooddata_eval.core.agentic.alert_skill._delete_alert"),
567567
):
568-
reasoning = evaluate_agentic_alert_skill(
568+
reasoning, conversation_id, response_id = evaluate_agentic_alert_skill(
569569
host="http://host",
570570
token="tok",
571571
workspace_id="ws1",
@@ -576,6 +576,8 @@ def test_evaluate_agentic_alert_skill_returns_reasoning_steps_on_pass():
576576
)
577577

578578
assert reasoning == ["thinking about it"]
579+
assert conversation_id == "conv-1"
580+
assert response_id is None
579581

580582

581583
def test_evaluate_agentic_alert_skill_attaches_reasoning_steps_to_exception_on_fail():
@@ -604,3 +606,5 @@ def test_evaluate_agentic_alert_skill_attaches_reasoning_steps_to_exception_on_f
604606
max_iterations=1,
605607
)
606608
assert exc_info.value.reasoning_steps == ["confused thinking"]
609+
assert exc_info.value.conversation_id == "conv-1"
610+
assert exc_info.value.response_id is None

packages/gooddata-eval/tests/test_agentic_conversation.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ def test_evaluate_agentic_conversation_returns_reasoning_steps_on_pass():
532532
chat_result.created_visualizations = [MagicMock()]
533533
chat_result.tool_call_events = [tc]
534534
chat_result.reasoning_steps = ["thinking about it"]
535+
chat_result.response_id = "resp-1"
535536
mock_client.send_message.return_value = chat_result
536537

537538
fixture = ConversationFixture(
@@ -547,13 +548,15 @@ def test_evaluate_agentic_conversation_returns_reasoning_steps_on_pass():
547548
],
548549
)
549550
with patch("gooddata_eval.core.agentic.conversation.ChatClient", return_value=mock_client):
550-
reasoning = evaluate_agentic_conversation(
551+
reasoning, conversation_id, response_id = evaluate_agentic_conversation(
551552
host="http://host",
552553
token="tok",
553554
workspace_id="ws1",
554555
fixture=fixture,
555556
)
556557
assert reasoning == ["thinking about it"]
558+
assert conversation_id == "conv-1"
559+
assert response_id == "resp-1"
557560

558561

559562
def test_evaluate_agentic_conversation_attaches_reasoning_steps_to_exception_on_fail():
@@ -568,6 +571,7 @@ def test_evaluate_agentic_conversation_attaches_reasoning_steps_to_exception_on_
568571
chat_result.tool_call_events = [tc]
569572
chat_result.alert_proposals = []
570573
chat_result.reasoning_steps = ["confused thinking"]
574+
chat_result.response_id = "resp-2"
571575
mock_client.send_message.return_value = chat_result
572576

573577
fixture = ConversationFixture(
@@ -594,3 +598,5 @@ def test_evaluate_agentic_conversation_attaches_reasoning_steps_to_exception_on_
594598
max_clarification_turns=0,
595599
)
596600
assert exc_info.value.reasoning_steps == ["confused thinking"]
601+
assert exc_info.value.conversation_id == "conv-1"
602+
assert exc_info.value.response_id == "resp-2"

packages/gooddata-eval/tests/test_agentic_metric_skill.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ def test_evaluate_agentic_metric_skill_returns_reasoning_steps_on_pass():
347347
}
348348
)
349349
with patch("gooddata_eval.core.agentic.metric_skill.ChatClient", return_value=mock_client):
350-
reasoning = evaluate_agentic_metric_skill(
350+
reasoning, conversation_id, response_id = evaluate_agentic_metric_skill(
351351
host="http://host/api/v1/actions/workspaces/ws1/ai",
352352
token="tok",
353353
workspace_id="ws1",
@@ -357,6 +357,8 @@ def test_evaluate_agentic_metric_skill_returns_reasoning_steps_on_pass():
357357
max_iterations=1,
358358
)
359359
assert reasoning == ["thinking about it"]
360+
assert conversation_id == "conv-1"
361+
assert response_id is None
360362

361363

362364
def test_evaluate_agentic_metric_skill_attaches_reasoning_steps_to_exception_on_fail():
@@ -383,3 +385,5 @@ def test_evaluate_agentic_metric_skill_attaches_reasoning_steps_to_exception_on_
383385
max_iterations=1,
384386
)
385387
assert exc_info.value.reasoning_steps == ["confused thinking"]
388+
assert exc_info.value.conversation_id == "conv-1"
389+
assert exc_info.value.response_id is None

packages/gooddata-eval/tests/test_agentic_runner.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def _item(test_kind: str = "agentic_alert_skill") -> DatasetItem:
2020
def test_run_agentic_items_surfaces_reasoning_steps_on_pass():
2121
with patch(
2222
"gooddata_eval.cli.agentic_runner.evaluate_agentic_alert_skill",
23-
return_value=["it created the alert"],
23+
return_value=(["it created the alert"], "conv-1", "resp-1"),
2424
):
2525
report = run_agentic_items(
2626
[_item()],
@@ -31,11 +31,15 @@ def test_run_agentic_items_surfaces_reasoning_steps_on_pass():
3131
)
3232
assert report.items[0].pass_at_k is True
3333
assert report.items[0].reasoning_steps == ["it created the alert"]
34+
assert report.items[0].conversation_id == "conv-1"
35+
assert report.items[0].response_id == "resp-1"
3436

3537

3638
def test_run_agentic_items_surfaces_reasoning_steps_from_exception_on_fail():
3739
exc = AlertSkillAssertionError("nope")
3840
exc.reasoning_steps = ["it got confused"]
41+
exc.conversation_id = "conv-2"
42+
exc.response_id = "resp-2"
3943
with patch("gooddata_eval.cli.agentic_runner.evaluate_agentic_alert_skill", side_effect=exc):
4044
report = run_agentic_items(
4145
[_item()],
@@ -46,6 +50,8 @@ def test_run_agentic_items_surfaces_reasoning_steps_from_exception_on_fail():
4650
)
4751
assert report.items[0].pass_at_k is False
4852
assert report.items[0].reasoning_steps == ["it got confused"]
53+
assert report.items[0].conversation_id == "conv-2"
54+
assert report.items[0].response_id == "resp-2"
4955

5056

5157
def test_run_agentic_items_defaults_reasoning_steps_to_empty_when_exception_has_none():
@@ -61,6 +67,8 @@ def test_run_agentic_items_defaults_reasoning_steps_to_empty_when_exception_has_
6167
run_ts="2026-01-01",
6268
)
6369
assert report.items[0].reasoning_steps == []
70+
assert report.items[0].conversation_id is None
71+
assert report.items[0].response_id is None
6472

6573

6674
def test_run_agentic_items_defaults_reasoning_steps_to_empty_for_untouched_kinds():
@@ -75,3 +83,5 @@ def test_run_agentic_items_defaults_reasoning_steps_to_empty_for_untouched_kinds
7583
)
7684
assert report.items[0].pass_at_k is True
7785
assert report.items[0].reasoning_steps == []
86+
assert report.items[0].conversation_id is None
87+
assert report.items[0].response_id is None

0 commit comments

Comments
 (0)