Skip to content

chore(eval): FlashRank rerank quality-lift probe (#46) - #64

Merged
jphein merged 1 commit into
mainfrom
chore/46-rerank-eval-probe
May 27, 2026
Merged

chore(eval): FlashRank rerank quality-lift probe (#46)#64
jphein merged 1 commit into
mainfrom
chore/46-rerank-eval-probe

Conversation

@jphein

@jphein jphein commented May 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Before/after eval probe quantifying the retrieval-quality lift of the live FlashRank rerank pass (rerank.py, ms-marco-TinyBERT-L-2-v2), per issue #46. Adds a re-runnable harness, a hand-verified labeled query set, frozen candidate pools, unit tests, and a findings report with a recommendation.

Method: for each labeled query, pull one candidate pool from the production palace (read-only GET /search) and score two orderings of the identical pool — baseline (vector distance asc, = rerank disabled) vs reranked (FlashRank score desc, = today). Holding retrieval constant isolates the reranker's contribution. Relevance is matched by hand-verified structural predicates (source file + content substring), not frozen drawer IDs, so the set survives palace re-mining.

Results (live, 11/12 usable, pool=20)

metric baseline reranked delta
R@5 1.000 0.909 −0.091
R@10 1.000 1.000 0.000
MRR 0.761 0.877 +0.116 (+15.3%)

Rerank latency: mean 47 ms (min 20, max 157), CPU-only host. 3 queries improved (incl. a rank 5→1 rescue), 7 unchanged (retrieval already optimal — reranker correctly left them alone), 1 regression (rank 3→7). The regression is a near-tie shuffle: 7 genuinely-relevant passages scored within 0.002 of each other (TinyBERT score compression at ~0.999).

Cross-validated: replaying the frozen pools in-process via rerank.py (--mode candidates) reproduces the metrics exactly.

Recommendation: KEEP nano now; follow up with an A/B vs MiniLM L-12

  • Lift is real (MRR +15%) and latency is fine (47 ms) → rules out REVERT.
  • The lone R@5 regression + pervasive flat scores are the "lift is real but more headroom wanted" → ESCALATE candidate, not yet a decision.
  • Keep nano live; A/B ms-marco-MiniLM-L-12-v2 against nano on the same frozen pools (zero retrieval cost, no daemon restart) and decide from that head-to-head. Note: the issue mentioned L-6, but this FlashRank build only ships L-12.

Full methodology, limitations, and per-query analysis: docs/evals/rerank-eval-2026-05-27.md.

What's included

  • scripts/evals/rerank_eval.py — harness (live + in-process candidates modes)
  • scripts/evals/collect_candidates.py — freeze candidate pools for offline A/B
  • scripts/evals/rerank_eval_queries.json — 12 labeled queries
  • tests/test_rerank_eval.py — 9 cases
  • docs/evals/ — report, raw metrics JSON, frozen candidate pools

Test plan

  • venv/bin/python -m pytest tests/ -q → 252 passed, 1 skipped
  • Live eval run completes clean (0 errors) against deployed daemon
  • --mode candidates replay reproduces live metrics exactly
  • Eval is strictly read-only against the palace (no writes)
  • (Follow-up) A/B MiniLM L-12 vs nano on the frozen pools

🤖 Generated with Claude Code

Before/after eval harness comparing baseline (vector-distance) vs
FlashRank-reranked ordering on an identical candidate pool, plus a
findings report with the metrics and a recommendation.

Result on 11 usable queries (live, pool=20): MRR +15.3% (0.761 -> 0.877),
R@10 unchanged, one R@5 regression (3 -> 7) from cross-encoder score
compression. Rerank latency ~47 ms mean. Verdict: KEEP nano now,
follow up with an A/B against ms-marco-MiniLM-L-12-v2 on the frozen
candidate pools (L-6 from the issue is not shipped in this FlashRank build).

- scripts/evals/rerank_eval.py: harness, live + in-process candidates modes
- scripts/evals/collect_candidates.py: freeze candidate pools for offline A/B
- scripts/evals/rerank_eval_queries.json: 12 hand-verified labeled queries
- tests/test_rerank_eval.py: 9 cases (full suite 252 passed / 1 skipped)
- docs/evals/: report + raw metrics + frozen candidate pools

Read-only against the production palace; no writes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 27, 2026 18:44
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a comprehensive evaluation framework to measure the effectiveness of the FlashRank reranking pass in the palace-daemon. By comparing baseline retrieval orderings against reranked outputs on frozen candidate pools, the changes provide a clear, reproducible signal on the reranker's impact. The evaluation confirms a positive lift in retrieval quality and provides a roadmap for future model optimizations.

Highlights

  • Evaluation Harness: Added a re-runnable evaluation harness (rerank_eval.py) to quantify the retrieval-quality lift of the FlashRank rerank pass by comparing baseline and reranked orderings.
  • Data Collection: Implemented collect_candidates.py to freeze candidate pools from the production daemon, enabling offline A/B testing and decoupling retrieval from reranking.
  • Ground Truth: Created a labeled query set (rerank_eval_queries.json) using structural relevance predicates to ensure the evaluation remains valid despite repository churn.
  • Findings & Recommendations: Documented a +15.3% MRR lift with acceptable latency, recommending the retention of the current 'nano' model while scheduling a follow-up A/B test against the MiniLM L-12 model.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a comprehensive evaluation suite for the FlashRank reranking feature, including a markdown report, a frozen candidate JSON, candidate collection and evaluation scripts, a labeled query set, and corresponding unit tests. The reviewer's feedback identifies a critical issue in both collect_candidates.py and rerank_eval.py where r.raise_for_status() can raise an uncaught HTTPError (such as transient 502/503/504 errors), bypassing the retry mechanism and crashing the scripts. The reviewer suggests catching requests.RequestException to handle all request-related exceptions gracefully.

out[q["id"]] = hits
print(f"ok {q['id']:<28} n={len(hits)}")
break
except (requests.Timeout, requests.ConnectionError) as e:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The except block currently only catches requests.Timeout and requests.ConnectionError. However, r.raise_for_status() on line 81 can raise requests.exceptions.HTTPError (e.g., on transient 502/503/504 errors). Because HTTPError is not caught here, any HTTP error will immediately crash the script instead of being retried or logged in the errors dictionary.

Catching requests.RequestException ensures that all request-related errors, including HTTP errors, are gracefully caught, retried, and logged.

Suggested change
except (requests.Timeout, requests.ConnectionError) as e:
except requests.RequestException as e:

)
r.raise_for_status()
return r.json()
except (requests.Timeout, requests.ConnectionError) as e:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Similar to the candidate collection script, r.raise_for_status() on line 164 can raise requests.exceptions.HTTPError on transient server errors (e.g., 502/503/504). Since only Timeout and ConnectionError are caught, any HTTP error will bypass the retry mechanism and immediately propagate, failing the query evaluation without retrying.

Catching requests.RequestException allows the harness to retry on transient HTTP errors as well.

Suggested change
except (requests.Timeout, requests.ConnectionError) as e:
except requests.RequestException as e:

@jphein
jphein merged commit 1d88c36 into main May 27, 2026
1 check failed
@jphein
jphein deleted the chore/46-rerank-eval-probe branch May 27, 2026 18:46
@jphein
jphein removed the request for review from Copilot May 27, 2026 19:16
jphein added a commit that referenced this pull request May 27, 2026
) (#69)

Addresses Gemini review on #64 + live eval.

Both eval scripts only caught Timeout/ConnectionError around
raise_for_status(), so a transient 502/503/504 from the contended daemon
crashed instead of retrying. Now retry on 5xx (transient) and let 4xx
(bad auth / bad request) propagate immediately rather than burning
retries masking a permanent error.

Live re-run against production (env URL fixed to http://familiar:8085)
confirms the round-1 finding and is stronger: MRR 0.748 -> 0.921
(+23.1%), R@10 unchanged, R@5 a wash, one persistent regression
(daemon-deploy-arch, rank 3->8) from cross-encoder score compression.
Verdict stands: KEEP nano, follow up with an A/B vs ms-marco-MiniLM-L-12-v2
on the frozen pools.

- scripts/evals/rerank_eval.py, collect_candidates.py: _is_retryable_http
  helper + 5xx retry / 4xx propagate
- tests/test_rerank_eval.py: 5 new cases (5xx retryable, 4xx not, fetch_live
  retries 503 then succeeds, 4xx fails fast) — full suite 307 passed / 1 skipped
- docs/evals/rerank-eval-2026-05-27.md: two-run results table + reconciliation
- docs/evals/rerank-eval-live-2026-05-27.json: raw live run #2 output

Read-only against the palace.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant