Skip to content

fix(gooddata-eval): retry KDA on any non-triggering response, drop text classification - #1733

Merged
FrankHuynh merged 1 commit into
masterfrom
QA-28800-kda-clarification-heuristic
Aug 13, 2026
Merged

fix(gooddata-eval): retry KDA on any non-triggering response, drop text classification#1733
FrankHuynh merged 1 commit into
masterfrom
QA-28800-kda-clarification-heuristic

Conversation

@FrankHuynh

@FrankHuynh FrankHuynh commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

What

_is_asking_kda_clarification tried to classify agent responses as "asking for clarification" vs "a final answer" -- a simulated user reply was only sent when the text matched. That heuristic missed a real, common response shape: a clarifying question immediately followed by a bullet list of the options being offered, e.g.:

I found two different "Total Net Revenue" metrics in your data model.
Which one should I analyze for the 2024 vs 2023 drop?

- {metric/metric_l1_sql_net_sales_summary_net_revenue}
- {metric/metric_l1_total_net_revenue}

The message doesn't end on "?" itself (it ends on the list), so the harness treated this as a final answer and gave up after turn 1 -- never nudging the simulated user to pick an option. The case scored triggered=False, a hard failure, even though one of the two offered metrics was the expected answer.

How found

Real CI trace (QA-28800, gpt56luna_openai combo, globalmart workspace): trace ba72a6d2c19a3ceeaa354ec641bc52f3. kda_disambiguated stayed False and the Langfuse session had only 1 trace total -- confirming the simulated-reply path was never reached.

Fix (redesigned per review)

A first pass patched the heuristic directly (wider bullet-marker support, "?" no longer needing to be the literal last character, plus a period_hint gap and a None 'None' prompt bug fixed alongside). Review (chi My) pointed out the cost of the two error directions here is asymmetric: missing a genuine clarifying question hard-fails the run, while misreading a final answer as one only costs one harmless extra turn -- the loop already breaks for good once create_args is set, so a false positive can never turn a pass into a fail. Every other skill in this package (visualization.py, alert_skill.py) already solves this the cheap way: never classify the text at all, just break on the goal signal (tool called / artifact produced) or an empty response, and otherwise always retry. Patching the KDA-specific heuristic for yet another response shape (this round: **Option 1**: ..., bold markdown with no space after the marker) would have meant chasing an open-ended list of shapes forever.

_is_asking_kda_clarification and _LIST_ITEM_RE are removed entirely. _run_once now matches visualization.py/alert_skill.py's own break conditions -- create_args set, or an empty response -- and otherwise always sends a simulated reply, regardless of what the agent's text says or how it's formatted. _DEFAULT_MAX_ITERATIONS bumped 3 -> 4 (3 was sized exactly for 2 real questions -- metric and period -- with zero slack for a wasted turn; every other skill in the package budgets 4-7).

Also fixed along the way:

  • generate_simulated_kda_response only ever knew about measure candidates, even when the agent's question was about the period to compare instead -- it had nothing period-specific to answer with. Extracted into _build_period_hint(), built from whichever of expected_output's Date Attribute/Analyzed Period/Reference Period fields are present (not requiring all three).
  • The prompt asserted "an acceptable metric/fact is None 'None'" as a real option when measure_candidates was None/empty (e.g. a period-only question) -- likely to make gpt-4o-mini invent a metric literally named "None". Extracted into _build_clarification_prompt(), which now omits the "For reference, ..." clause entirely when there's nothing usable to reference.

Tests

_is_asking_kda_clarification's own unit tests removed along with the function. The end-to-end run_agentic_kda_skill regression tests for the real captured trace and the period-clarification case stay (now exercising the always-retry path instead of a classifier match), plus:

  • A new test for the bold-markdown case chi My's review flagged (**Option 1**: ...).
  • Direct unit tests for _build_period_hint (every field-presence combination) and _build_clarification_prompt (the None-candidates fix).

48 tests in test_agentic_kda_skill.py, all passing.

Verification

  • ruff check / ruff format --check: clean.
  • pytest packages/gooddata-eval/tests/: 349 passed, same 9 pre-existing unrelated failures (missing openai module in this venv) as on master.

Not included in this PR

JIRA: QA-28800

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of clarifying questions followed by bulleted, numbered, or table-based options.
    • Preserved clarification detection when questions end directly with a question mark.
    • Prevented option lists followed by explanatory text from being misclassified.
    • Improved metric and comparison-period disambiguation in follow-up interactions.
    • Follow-up responses now better account for available metric and period context.
    • Increased resilience when additional clarification is needed during KDA analysis.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 09ce8dd8-b35e-46a5-ab7f-280cd3a7ac4e

📥 Commits

Reviewing files that changed from the base of the PR and between e68ef79 and 931817c.

📒 Files selected for processing (2)
  • packages/gooddata-eval/src/gooddata_eval/core/agentic/kda_skill.py
  • packages/gooddata-eval/tests/test_agentic_kda_skill.py

📝 Walkthrough

Walkthrough

The KDA skill now sends a simulated reply after each non-empty, non-final response. It supports metric and period hints, raises the default iteration limit to four, and retains retry termination on failure. Tests cover metric and period clarification flows.

Changes

KDA simulated clarification handling

Layer / File(s) Summary
Period-aware simulated responses
packages/gooddata-eval/src/gooddata_eval/core/agentic/kda_skill.py, packages/gooddata-eval/tests/test_agentic_kda_skill.py
Prompt construction includes only available metric and period references. The simulator accepts an optional period hint and answers the assistant’s actual question. Tests cover missing, partial, and combined candidates.
Clarification retry integration
packages/gooddata-eval/src/gooddata_eval/core/agentic/kda_skill.py, packages/gooddata-eval/tests/test_agentic_kda_skill.py
The retry flow generates simulated replies for non-empty, non-final responses. It derives metric and period context from expected output and stops on empty responses or the iteration limit.
Clarification end-to-end coverage
packages/gooddata-eval/tests/test_agentic_kda_skill.py
Tests cover metric questions with bullet and bold Markdown options, period clarification, follow-up turns, and successful KDA execution.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Mergeability Score: ⚪ Minimal · up to 93181

The PR improves clarification handling for option lists and period questions, with targeted end-to-end coverage and reported checks passing. No actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant KDAEvaluator
  participant ExpectedOutput
  participant SimulatedResponseGenerator
  participant KDAAgent
  KDAEvaluator->>KDAAgent: submit initial KDA request
  KDAAgent-->>KDAEvaluator: return non-final response
  KDAEvaluator->>ExpectedOutput: derive metric and period hints
  ExpectedOutput-->>KDAEvaluator: return available context
  KDAEvaluator->>SimulatedResponseGenerator: generate simulated reply
  SimulatedResponseGenerator-->>KDAEvaluator: return follow-up response
  KDAEvaluator->>KDAAgent: submit simulated reply
  KDAAgent-->>KDAEvaluator: return final KDA result
Loading

Possibly related PRs

Suggested reviewers: lupko, pcerny, hkad98

Poem

A rabbit hears a question hop,
Sends a reply and does not stop.
Metric hints and periods guide,
Four small turns keep things aligned.
Tests watch every burrowed stride.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main changes: retrying KDA for non-triggering responses and removing text classification.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/gooddata-eval/tests/test_agentic_kda_skill.py (1)

117-138: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add numbered-option regression cases.

The requirement includes numbered option lists, but these tests cover only - bullet markers. Add positive cases for both 1. and 1) formats, which _LIST_ITEM_RE accepts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/gooddata-eval/tests/test_agentic_kda_skill.py` around lines 117 -
138, Extend the clarification detection tests around
_is_asking_kda_clarification with positive regression cases whose option lists
use both “1.” and “1)” markers. Keep the question followed by the numbered list
at the end of the message, and assert the helper returns true, covering the two
formats accepted by _LIST_ITEM_RE.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/gooddata-eval/tests/test_agentic_kda_skill.py`:
- Around line 117-138: Extend the clarification detection tests around
_is_asking_kda_clarification with positive regression cases whose option lists
use both “1.” and “1)” markers. Keep the question followed by the numbered list
at the end of the message, and assert the helper returns true, covering the two
formats accepted by _LIST_ITEM_RE.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1c3772f6-7323-4def-bfc1-9ce4eb1da797

📥 Commits

Reviewing files that changed from the base of the PR and between 8ad7eb3 and 67f6c97.

📒 Files selected for processing (2)
  • packages/gooddata-eval/src/gooddata_eval/core/agentic/kda_skill.py
  • packages/gooddata-eval/tests/test_agentic_kda_skill.py

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.59459% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.44%. Comparing base (8ad7eb3) to head (931817c).

Files with missing lines Patch % Lines
...a-eval/src/gooddata_eval/core/agentic/kda_skill.py 94.59% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1733      +/-   ##
==========================================
+ Coverage   79.42%   79.44%   +0.02%     
==========================================
  Files         272      272              
  Lines       18997    19012      +15     
==========================================
+ Hits        15088    15105      +17     
+ Misses       3909     3907       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@FrankHuynh
FrankHuynh force-pushed the QA-28800-kda-clarification-heuristic branch from 67f6c97 to 3a76027 Compare August 12, 2026 14:17
@FrankHuynh FrankHuynh changed the title fix(gooddata-eval): detect KDA clarifying questions followed by an option list fix(gooddata-eval): detect KDA clarifying questions about metric or period Aug 12, 2026

@myhoai myhoai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the full core/agentic/ package to put this fix in context. The change is correct and minimal — the new branch matches the captured trace, and none of the five existing _is_asking_kda_clarification tests regress. Two things I'd like to discuss before merge.

1. The cost of the two error directions is very asymmetric, and the heuristic is biased the wrong way

_is_asking_kda_clarification is only ever reached when create_args is None (kda_skill.py:226 already broke out otherwise), i.e. only on runs that are currently failing.

  • False negative (agent is asking, we think it answered) → break after turn 1 → triggered=False → hard failure. That's QA-28800.
  • False positive (agent answered, we think it's asking) → one extra turn + one gpt-4o-mini call, plus noise on kda_disambiguated. It cannot turn a pass into a fail, because the loop already broke once create_args was set.

So false negatives are expensive and false positives are nearly free — but the matcher is written strict-first (endswith("?")), which optimises for the cheap direction.

I ran the post-fix heuristic against neighbouring response shapes:

Response shape Result
Which one?\n- a\n- b (the captured trace) ✅ True
Which one?\n- a\n- b\n\nLet me know which one you prefer. False
question + list where an item wraps onto an indented continuation line False
or bullets instead of -/* False
options rendered as a markdown table False
**Option 1** … (bold, no space after *) False

Row 2 — question, option list, one closing sentence — is a very common LLM shape and is my best guess at the next ticket. The fix patches one shape; the adjacent ones still fail identically.

2. Every other skill in this package solves this differently — KDA is the only outlier

Module Break condition "Is it asking?" detection Budget
general_question, guardrail, search_tool none, single turn 1
visualization.py:165-179 viz produced, or empty text_response none — never classifies 4
alert_skill.py:455-474 tool_called, or empty response with no tool calls none — never classifies 6
metric_skill.py:168-172 metric created "?" in t or "could you" or "please provide" or "clarif" 7
conversation.py:195-199, 330 turn ends same but looser ("please"), plus the structural chat_result.alert_proposals signal 20
kda_skill.py:236 create called endswith("?") + the new list branch 3

visualization and alert_skill — the two closest analogues (run a skill, produce an artifact, may need a round trip) — deliberately never classify the text at all. They break on the goal signal (viz created / alert tool called) and on an empty response, and otherwise just nudge again. That's exactly the asymmetric-cost reasoning above, and it makes this whole class of bug structurally impossible.

KDA already has the equivalent goal signal (create_args is not None, kda_skill.py:226). That makes _is_asking_kda_clarification a pure extra early-give-up condition — dropping it turns KDA into the viz/alert model.

Suggestion: rather than adding a second regex rule, consider matching visualization.py:165-179: break on create_args is not None or empty response_text, otherwise always send a simulated reply. That fixes QA-28800 and every shape in the table above, with no further tickets. If you keep the classifier, at minimum please widen _LIST_ITEM_RE and tolerate a trailing prose line. Either way _DEFAULT_MAX_ITERATIONS = 3 deserves a bump — kda_skill.py:18-20 says 3 is already sized exactly for "metric and period each needing their own question", so there is no slack for a wasted turn (viz/alert/metric carry 4–7).

If there is a KDA-specific reason it must classify where viz and alert don't, that's worth a line in the docstring — the current "each skill's heuristic has already drifted independently" reads as license to drift, but the actual drift in this package is only between loose and absent.

Rest is nits, inline. Nice trace-backed problem statement in the description, and thanks for the period-hint plumbing — that gap was real.

Comment thread packages/gooddata-eval/src/gooddata_eval/core/agentic/kda_skill.py Outdated
Comment thread packages/gooddata-eval/src/gooddata_eval/core/agentic/kda_skill.py Outdated
Comment thread packages/gooddata-eval/src/gooddata_eval/core/agentic/kda_skill.py Outdated
Comment thread packages/gooddata-eval/src/gooddata_eval/core/agentic/kda_skill.py Outdated
Comment thread packages/gooddata-eval/tests/test_agentic_kda_skill.py
@FrankHuynh
FrankHuynh force-pushed the QA-28800-kda-clarification-heuristic branch from 3a76027 to e68ef79 Compare August 13, 2026 02:39
…xt classification

_is_asking_kda_clarification tried to classify agent responses as "asking for
clarification" vs "a final answer" via a "?"-based heuristic, so a simulated user
reply was only sent when it matched. That heuristic missed a real, common response
shape: a clarifying question immediately followed by a bullet list of the options
being offered (e.g. "Which metric?\n- metric A\n- metric B") -- the message doesn't
end on "?" itself, so the run gave up after turn 1 instead of ever nudging the
simulated user to pick one, scoring a genuinely-ambiguous case as triggered=False.

Found via a real CI trace (QA-28800, gpt56luna_openai / globalmart): the chatbot
asked to disambiguate between two "Total Net Revenue" metrics -- one of which was
the expected answer -- but kda_disambiguated stayed False and the session never
got a second turn, confirming the simulated-reply path was never reached.

First pass patched the heuristic (wider bullet-char support, "?" no longer needing
to be the literal last character, a period_hint gap, a "None 'None'" prompt bug).
Review (chi My) pointed out the cost of the two error directions is asymmetric:
missing a genuine clarifying question hard-fails the run, while misreading a final
answer as one only costs one harmless extra turn (the loop already breaks for good
once create_args is set, so a false positive here can never turn a pass into a
fail). Every other skill in this package (visualization.py, alert_skill.py) already
solves this the cheap way: never classify the text at all, just break on the goal
signal (tool called / artifact produced) or an empty response, and otherwise always
retry. Patching the KDA-specific heuristic for one more response shape (this round
it was "**Option 1**: ..." -- bold markdown with no space after the marker) would
have meant chasing an open-ended list of shapes forever.

Fix: dropped _is_asking_kda_clarification and _LIST_ITEM_RE entirely. _run_once now
matches visualization.py/alert_skill.py's own break conditions -- create_args set,
or an empty response -- and otherwise always sends a simulated reply, regardless of
what the agent's text says or how it's formatted. _DEFAULT_MAX_ITERATIONS bumped
3 -> 4 (chi My's point: 3 was sized exactly for 2 real questions with zero slack for
a wasted turn; every other skill in the package budgets 4-7).

Also fixed along the way:
- generate_simulated_kda_response only ever knew about measure candidates, even when
  the agent's question was about the PERIOD to compare instead -- it had nothing
  period-specific to answer with. Extracted into _build_period_hint(), built from
  whichever of expected_output's Date Attribute/Analyzed Period/Reference Period
  fields are present (not requiring all three).
- The prompt asserted "an acceptable metric/fact is None 'None'" as a real option
  when measure_candidates was None/empty (e.g. a period-only question) -- likely to
  make gpt-4o-mini invent a metric literally named "None". Extracted into
  _build_clarification_prompt(), which now omits the "For reference, ..." clause
  entirely when there's nothing usable to reference.

Tests: _is_asking_kda_clarification's own unit tests removed along with the
function; the end-to-end run_agentic_kda_skill regression tests for the real
captured trace and the period-clarification case stay (now exercising the
always-retry path instead of a classifier match), plus a new test for the bold-
markdown case chi My's review flagged, direct unit tests for _build_period_hint
and _build_clarification_prompt, and a bumped _DEFAULT_MAX_ITERATIONS. 48 tests in
test_agentic_kda_skill.py, all passing; package suite unchanged at 9 pre-existing
unrelated failures (missing openai module in this venv).

JIRA: QA-28800
@FrankHuynh
FrankHuynh force-pushed the QA-28800-kda-clarification-heuristic branch from e68ef79 to 931817c Compare August 13, 2026 04:03
@FrankHuynh FrankHuynh changed the title fix(gooddata-eval): detect KDA clarifying questions about metric or period fix(gooddata-eval): retry KDA on any non-triggering response, drop text classification Aug 13, 2026
@FrankHuynh

Copy link
Copy Markdown
Contributor Author

Thanks for the deep dive — went with the redesign, not another patch.

_is_asking_kda_clarification and _LIST_ITEM_RE are removed entirely. _run_once now matches visualization.py/alert_skill.py's own break conditions exactly: break on create_args is not None or an empty response, otherwise always send a simulated reply — no text classification at all. Re-ran your 6-row table against this: all 6 now return "always retry" (including **Option 1** with no space after the marker), since none of them are inspected anymore.

Also bumped _DEFAULT_MAX_ITERATIONS 3 → 4 per your point on the budget having zero slack — matches visualization.py's own budget as the closest analogue.

Left the module docstring's "each skill's heuristic has already drifted independently" line out since there's no more KDA-specific heuristic to justify.

48 tests passing (down from before since _is_asking_kda_clarification's own unit tests went with it), plus a new regression test for the bold-markdown case and direct unit tests for the two helper functions this pulled out (_build_period_hint, _build_clarification_prompt).

@FrankHuynh
FrankHuynh merged commit d5e0fa1 into master Aug 13, 2026
14 checks passed
@FrankHuynh
FrankHuynh deleted the QA-28800-kda-clarification-heuristic branch August 13, 2026 05:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants