diff --git a/src/vouch/context.py b/src/vouch/context.py index 2eaa8707..fdfb55ba 100644 --- a/src/vouch/context.py +++ b/src/vouch/context.py @@ -455,9 +455,11 @@ def _retrieve( if raw: filtered = filter_hits(store, raw, viewer, limit=limit) # Parity with the hybrid path: an operator who opted into - # recency gets it regardless of which backend serves the query. + # recency or rerank gets it regardless of which backend serves + # the query — both are configured globally, not per backend. filtered = _maybe_recency(store, hits=filtered) filtered = _maybe_pages_first(store, hits=filtered) + filtered = _maybe_rerank(store, query=query, hits=filtered, limit=limit) return [(k, i, s, sc, "embedding") for k, i, s, sc in filtered] return [] @@ -468,6 +470,7 @@ def _retrieve( filtered = filter_hits(store, hits, viewer, limit=limit) filtered = _maybe_recency(store, hits=filtered) filtered = _maybe_pages_first(store, hits=filtered) + filtered = _maybe_rerank(store, query=query, hits=filtered, limit=limit) return [(k, i, s, sc, "fts5") for k, i, s, sc in filtered] except sqlite3.Error: pass diff --git a/tests/test_retrieval_backend.py b/tests/test_retrieval_backend.py index 344888fa..4e1d552b 100644 --- a/tests/test_retrieval_backend.py +++ b/tests/test_retrieval_backend.py @@ -241,6 +241,43 @@ def fake_rerank(*, query, hits, reranker, top_k): assert [item["id"] for item in pack["items"]] == ["c3", "c2", "c1"] +@pytest.mark.parametrize("backend", ["embedding", "fts5"]) +def test_context_rerank_applies_on_single_retriever_backends( + store: KBStore, monkeypatch: pytest.MonkeyPatch, backend: str +) -> None: + """retrieval.rerank is global config, so it must not be hybrid-only. + + An operator pinning retrieval.backend to embedding or fts5 and enabling + rerank previously got no reranking at all, silently — the stage only ran + on the auto/hybrid branch. + """ + from vouch.embeddings import rerank as rerank_mod + + src = store.put_source(b"e2") + store.put_claim(Claim(id="c2", text="OAuth refresh flow", evidence=[src.id])) + health.rebuild_index(store) + hits = [ + ("claim", "c1", "JWT token rotation", 0.90), + ("claim", "c2", "OAuth refresh flow", 0.80), + ] + monkeypatch.setattr(context.index_db, "search_semantic", lambda *a, **k: list(hits)) + monkeypatch.setattr(context.index_db, "search", lambda *a, **k: list(hits)) + monkeypatch.setattr(context, "_RERANKER_CACHE", None) + monkeypatch.setattr(rerank_mod, "default_reranker", lambda: object()) + monkeypatch.setattr( + rerank_mod, + "rerank", + lambda *, query, hits, reranker, top_k: list(reversed(hits))[:top_k], + ) + _set_backend(store, backend) + _set_rerank(store, enabled=True, top_k=2) + + pack = context.build_context_pack(store, query="auth", limit=3) + + assert _backends(pack) == {backend} + assert [item["id"] for item in pack["items"]] == ["c2", "c1"] + + def test_context_rerank_reuses_default_reranker( store: KBStore, monkeypatch: pytest.MonkeyPatch ) -> None: