Files
library-desk/tests/test_enrichment_topk.py
jpmschweitzerandClaude Fable 5 c17c623936 perf: enrich only top-k results with one batched related-docs query
Phase 3 enrichment ran a sequential Neo4j query per fused result and the
final trim then discarded most of the output. Enrichment now covers only
results that can still reach the response - the Phase 4 rerank slice
(RERANK_SLICE_SIZE = 20, results beyond it are dropped when reranking)
or final_result_count, whichever applies - and resolves every page in a
single UNWIND $page_ids Cypher query via the new tenant-scoped
GraphService.get_related_documents_batch (per-page ordering by
shared_entities and per-page limit preserved via ORDER BY + collect()).

Unenriched tail results still carry related_dossiers: [] so the response
shape is unchanged. Query validated with EXPLAIN against the live Neo4j.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbFZyDvYksazX6nYQYZ67L
2026-07-14 14:23:34 +02:00

119 lines
3.7 KiB
Python

"""
Offline tests for Phase 3 enrichment running only on the top-k slice with
ONE batched related-documents lookup (perf: the old code ran a sequential
Neo4j query per fused result and the final trim discarded most of it).
"""
from unittest.mock import AsyncMock, MagicMock
import pytest
from src.services.hybrid_rag_service import HybridRAGService
TENANT = "llm_tester"
@pytest.fixture
def graph():
g = MagicMock()
g.get_related_documents_batch = AsyncMock(return_value={})
return g
@pytest.fixture
def service(graph):
settings = MagicMock()
settings.ollama_llm_model = "test-model"
return HybridRAGService(
vector_service=MagicMock(),
graph_service=graph,
searxng_client=MagicMock(),
ollama_client=MagicMock(),
content_extractor=MagicMock(),
settings=settings,
)
def _fused(n):
return [
{
"result": {"page_id": i + 1, "title": f"page {i + 1}"},
"rrf_score": 1.0 / (i + 1),
"sources": ["vector"],
}
for i in range(n)
]
@pytest.mark.unit
class TestTopKEnrichment:
async def test_only_top_k_enriched_with_single_batched_lookup(
self, service, graph
):
results = _fused(30)
enriched = await service._enrich_with_related_dossiers(
results, user=TENANT, top_k=5
)
# ONE batched lookup, only for the top-k page ids
graph.get_related_documents_batch.assert_awaited_once()
kwargs = graph.get_related_documents_batch.await_args.kwargs
assert kwargs["page_ids"] == [1, 2, 3, 4, 5]
assert kwargs["user"] == TENANT
# The single-page method must not be used anymore
graph.get_related_documents.assert_not_called()
# Every result still carries the key (tail is empty)
assert all("related_dossiers" in r for r in enriched)
assert all(r["related_dossiers"] == [] for r in enriched[5:])
async def test_related_docs_mapped_onto_results(self, service, graph):
graph.get_related_documents_batch = AsyncMock(return_value={
1: [{
"page_id": 9, "title": "rel", "path": "p/rel",
"tags": ["docker", "infra", "misc", "extra-tag-ignored"],
"shared_entities": 3,
}],
})
results = _fused(3)
enriched = await service._enrich_with_related_dossiers(
results, user=TENANT, top_k=2
)
dossiers = enriched[0]["related_dossiers"]
assert len(dossiers) == 3 # max 3 tags per related doc
assert dossiers[0] == {
"page_id": 9, "title": "rel", "path": "p/rel",
"tag": "docker", "shared_entities": 3,
}
assert enriched[1]["related_dossiers"] == []
assert enriched[2]["related_dossiers"] == []
async def test_results_without_page_id_are_skipped(self, service, graph):
results = [
{"result": {"url": "http://x", "title": "web"}, "sources": ["web"]},
{"result": {"page_id": 7, "title": "wiki"}, "sources": ["vector"]},
]
enriched = await service._enrich_with_related_dossiers(
results, user=TENANT, top_k=10
)
kwargs = graph.get_related_documents_batch.await_args.kwargs
assert kwargs["page_ids"] == [7]
assert enriched[0]["related_dossiers"] == []
async def test_batch_lookup_failure_degrades_gracefully(self, service, graph):
graph.get_related_documents_batch = AsyncMock(
side_effect=RuntimeError("neo4j down")
)
results = _fused(3)
enriched = await service._enrich_with_related_dossiers(
results, user=TENANT, top_k=3
)
assert all(r["related_dossiers"] == [] for r in enriched)