G4: a search-quality regression suite that is proven to fail - #538
Merged
Conversation
🦋 Changeset detectedLatest commit: cc504c9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
brentrager
force-pushed
the
g4-retrieval-quality-evals
branch
from
August 23, 2026 01:53
7598958 to
41f5240
Compare
Closes the deterministic half of feature gap G4, and formalizes the judged half into a scored regression layer with a nightly job. Half 1 — deterministic, ungated, gates every PR. A frozen 20-document corpus is seeded through the real ingest→chunk→embed→store pipeline and a frozen 20-query labeled set runs through the real knowledge_search tool; recall@3, recall@5 and MRR are asserted against hand-written constants. There is no SMOOTH_AGENT_E2E gate, no #[cfg(feature)] and no #[ignore] — a gated suite that prints "ok. 0 passed" is a suite that did not run. The corpus exists in this shape because the first draft did not work: 13 unrelated documents scored a perfect recall@3 with every degradation still passing. It is now built around near-duplicate distractors, with half the queries targeting facts in a document's second or third paragraph, so the eval measures fact retrieval rather than topic matching. Four permanent degradation tests keep the gate's own sensitivity under test, and two production regressions were introduced by hand to verify it end to end: dropping Chunker's defaults to 60/0 took MRR from 0.975 to 0.792, and flipping one character in LexicalReranker's comparator took recall@3 from 0.975 to 0.300. Both reddened the suite; both were restored. Half 2 — every Scenario now declares a typed Competency (a required field, not a lookup table that drifts silently), and tests/regression.rs rolls all 15 scenarios into a per-competency Scorecard with its own floor, so a drop in grounding no longer averages away against a rise in tone. nightly-evals.yml sweeps a model matrix, appends each night's scorecard to a cached history rendered as a trend, and cannot go green on a skip: SMOOTH_AGENT_EVALS_REQUIRED turns a missing credential into a failure, and the gate is the cargo exit code — nothing parses a log line.
brentrager
force-pushed
the
g4-retrieval-quality-evals
branch
from
August 23, 2026 02:15
5f78f74 to
cc504c9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the deterministic half of feature gap G4 (answer- & search-quality regression suite) and formalizes the judged half into a scored regression layer with a nightly job.
docs/Planning/Feature Gaps.md§G4 is updated in this PR.Half 1 — deterministic retrieval quality (gates every PR)
rust/evals/tests/retrieval_quality.rsseeds a frozen 20-document corpus through the real ingest→chunk→embed→store pipeline (MockConnector→ingest()→Chunker→DeterministicEmbedder→InMemoryKnowledge), runs a frozen 20-query labeled set through the realKnowledgeSearchTool, and asserts recall@3 / recall@5 / MRR. No LLM, no key, no network, no Docker, no clock.It is deliberately ungated — no
SMOOTH_AGENT_E2E, no#[cfg(feature)], no#[ignore]. A gated suite that printsok. 0 passedis a suite that did not run.Thresholds are hand-written constants, never computed from the run. The suite has zero run-to-run variance (
eval_is_deterministic_across_runsasserts it), so the headroom is a budget for benign ranking churn, not noise insurance.The corpus is shaped by a failure
The first draft — 13 unrelated documents — scored a perfect recall@3 with every degradation still passing. It detected nothing. It is now built around near-duplicate distractors (
exchanges/cancellationsvsreturns,atlas-r5vsatlas-r7,diagnosticsvserror-codes), and half the queries target a fact in a document's second or third paragraph, so it measures fact retrieval rather than topic matching.Proof it can fail
Four permanent degradation tests keep the gate's own sensitivity under test:
LexicalRerankerAnd two production-code regressions were introduced by hand, confirmed red, then restored:
Chunkerdefaults 500/64 → 60/0LexicalRerankercomparator flipped (one char)Two honest findings recorded in the docs: an earlier "truncate every paragraph to 40 chars" degradation improved the numbers (the in-memory scorer divides by chunk length, so shorter chunks rank higher) — which is why a degradation has to be measured rather than assumed. And the
LexicalRerankertrades coverage for ordering (MRR up, recall@5 down), so the suite asserts MRR and recall@3 do not regress and deliberately does not pin recall@5.Not covered:
InMemoryKnowledgeranks lexically, so an embedder swap is not scored. Scoring dense retrieval means running this same corpus against the pgvector adapter under testcontainers; the corpus, labels, and metrics are backend-agnostic and move over unchanged.Half 2 — judged regression layer + nightly
Every
Scenarionow declares a typedCompetency— a required field, not a name→competency lookup table that would drift the moment someone adds a scenario.tests/regression.rsruns all 15 scenarios from both suites and rolls them into a per-competencyScorecardwith its own floor, so a drop in grounding no longer averages away against a rise in tone:anti_hallucination,safetygrounding,tool_use,tonemulti_turn_reasoning.github/workflows/nightly-evals.ymlsweeps a matrix of agent models (newSMOOTH_AGENT_EVAL_MODEL), appends each night's scorecard to a cachedeval-history.jsonlrendered as a trend table in the job summary, and refuses two failure modes:SMOOTH_AGENT_EVALS_REQUIRED=1makes the suite fail rather than skip without credentials, and a preflight step fails first with an actionable message.^-anchors a test log; the gate is thecargo testexit code.CARGO_TERM_COLOR: neveris set regardless.Verification
cargo test --workspace— all green (no failures across the workspace).cargo fmt --all -- --check— clean.cargo clippy --workspace --all-targets -- -D warnings— clean.every_scenario_declares_a_competency_with_a_floor) does run.