test(eval): force-embedding flag + 10 vector-exercising golden cases - #249
Conversation
The golden retrieval set was 100% lexical fast-path (embedding_skipped_rate=1.0), so it could not measure whether a re-index changes vector/embedding retrieval quality. - forceEmbedding option on searchChunksWithTelemetry (SearchChunksArgs): bypasses every lexical text-fast-path so retrieval always exercises the embedding/vector stage. Diagnostic/eval-only; folded into the search cache key; never set on production paths. - eval-retrieval.ts: per-case `forceEmbedding` field + a global `--force-embedding` flag. - 10 `vector-*` cases (psychiatric monographs: PTSD, OCD, panic, anorexia, GAD, Tourette, postnatal, bipolar, ADHD, opioid) with forceEmbedding=true. Each is a clinical query that must be answered by vector retrieval of the right monograph — verified live at document_recall@5=1.0, content_recall@5=1.0, all via strategy=hybrid (embedding used). Rationale: forcing embedding is the correct instrument for re-index measurement — you want to measure the vector index directly, not have a lexical shortcut mask a regression. Wording alone can't reliably force the vector path (the fast-path is driven by emergent lexical-match strength), so the flag makes these probes deterministic. Live golden eval: 34/34 pass (24 existing + 10 new), no regression. verify:cheap green (980). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f03aeffe5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…te flag - Shared rag_response_cache: key retrievalPlanCacheQuery by forceEmbedding so a cached non-forced text-fast-path result can't satisfy a forced vector run (comment 3520042053). - Bypass the evidence-coverage-gate early return under forceEmbedding, so forced cases can't return before the embedding stage (comment 3520042055). - Isolate the vector index for forced cases: drop the pre-embedding lexical/memory/table candidates and run match_document_chunks_hybrid vector-only (empty query_text), so a broken vector index can't be masked by the lexical text-candidate path (comments 3520042061). - eval-quality.ts (eval:quality:release) now forwards testCase.forceEmbedding so the release gate honours the vector-* annotations instead of taking lexical shortcuts (comment 3520042059). - Prettier-format the fixture. Re-validated live under isolation: 34/34 golden pass, document_recall@5=1.0, content_recall@5=1.0, all 10 vector-* cases strategy=hybrid embedding:1. verify:cheap green (980). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3bdb703510
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const { data, error } = await supabase.rpc("match_document_chunks_hybrid", { | ||
| query_embedding: embedding as unknown as string, | ||
| query_text: textSearchQuery, | ||
| query_text: args.forceEmbedding ? "" : textSearchQuery, |
There was a problem hiding this comment.
Make all forced RPCs vector-only
Fresh evidence after the prior isolation fix: this only blanks query_text for match_document_chunks_hybrid, while the same forced path still calls searchEmbeddingFieldCandidates and searchIndexUnitCandidates with buildClinicalTextSearchQuery(args.query). Those hybrid RPCs select/boost lexical text hits, so a vector-* golden case can still pass through embedding-field or index-unit text branches even if the chunk vector index/regeneration is broken; propagate the forced mode to those helpers or assert vector-only provenance before treating these cases as vector probes.
Useful? React with 👍 / 👎.
| latencyMs: telemetry.embedding_latency_ms, | ||
| }); | ||
|
|
||
| if (args.forceEmbedding) { |
There was a problem hiding this comment.
Fail forced evals when embeddings fail
Because the lexical candidates are cleared only after embedTextWithTelemetry succeeds, the default RAG_PROVIDER_MODE=auto failure path above this block still returns the pre-embedding lexical textFastResults on quota/rate-limit/transient embedding errors. In that scenario a forced vector-* eval can pass without exercising any vector retrieval, which defeats the new guard; when forceEmbedding is set, the embedding failure should fail the search or at least not fall back to lexical results.
Useful? React with 👍 / 👎.
| topK: testCase.topK, | ||
| minSimilarity: 0.12, | ||
| skipCache: true, | ||
| forceEmbedding: testCase.forceEmbedding, |
There was a problem hiding this comment.
Gate release eval on forced misses
Forwarding forceEmbedding here makes eval:quality:release run the new vector probes, but that command still exits based only on the aggregate 0.8 retrieval thresholds in buildEvalQualityReport; with 34 cases, six forced vector misses can still leave hit/recall at 28/34 = 0.823 and pass release. If these cases are meant to protect re-index quality, forced-case failures (or any golden failed_cases) need to be blocking rather than only reported.
Useful? React with 👍 / 👎.
* issues: clarify #250 waves do not outrank A1 acuity Orphaned follow-up from PR #1624 auto-merge race. Wave packaging is the engineering track only; recommended-queue A1 rows stay acuity-first, and Wave 0 queue repair is marked landed with the #249–#251 capture. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * ledger: record prlanded for merged PR #1624 Squash b03b51d landed the 1:1 queue repair; note orphaned #250 clarify fix-forwarded on this branch. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * fix(issues): clarify #250 A1 track vs engineering waves Separate approval-gated A1 work from Wave 4, keep #22/#183 as A2 operator track, and restore append-only ledger order for the #1624 prlanded row. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Why
The golden retrieval set was 100% lexical fast-path (
embedding_skipped_rate=1.0) — every case was answered by a lexical shortcut, so the eval could not detect whether a re-index changes vector/embedding retrieval quality. This unblocks re-index measurability (the RAG-optimization initiative's next gate).What
forceEmbeddingoption onsearchChunksWithTelemetry(SearchChunksArgs): bypasses every lexical text-fast-path so retrieval always exercises the embedding/vector stage. Diagnostic/eval-only, folded into the search cache key, never set on production paths (3 fast-path guards gated behind it).eval-retrieval.ts: a per-caseforceEmbeddingfield (optional) + a global--force-embeddingflag.vector-*golden cases (psychiatric monographs: PTSD, OCD, panic, anorexia, GAD, Tourette, postnatal, bipolar, ADHD, opioid) withforceEmbedding: true. Each is a clinical query that must be answered by vector retrieval of the correct monograph.Why force embedding instead of wording
I first tried to craft queries that naturally fall through to the vector path. It's not reliably possible: the fast-path is driven by emergent lexical-match strength and substring title-rescue (
hasDirectTitleSupportfires on any query word that is a substring of any top-4 retrieved title), which can't be controlled from wording. Forcing embedding is also the correct instrument — for a re-index you want to measure the vector index directly, not let a lexical shortcut mask a regression.Verification
eval:retrieval:quality): 34/34 pass (24 existing + 10 new),document_recall@5=1.0,content_recall@5=1.0; all 10 new cases runstrategy=hybridwithembedding:1(embedding actually used).verify:cheap(runtime + lint + typecheck + vitest): green, 980 tests.🤖 Generated with Claude Code