Skip to content

fix(pipeline): log retrieval failures so a broken retriever is distinguishable from an empty one - #262

Merged
himanshu231204 merged 5 commits into
OpenAgentHQ:mainfrom
Nitjsefnie-OSC:fix/256-surface-retrieval-failures
Jul 31, 2026
Merged

fix(pipeline): log retrieval failures so a broken retriever is distinguishable from an empty one#262
himanshu231204 merged 5 commits into
OpenAgentHQ:mainfrom
Nitjsefnie-OSC:fix/256-surface-retrieval-failures

Conversation

@Nitjsefnie

Copy link
Copy Markdown
Contributor

Description

Pipeline._retrieve wrapped retrieval in a bare except Exception and degraded to the dataset-context fallback with nothing logged, so a completely broken retriever — bad credentials, unreachable vector store, a bug in its own code — produced exactly the same observable result as a retriever that ran fine and found nothing.

The fallback is unchanged. The failure is now logged, naming the exception, so a degraded run can be told apart from a clean one. The inspect.signature fallback next to it gets the same treatment.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Test update
  • CI/CD update

Related Issues

Closes #256

How Has This Been Tested?

  • Unit tests pass (uv run pytest tests/unit -v --tb=short → 1013 passed, 4 skipped; the skips are pre-existing environment skips)
  • Whole suite with coverage, matching the CI Coverage job (uv run pytest --cov=openagent_eval --cov-fail-under=75 → 1067 passed, 81.12%)
  • Linter passes (uv run ruff check .) — see note below
  • Type checker passes (uv run mypy openagent_eval/) — see note below
  • Manual testing performed

A regression test drives a retriever whose retrieve() raises, through the real pipeline, over a dataset item carrying a context. It asserts the fallback still returns that context, that the exception is now visible in the log, and that result.errors stays empty — that last assertion is deliberate, and the reason is below. Reverting the production change makes it fail; the failure output is the RuntimeError no longer appearing.

Checklist

  • My code follows the project's coding standards
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Additional Notes

On the choice you were asked to make in #256. The issue offered you two options — a log line only, or a log line plus an entry in the run's errors — and said the errors entry "would be better still". I built that version first, and it is wrong. Recording a non-fatal degradation in errors corrupts the run's arithmetic:

engine.py:83 computes successful_evaluations = len(result.results) - len(result.errors). The existing defensive boundary in pipeline.py gets away with appending to errors because it also pushes a placeholder result, so the two cancel in that subtraction. A retrieval degradation has no such pairing — the item genuinely succeeds — so each one silently subtracts a success.

Measured on a one-item run where retrieval degraded and the item still evaluated: successful_evaluations reported 0 when the true value was 1, and failed_evaluations reported 1 when the true value was 0. It also flows into summary["errors"], into every renderer's failure section via reports/base.py, into the CLI's Errors: N, and — the one that decided it — into cicd/plugin.py, which flattens failed_evaluations into gate metrics. A user gating on failed_evaluations == 0 would start failing CI on a perfectly healthy run.

So this PR is the log-only version. It trades none of that away and still delivers what #256 asked for: a broken retriever is now distinguishable from an empty one. The test pins result.errors == [] so that a future change reintroducing the entry is caught rather than quietly mis-counting.

If you do want per-run structured visibility, I think it needs a channel no existing consumer reads as "failed item" — a warnings or degradations list on PipelineResult rather than an entry in errors. Happy to send that separately if you'd like it.

On the two unchecked test boxes. ruff check . exits 1 on pre-existing findings at main itself, so I could not honestly tick that. Instead I compared per-file, at main and on this branch, for the files this PR touches: pipeline.py has the same two pre-existing TC001 findings on both sides and the new test file is clean, so this diff adds nothing new. I have not reformatted or fixed those pre-existing findings, since that would bury a small behavioural change in unrelated churn. The mypy box is unticked for the same reason — it is not clean at main, and this diff does not change that.

Out of scope, noted rather than fixed: _generate has the same bare-except shape a few lines below. I left it alone to keep this diff to the issue, but it is the same class if you want it covered.

Generated by Claude Opus 5 (brief, review), Kimi K3 (implementation)

Nitjsefnie and others added 4 commits July 31, 2026 07:43
…HQ#256)

A retriever whose retrieve() raises currently degrades to the
dataset-context fallback with nothing logged and nothing in the run's
errors, making a broken retriever indistinguishable from a clean empty
retrieval. This test asserts the fallback still applies while the
failure is recorded in errors and logged. Fails on unfixed code.

Co-Authored-By: Kimi K3 <noreply@kimi.com>
Pipeline._retrieve swallowed every exception in a bare except and
silently degraded to the dataset-context fallback, making a broken
retriever indistinguishable from a clean empty retrieval. The fallback
behaviour is unchanged; the failure is now logged naming the exception
and recorded in the run's errors in the same shape used by the
per-item failure path. Also log when inspect.signature fails and the
retriever is assumed to lack ground_truth_contexts support.

Co-Authored-By: Kimi K3 <noreply@kimi.com>
Recording the non-fatal retrieval degradation in result.errors broke
engine.py's summary arithmetic: successful_evaluations is computed as
len(results) - len(errors), and the retrieval-failure entry has no
paired placeholder result, so a healthy run was mis-counted as
failed. That polluted the pipeline summary, every report renderer's
failure section, the CLI error count, and the cicd plugin gate
metrics, failing CI for users gating on failed_evaluations == 0.

Remove the result.errors append; the failure is still logged naming
the exception and the dataset-context fallback is unchanged. The
regression test now pins the log-only contract: it asserts
result.errors is empty and the run still counts as successful, which
catches any future change that reintroduces the errors entry.

Co-Authored-By: Kimi K3 <noreply@kimi.com>
Leftover from the log-only rework: the item and result parameters were
added only to support the result.errors append, which was removed when
the fix became log-only. Nothing in the body uses either parameter, so
revert _retrieve to its original (question, context, gt_contexts)
signature and the call site to the original three arguments. Logging,
fallback behaviour, and the docstring rationale are unchanged.

Co-Authored-By: Kimi K3 <noreply@kimi.com>
@himanshu231204

Copy link
Copy Markdown
Member

/oc review pr

@himanshu231204
himanshu231204 merged commit f7a9386 into OpenAgentHQ:main Jul 31, 2026
8 checks passed
@github-actions

Copy link
Copy Markdown

🎉 Congratulations @Nitjsefnie!

Your pull request has been successfully merged into main. 🚀

Thank you for contributing to OpenAgentHQ and helping improve the project.

We truly appreciate your contribution and hope to see you back with more amazing PRs!

Happy Open Sourcing! ❤️

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.

[BUG] pipeline: every retrieval failure is silently swallowed, so a broken retriever is indistinguishable from an empty result

2 participants