Context
PR #1143 (ironhalik) added the optional cross-encoder rerank stage: RerankProvider
protocol, FastEmbed (local ONNX) and LiteLLM (hosted) providers, factory with process-wide
singleton cache, opt-in config under reranker_*, fixed-prefix + demoted-tail pagination
stability, and fail-fast 503/502 error mapping. Unit coverage from the PR is strong
(pipeline math, provider contracts, factory, router mapping, redaction, config validation —
all in the default CI suites).
An audit of the merged feature found the remaining gaps concentrated in four areas, plus a
few small code fixes. This issue tracks closing them.
Phase 1 — CI-visible coverage gaps (one PR, no external deps)
Small fixes (ride the Phase 1 PR as separate commits)
Phase 2 — real-model and live-provider verification
Phase 3 — quality regression harness
Phase 4 — operational drills (manual, test-live style)
Notes for implementers
- Reranking applies to vector and hybrid modes only; FTS/title/permalink keep their
ranking (search_repository_base.py:1646). Scores replace fused scores for the fixed
20-candidate prefix; the tail is demoted rank-only (rerank_provider.py:31-42) — the
zero-floor tie-break is order-dependent, so downstream score-only re-sorts would break
pagination stability.
- Failure semantics are deliberately fail-fast (no silent un-reranked fallback): a
degraded page could duplicate/omit results relative to an already-served reranked page.
Tests must not "fix" this.
Refs: #1143 (feature), #950 (motivation, closed), #618 #666 #951 #1155 (open follow-ons).
Context
PR #1143 (ironhalik) added the optional cross-encoder rerank stage:
RerankProviderprotocol, FastEmbed (local ONNX) and LiteLLM (hosted) providers, factory with process-wide
singleton cache, opt-in config under
reranker_*, fixed-prefix + demoted-tail paginationstability, and fail-fast 503/502 error mapping. Unit coverage from the PR is strong
(pipeline math, provider contracts, factory, router mapping, redaction, config validation —
all in the default CI suites).
An audit of the merged feature found the remaining gaps concentrated in four areas, plus a
few small code fixes. This issue tracks closing them.
Phase 1 — CI-visible coverage gaps (one PR, no external deps)
tests/repository/test_rerank_pipeline.py(:527-841) skip on Postgres("sqlite-vec repository behavior is local SQLite-only"), so hybrid/vector rerank
ordering on pgvector is asserted only by the single semantic-marked benchmark test.
Port them to a Postgres variant (recording-reranker fixture;
test-int/semantic/+semanticmarker if they need real vector tables, main suite otherwise).never invoked, ranking identical to baseline. (Only the inverse — no provider — is
tested today:
test_search_without_reranker_keeps_baseline.)create_search_repositoryinjects the singleton provider into bothbackends (
search_repository.py:175-207); repository__init__self-resolution(
sqlite_search_repository.py:84-86,postgres_search_repository.py:100-102) respectssemantic_search_enabled.reranker_max_document_charsset via config truncates thedocument text the provider receives (currently only the pure helper is tested).
chatgpt_tools.py:205's broadexcept Exceptioncollapses rerank 503s into a generic "Internal search error". Distinguish retryable
unavailability in the returned payload, and pin it with a test.
Small fixes (ride the Phase 1 PR as separate commits)
fastembed_rerank_provider.py:71(
Xenova/ms-marco-MiniLM-L-6-v2; the real default isjinaai/jina-reranker-v1-tiny-enfrom
config_models.py:61) — dead today but a trap for direct construction.reranker_timeoutconfig key: the LiteLLM provider hardcodestimeout=30.0(
litellm_rerank_provider.py:34) and the factory never passes it. Add the config field(validated
gt=0, default 30), thread it through the factory, document it indocs/semantic-search.md.Phase 2 — real-model and live-provider verification
test-semanticCI job with modelcaching): load actual
jinaai/jina-reranker-v1-tiny-en, tiny fixed corpus, assert therelevant doc ranks first and scores are
[0,1]-calibrated.test-int/semantic/litellm_live_harness.py(embeddings-only today) withrerank: Cohere
rerank-v3.5+ one alternate (Jina or Voyage), env-keyed, run manuallywith real keys — contract shape, ordering sanity, auth-failure classification
(permanent vs transient). Mirrors the feat(core): add LiteLLM embedding provider #809 embedding live-smoke pattern.
Phase 3 — quality regression harness
~20 queries with expected top notes; measure hit@k and MRR reranker-off vs reranker-on
(FastEmbed). Build on the existing
test-int/semantic/metrics.py/corpus.py/report.pymachinery. Assert no regression; report the delta. This is the durable guardthe LoCoMo motivation (Add a rerank stage to search: ~half of LoCoMo benchmark misses are ranking failures, and there is ~20x latency headroom #950) asked for, and the baseline for Add reranking step to search pipeline (local cross-encoder) #618/Retrieval pipeline improvements: reranking, length normalization, noise filtering, adaptive recall #666/Entity-aware ranking boost: cross-conversation entity confusion dominates hard retrieval misses in LoCoMo benchmark #951 follow-ons.
reranker — validates the "~20x headroom" claim and catches future model swaps that blow
the budget.
Phase 4 — operational drills (manual, test-live style)
semantic_search_enabled→ clear error atbm config settime; model typo rejected at set-time; secrets absent fromconfig list/ diagnostics output.aborts rather than returning a partial page; recovery without restart.
no duplicates or omissions.
Notes for implementers
ranking (
search_repository_base.py:1646). Scores replace fused scores for the fixed20-candidate prefix; the tail is demoted rank-only (
rerank_provider.py:31-42) — thezero-floor tie-break is order-dependent, so downstream score-only re-sorts would break
pagination stability.
degraded page could duplicate/omit results relative to an already-served reranked page.
Tests must not "fix" this.
Refs: #1143 (feature), #950 (motivation, closed), #618 #666 #951 #1155 (open follow-ons).