-
Notifications
You must be signed in to change notification settings - Fork 71
feat(gooddata-eval): record why an agentic simulated-user loop stopped #1789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
c9a9100
5c819b1
d0eb73a
120bb95
a7c0e55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ | |
| AgenticAssertionError, | ||
| AgenticEvalOutcome, | ||
| ChatResult, | ||
| LoopExit, | ||
| ReasoningStepEvent, | ||
| ToolCallEvent, | ||
| build_latency_breakdown, | ||
|
|
@@ -191,6 +192,10 @@ class KdaRunResult: | |
| response_id: str | None = None | ||
| tool_call_events: list[ToolCallEvent] = field(default_factory=list) | ||
| reasoning_step_events: list[ReasoningStepEvent] = field(default_factory=list) | ||
| # Why the simulated-user loop stopped -- see LoopExit. `triggered=False` alone cannot | ||
| # separate a refusal from a run that hit max_iterations while still on track. | ||
| exit_reason: LoopExit = LoopExit.BUDGET_EXHAUSTED | ||
| turns_used: int = 0 | ||
|
|
||
|
|
||
| @dataclass | ||
|
|
@@ -278,10 +283,15 @@ def _accumulate(result: ChatResult) -> None: | |
| all_tool_call_events.extend(result.tool_call_events or []) | ||
| all_reasoning_step_events.extend(result.reasoning_step_events or []) | ||
|
|
||
| # Defaults to BUDGET_EXHAUSTED: every other exit assigns explicitly, so a loop that | ||
| # simply runs out of range() is labelled correctly with no trailing else. | ||
| exit_reason = LoopExit.BUDGET_EXHAUSTED | ||
| turns_used = 0 | ||
| turns = 0 | ||
| steps = 0 | ||
|
|
||
| for iteration in range(max_iterations): | ||
| turns_used = iteration + 1 | ||
| try: | ||
| chat_result = client.send_message(conv_id, current_question) | ||
| except Exception as exc: # noqa: BLE001 -- end this run, not the whole assertion | ||
|
|
@@ -295,6 +305,7 @@ def _accumulate(result: ChatResult) -> None: | |
| if create_args is not None: | ||
| turn_wall_clock_sec = partial.turn_wall_clock_sec | ||
| turn_completed = False | ||
| exit_reason = LoopExit.CHAT_ERROR | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '270,370p' packages/gooddata-eval/src/gooddata_eval/core/agentic/kda_skill.py
sed -n '1210,1270p' packages/gooddata-eval/tests/test_agentic_kda_skill.py
rg -n 'except ChatError|except Exception|total_turns|turns_used' packages/gooddata-eval/src/gooddata_eval/core/agentic/{alert_skill.py,metric_skill.py,visualization.py,conversation.py}Repository: gooddata/gooddata-python-sdk Length of output: 13454 🏁 Script executed: sed -n '1,80p' packages/gooddata-eval/src/gooddata_eval/core/agentic/kda_skill.py
sed -n '680,725p' packages/gooddata-eval/src/gooddata_eval/core/agentic/alert_skill.py
sed -n '270,305p' packages/gooddata-eval/src/gooddata_eval/core/agentic/metric_skill.py
rg -n -C 5 'ChatError|turns_used|total_turns|send.*fail|failed.*send|RuntimeError' packages/gooddata-eval/tests packages/gooddata-eval/src/gooddata_eval/core/agentic/{kda_skill.py,alert_skill.py,metric_skill.py}Repository: gooddata/gooddata-python-sdk Length of output: 50385 🏁 Script executed: rg -n -C 8 'class ChatError|class TransientChatError|def send_message|RemoteProtocolError|_retryable_exc|except Exception|raise' packages/gooddata-eval/src/gooddata_eval/core/chat/sse_client.py
sed -n '330,390p' packages/gooddata-eval/tests/test_agentic_kda_skill.py
sed -n '1225,1265p' packages/gooddata-eval/tests/test_agentic_kda_skill.pyRepository: gooddata/gooddata-python-sdk Length of output: 15195 Do not convert arbitrary The KDA handler catches Catching 🤖 Prompt for AI Agents |
||
| break | ||
| turns += 1 | ||
| steps += chat_result.reasoning_step_count | ||
|
|
@@ -311,8 +322,10 @@ def _accumulate(result: ChatResult) -> None: | |
| # final either way -- execute_result may still be None (e.g. the skill's | ||
| # execute tool isn't available at all when data-sharing is off for the org). | ||
| turn_wall_clock_sec = chat_result.turn_wall_clock_sec | ||
| exit_reason = LoopExit.SUCCESS | ||
| break | ||
| if not response_text: | ||
| exit_reason = LoopExit.AGENT_SILENT | ||
| break | ||
| if iteration >= max_iterations - 1: | ||
| break | ||
|
|
@@ -328,6 +341,7 @@ def _accumulate(result: ChatResult) -> None: | |
| disambiguated = True | ||
| except Exception as exc: # noqa: BLE001 -- safety net, not the assertion; end only this run | ||
| _log.warning("Simulated KDA user reply failed for conversation %s: %s", conv_id, exc) | ||
| exit_reason = LoopExit.SIMULATED_USER_FAILED | ||
| break | ||
|
|
||
| ev = _evaluate_run(create_args, execute_result, turn_completed, disambiguated) | ||
|
|
@@ -343,6 +357,8 @@ def _accumulate(result: ChatResult) -> None: | |
| response_id=response_id, | ||
| tool_call_events=all_tool_call_events, | ||
| reasoning_step_events=all_reasoning_step_events, | ||
| exit_reason=exit_reason, | ||
| turns_used=turns_used, | ||
| ) | ||
|
|
||
| try: | ||
|
|
@@ -505,6 +521,10 @@ def _write_scores(ctx: RunTraceContext) -> None: | |
| "disambiguated": ev.disambiguated, | ||
| "actual_create_args": best.actual_create_args, | ||
| "actual_execute_result": best.actual_execute_result, | ||
| # Why the loop stopped -- see LoopExit. | ||
| "exit_reason": best.exit_reason.value, | ||
| "turns_used": best.turns_used, | ||
| "max_iterations": max_iterations, | ||
| "latency_breakdown": build_latency_breakdown(best.tool_call_events, best.reasoning_step_events), | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Process completed
create_metric_alertevents fromChatError.partial_result.When
send_messageraisesChatErrorafter a successfulcreate_metric_alertevent, this handler exits without processingexc.partial_result. The alert ID then never reachesalert_id_to_delete, so cleanup does not delete the created alert. Process the completed event and register its alert ID before settingexit_reasonand breaking.🤖 Prompt for AI Agents