Skip to content

Commit 17cbe61

Browse files
Tomkessclaude
andcommitted
fix(gooddata-eval): count either producing tool in refusal scoring
A refusal is a creation shape, so `_producing_tool` returns `draft_dashboard` and the flag matched only that name. An agent that routed to the editor and had a `patch_dashboard` call rejected left the same empty result and empty part, so it passed as a clean refusal -- the same hole the previous commit closed, through the other tool. Both names count now. The flag is read only by the refusal branch, so widening it tightens that shape and changes nothing else. Found in review by CodeRabbit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent a047206 commit 17cbe61

2 files changed

Lines changed: 26 additions & 6 deletions

File tree

‎packages/gooddata-eval/src/gooddata_eval/core/agentic/dashboard_skill.py‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -769,9 +769,9 @@ class DashboardEvaluation:
769769
titles_matched: bool = True
770770
forbidden_absent: bool = True
771771
answer_matched: bool = True
772-
# Whether the producing tool was called AT ALL, successful or not. Only a refusal case
773-
# reads it: everywhere else a failed call is a retry the agent recovered from, which is
774-
# why `_extract_tool_result` skips it.
772+
# Whether EITHER dashboard-producing tool was called at all, successful or not. Only a
773+
# refusal case reads it: everywhere else a failed call is a retry the agent recovered
774+
# from, which is why `_extract_tool_result` skips it.
775775
producing_tool_called: bool = False
776776
failures: list[str] = field(default_factory=list)
777777
notes: list[str] = field(default_factory=list)
@@ -935,7 +935,10 @@ def evaluate_dashboard_response(
935935
else []
936936
),
937937
*(
938-
[f"the case expects no dashboard, but the agent called {tool} (the call did not succeed)"]
938+
[
939+
"the case expects no dashboard, but the agent called a dashboard-producing "
940+
"tool (the call did not succeed)"
941+
]
939942
if producing_tool_called and not drafted
940943
else []
941944
),
@@ -1216,8 +1219,11 @@ def _execute_single_dashboard_run(
12161219
_skill_activated(all_tool_call_events, _required_skill(expected_output)),
12171220
patch_part=patch_part,
12181221
answer_text=answer_text,
1219-
# Any call, not just a successful one -- see DashboardEvaluation.
1220-
producing_tool_called=any(tc.function_name == tool for tc in all_tool_call_events),
1222+
# Any call, not just a successful one -- see DashboardEvaluation. BOTH tools,
1223+
# not the one this case selected: a refusal is a creation shape, so `tool` is
1224+
# `draft_dashboard`, and an agent that routed to the editor and had a
1225+
# `patch_dashboard` call rejected would otherwise leave this false and pass.
1226+
producing_tool_called=any(tc.function_name in (_DRAFT_TOOL, _PATCH_TOOL) for tc in all_tool_call_events),
12211227
),
12221228
tool_result=tool_result,
12231229
dashboard_part=dashboard_part,

‎packages/gooddata-eval/tests/test_agentic_dashboard_skill.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,6 +1515,20 @@ def test_a_failed_draft_attempt_is_read_off_the_turn_not_the_result(self):
15151515
assert summary.best.evaluation.producing_tool_called
15161516
assert not summary.best.evaluation.strict_pass
15171517

1518+
def test_a_rejected_patch_call_counts_too(self):
1519+
"""A refusal is a creation shape, so the case's selected tool is `draft_dashboard`.
1520+
An agent that routed to the editor instead and had its `patch_dashboard` call
1521+
rejected leaves the same empty result, so matching only the selected tool would let
1522+
it pass."""
1523+
client = MagicMock()
1524+
client.send_message.return_value = _chat_result(
1525+
tool_calls=[_tool_call("patch_dashboard", {"status": "error", "message": "no such dashboard"})],
1526+
text="There is no fraud data in this workspace, so I cannot build that.",
1527+
)
1528+
summary = _run_with(client, self._NO_DATA)
1529+
assert summary.best.evaluation.producing_tool_called
1530+
assert not summary.best.evaluation.strict_pass
1531+
15181532
def test_an_answer_alone_still_passes_when_nothing_was_called(self):
15191533
"""The control for the two above: the flag must not fail a genuine refusal."""
15201534
client = MagicMock()

0 commit comments

Comments
 (0)