Skip to content

fix(#158): surface Gemini finish_reason in degrade reasoning + raise e2e floor to 4096 - #160

Merged
wjduenow merged 2 commits into
devfrom
fix/158-gemini-grader-degrade
May 29, 2026
Merged

fix(#158): surface Gemini finish_reason in degrade reasoning + raise e2e floor to 4096#160
wjduenow merged 2 commits into
devfrom
fix/158-gemini-grader-degrade

Conversation

@wjduenow

@wjduenow wjduenow commented May 28, 2026

Copy link
Copy Markdown
Owner

Two fixes from the post-#155 full-pipeline finding (#158):

1. Diagnostic upgrade (signalforge.grade.engine)

New _format_degrade_reasoning(exc) helper. When the wrapped cause is LLMResponseFormatError, surface its bare message (which names the vendor finish_reason field + value via LLMProvider.unclean_finish_reason_message) into the degraded GradingResult.reasoning:

Before:

"call failed: GradeLLMError"

After:

"call failed: GradeLLMError: Gemini response did not finish with a clean stop reason (finish_reason='MAX_TOKENS')..."

For every other cause (auth / rate-limit / parser / budget) the existing class-name shape is preserved verbatim — only the response-shape branch grows the diagnostic, so the audit corpus stays diff-clean for the 90% case.

Two new unit tests pin both the new + preserved shapes.

2. Floor bump (e2e overlay)

grade_max_output_tokens overlay raised 2048 → 4096 in both test_e2e_gemini_smoke.py and the [gemini] parametrization of test_e2e_bigquery_smoke.py.

The 2048 figure was the in-isolation 5-pair smoke floor verified in #155 DEC-008. The first full-pipeline run found 5–6/108 pairs still degrade at 2048 (Gemini's per-pair reasoning length is high-variance enough that the in-isolation floor is not the full-fixture floor). 4096 is the new #158 fixture-scale floor.

Doc reframes (in lockstep)

  • docs/grade-ops.md + docs/draft-ops.md — Gemini row of the recommended-floors table bumped 2048 → 4096, with a "fixture-scale caveat" callout explaining the floor is necessary but not sufficient and operators should watch aggregate_complete on their own fixture.
  • plans/super/155-...md DEC-008 — addendum reframing the 2048 "verified safe" claim as scoped to the in-isolation probe; the DEC stays as historical record of the 2048 figure's provenance.
  • tests/grade/test_gemini_neutrality.py — the existing safety-blocked-response contract test moves from bad.reasoning == "call failed: GradeLLMError" to bad.reasoning.startswith("call failed: GradeLLMError: ") + "finish_reason='SAFETY'" in bad.reasoning. The contract pin moves with the broadened reasoning.

Validation

Beads

  • bd_1-scaffolding-we0 — diagnostic upgrade
  • bd_1-scaffolding-ila — floor bump + doc reframe

Closes #158.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Updated Gemini's recommended maximum output token limit from 2048 to 4096 to prevent truncation in production environments.
  • Improvements

    • Enhanced error diagnostics when grading fails to include provider-specific failure details for better troubleshooting.
  • Tests

    • Updated test configurations to reflect new token limits and added coverage for improved error formatting.

Review Change Stack

…e2e floor to 4096

Two fixes from the post-#155 full-pipeline finding (#158):

1. **Diagnostic upgrade (`signalforge.grade.engine`).** Add
   `_format_degrade_reasoning(exc)` helper. When the wrapped cause is
   `LLMResponseFormatError`, surface its bare `message` (which names
   the vendor `finish_reason` field + value via
   `LLMProvider.unclean_finish_reason_message`) into the degraded
   `GradingResult.reasoning` — e.g.
   `"call failed: GradeLLMError: Gemini response did not finish with
   a clean stop reason (finish_reason='MAX_TOKENS')..."` instead of
   the bare `"call failed: GradeLLMError"`. For every other cause
   (auth / rate-limit / parser / budget) the existing class-name
   shape is preserved verbatim so the audit corpus stays diff-clean
   for the 90% case — only the response-shape branch grows the
   diagnostic. Two new unit tests pin the new + preserved shapes.

2. **Floor bump (e2e overlay).** Raise `grade_max_output_tokens`
   overlay from 2048 to 4096 in both `test_e2e_gemini_smoke.py` and
   the `[gemini]` parametrization of `test_e2e_bigquery_smoke.py`.
   The 2048 figure was the in-isolation 5-pair smoke floor verified
   in #155 DEC-008; the first full-pipeline run found 5–6/108 pairs
   still degrade at 2048 (Gemini's per-pair `reasoning` length is
   high-variance enough that the in-isolation floor is not the
   full-fixture floor). 4096 is the new #158 fixture-scale floor.

Doc reframes in lockstep:
- `docs/grade-ops.md` + `docs/draft-ops.md` — Gemini row of the
  recommended-floors table bumped 2048 → 4096, with a
  "fixture-scale caveat" callout explaining the floor is necessary
  but not sufficient and operators should watch
  `aggregate_complete` on their own fixture.
- `plans/super/155-...md` DEC-008 — addendum reframing the 2048
  "verified safe" claim as scoped to the in-isolation probe; the
  DEC stays as historical record of the 2048 figure's provenance.

Existing `test_grade_artifacts_safety_blocked_response_degrades_pair`
updated to assert the new `startswith("call failed: GradeLLMError: ")`
+ `"finish_reason='SAFETY'" in reasoning` shape rather than the bare
class name — the contract pin moves with the broadened reasoning.

The acceptance gate from #158 (full-fixture e2e against Gemini
returning `aggregate_complete=True`) is only verified by re-running
`tests/cli/test_e2e_bigquery_smoke.py[gemini]` + `test_e2e_gemini_smoke.py`
under the live env-var stack (`SF_RUN_GEMINI=1 GOOGLE_API_KEY=…
SF_RUN_BQ=1 ANTHROPIC_API_KEY=… GOOGLE_CLOUD_PROJECT=… uv run pytest -m
"e2e and gemini" --no-cov`) — those are excluded from default CI.
Local 2589-test suite + ruff + pyright clean.

Beads: bd_1-scaffolding-we0 (diagnostic), bd_1-scaffolding-ila (floor).

Closes #158.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (4)
  • feature/.*
  • bug/.*
  • hotfix/.*
  • feat/.*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9b41e7a8-1724-45b0-b22c-9503d39b3614

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements the resolution to #158 by raising Gemini's max_output_tokens floor to 4096 and enhancing the grading engine to surface vendor-specific finish_reason details in degraded results, improving diagnostics for full-pipeline truncation failures.

Changes

Gemini max_output_tokens floor bump and degraded reasoning enhancement

Layer / File(s) Summary
Enhanced degraded reasoning for response-format errors
src/signalforge/grade/engine.py, tests/grade/test_engine.py
_format_degrade_reasoning(exc) helper preserves vendor finish_reason when GradeLLMError wraps LLMResponseFormatError, exposing provider-specific failure details (e.g. MAX_TOKENS, SAFETY) in degraded reasoning fields. Unit tests validate the distinction between response-format and non-response-format error branches.
Integration test for new reasoning format
tests/grade/test_gemini_neutrality.py
Gemini safety-blocked degradation assertions transition from exact equality to prefix match and inner detail containment, validating that degraded reasoning includes both error class and vendor-specific finish_reason value.
E2E test Gemini config and documentation
tests/cli/test_e2e_gemini_smoke.py, tests/cli/test_e2e_bigquery_smoke.py
Gemini grader overlays raise grade_max_output_tokens from 2048 to 4096 with updated module docstrings and comments explaining the full-fixture reasoning truncation behavior and expanded failure diagnostics.
Operations documentation and plan updates
docs/draft-ops.md, docs/grade-ops.md, plans/super/155-gemini-truncation-e2e-gap.md
Recommended Gemini max_output_tokens floor updated to 4096; documentation reframes prior 2048 claim as an in-isolation 5-pair finding and clarifies that degraded reasoning now surfaces vendor finish_reason for operator inspection without re-reading stderr.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #155: The per-provider max_output_tokens table introduced in #155 DEC-008 is reframed in this PR to reflect the full-pipeline e2e finding that 2048 was insufficient for the Austin fixture at scale; the new 4096 floor and fixture-scale caveat directly address the provenance gap identified in #158.

Poem

🐰 Four-thousand tokens now guide the way,
Where Gemini's verbose thoughts hold sway,
With finish-reasons surfaced bright,
Operators see the truncation's plight—
Full-fixture truth shines through the night! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the two main changes: surfacing Gemini finish_reason in degrade reasoning (diagnostic fix) and raising e2e floor to 4096 (mitigation).
Linked Issues check ✅ Passed The PR fully addresses the primary objectives from #158: implements diagnostic upgrade to surface finish_reason in degrade reasoning, raises e2e floor to 4096, updates docs/DEC-008 with clarified guidance, and adds test coverage.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #158: diagnostic reasoning enhancement, e2e floor bump, documentation updates, and corresponding test adjustments; no unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot AI 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.

Pull request overview

This PR addresses #158 by improving Gemini degrade diagnostics and raising the Gemini grader e2e token floor to reduce full-fixture truncation degrades.

Changes:

  • Adds _format_degrade_reasoning(...) so LLMResponseFormatError details, including vendor finish_reason, are preserved in degraded grading results.
  • Raises Gemini e2e grading overlays from 2048 to 4096 max output tokens.
  • Updates related tests, operational docs, and the historical #155 plan record.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/signalforge/grade/engine.py Adds the degrade-reasoning formatter and uses it when building degraded grading results.
tests/grade/test_engine.py Adds unit coverage for broadened response-format reasoning and preserved legacy shapes.
tests/grade/test_gemini_neutrality.py Updates the Gemini safety-blocked contract assertion for the new diagnostic reasoning.
tests/cli/test_e2e_gemini_smoke.py Raises Gemini grader e2e token overlay and updates comments/assertion messages.
tests/cli/test_e2e_bigquery_smoke.py Raises the Gemini parametrized grader overlay to 4096.
docs/grade-ops.md Updates grader floor guidance and Gemini diagnostic documentation.
docs/draft-ops.md Mirrors the Gemini floor update in drafter operations guidance.
plans/super/155-gemini-truncation-e2e-gap.md Adds historical context reframing the prior 2048 floor.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/grade-ops.md Outdated
Comment thread docs/draft-ops.md Outdated
Comment thread plans/super/155-gemini-truncation-e2e-gap.md Outdated
@wjduenow

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/cli/test_e2e_gemini_smoke.py (1)

69-69: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the stale token-cap reference in the invariant doc block.

Line 69 still says this assertion proves the 2048 cap, but this test now pins 4096. Keeping it stale can mislead triage.

Suggested edit
-   assertion that proves the 2048 cap fixes the truncation bug.**
+   assertion that proves the 4096 cap fixes the truncation bug.**
🤖 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 `@tests/cli/test_e2e_gemini_smoke.py` at line 69, Update the stale invariant
doc block that still references the "2048" token-cap to reflect the current test
pin of "4096": locate the docstring/comment in
tests/cli/test_e2e_gemini_smoke.py that mentions "2048" (the statement
describing the assertion that proves the cap) and change that literal and any
explanatory text to "4096" so the comment matches the actual assertion and
avoids misleading triage.
🤖 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.

Outside diff comments:
In `@tests/cli/test_e2e_gemini_smoke.py`:
- Line 69: Update the stale invariant doc block that still references the "2048"
token-cap to reflect the current test pin of "4096": locate the
docstring/comment in tests/cli/test_e2e_gemini_smoke.py that mentions "2048"
(the statement describing the assertion that proves the cap) and change that
literal and any explanatory text to "4096" so the comment matches the actual
assertion and avoids misleading triage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b7733a5d-5c31-4fad-96fe-5896dc267d3f

📥 Commits

Reviewing files that changed from the base of the PR and between 19724b2 and 9cc846c.

📒 Files selected for processing (8)
  • docs/draft-ops.md
  • docs/grade-ops.md
  • plans/super/155-gemini-truncation-e2e-gap.md
  • src/signalforge/grade/engine.py
  • tests/cli/test_e2e_bigquery_smoke.py
  • tests/cli/test_e2e_gemini_smoke.py
  • tests/grade/test_engine.py
  • tests/grade/test_gemini_neutrality.py

@wjduenow

Copy link
Copy Markdown
Owner Author

Live e2e verified ✅

Ran the #158 acceptance gate against real Gemini + BigQuery:

SF_RUN_GEMINI=1 GOOGLE_API_KEY=… SF_RUN_BQ=1 ANTHROPIC_API_KEY=… GOOGLE_CLOUD_PROJECT=… \
  uv run pytest -m "e2e and gemini" --no-cov
SF_RUN_GEMINI=1 GOOGLE_API_KEY=… SF_RUN_BQ=1 ANTHROPIC_API_KEY=… GOOGLE_CLOUD_PROJECT=… \
  uv run pytest "tests/cli/test_e2e_bigquery_smoke.py::test_e2e_signalforge_generate_against_austin_bikeshare[gemini]" \
    -m e2e --no-cov
Test Result Runtime
test_e2e_gemini_smoke.py::test_…with_gemini_grader ✅ PASSED 8m28s
test_e2e_bigquery_smoke.py::test_…[gemini] ✅ PASSED 8m54s

Both runs hit the load-bearing assertion grading_report.aggregate_complete is True — every (artifact × criterion) pair scored cleanly at max_output_tokens=4096, where the same fixture degraded 5–6/108 pairs at 2048 in #158's evidence. Bug closed.

Marker-selection follow-up worth noting

-m "e2e and gemini" only selects the dedicated test_e2e_gemini_smoke.pytest_e2e_bigquery_smoke.py carries only @pytest.mark.e2e, not @pytest.mark.gemini (the marker is on the file, not the parametrize). The BQ[gemini] variant has to be selected by node-ID (above) or pytest -m e2e -k gemini. Not a #158 blocker; flagging for the #157 e2e suite cost-docs follow-up to document.

Four review-feedback fixes, all real:

1. `docs/grade-ops.md` (Copilot l.599): the safety-filter handling
   prose still pointed at `GeminiProvider.extract_text_blocks` as
   the raise site, but post-#155 the provider-neutral
   `LLMProvider.is_clean_completion` gate in `call_llm` is the
   primary raise site — and it fires on partial-text MAX_TOKENS too,
   not just zero-text-parts. Reworded to name both paths and route
   them through the same conservative-degrade contract.

2. `docs/draft-ops.md` (Copilot l.595): the Gemini drafter floor
   row leaned on grader-side evidence (#158) while the section
   lead-in says floors are "from live drafting runs." Qualified the
   4096 figure as a *conservative mirror* of the grader floor,
   explicitly flagged as pending Gemini-drafter live validation
   when that lands.

3. `plans/super/155-...md` DEC-008 (Copilot l.56): the #158
   addendum carried a broken-sentence placeholder
   ("See `plans/super/155-...` was the in-isolation verification"
   — typo from a half-edit). Replaced with a concrete pointer to
   issue #158 + the durable lesson in memory
   `in-isolation-smoke-misses-pipeline-drift`.

4. `tests/cli/test_e2e_gemini_smoke.py:69` (CodeRabbit outside-diff):
   module-docstring invariant #6 still said "the 2048 cap fixes the
   truncation bug" — stale after the floor bump. Now says "the 4096
   cap (#158) fixes the full-fixture truncation bug."

No code changes (doc + docstring only). Full local validation
(ruff + pyright + 2589 pytest) clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@wjduenow

Copy link
Copy Markdown
Owner Author

PR Review Summary

All four review findings landed in commit 392ca31.

Fixed (4 items)

File Line Issue Reviewer
docs/grade-ops.md 599 Stale "When Gemini blocks…extract_text_blocks raises" wording — post-#155 the provider-neutral is_clean_completion gate is the primary raise site (and fires on partial-text MAX_TOKENS too). Reworded to name both paths, route through one degrade contract. Copilot
docs/draft-ops.md 595 Gemini drafter floor row leaned on grader-side evidence while section lead-in says floors are "from live drafting runs." Qualified as conservative mirror of the grader floor, flagged pending Gemini-drafter live validation. Copilot
plans/super/155-...md 56 DEC-008 addendum had a broken-sentence placeholder (See plans/super/155-... was the in-isolation verification). Replaced with concrete pointer to issue #158 + the durable lesson in memory in-isolation-smoke-misses-pipeline-drift. Copilot
tests/cli/test_e2e_gemini_smoke.py 69 Module-docstring invariant #6 still said "the 2048 cap fixes the truncation bug" — stale after the floor bump. Now reads "the 4096 cap (#158) fixes the full-fixture truncation bug." CodeRabbit (outside-diff)

False Positives (0 items)

No false positives — every comment identified a real bug.

Validation

  • uv run ruff check . && uv run ruff format --check . && uv run pyright: clean
  • uv run pytest --no-cov: 2589 passed
  • Live e2e from earlier comment: still valid (no code changes, only doc/docstring)

@wjduenow
wjduenow merged commit d345c91 into dev May 29, 2026
6 checks passed
@wjduenow
wjduenow deleted the fix/158-gemini-grader-degrade branch May 29, 2026 15:44
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.

3 participants