multi-document-rag-with-RAGAS / tests /test_retrieval.py
dharandhamo's picture
Add Multi-Document RAG project with Nvidia integration
f813ba1
Raw
History Blame Contribute Delete
5.85 kB
"""Tests for retrieval: embedder, vector store, query router, and self-corrector."""
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from app.core.retrieval.query_router import _heuristic_classify
# ── Embedder Tests ────────────────────────────────────────────────────
@pytest.mark.asyncio
@patch("app.core.retrieval.embedder._embed_content_sync")
async def test_embedder_returns_correct_dimension(mock_embed: MagicMock) -> None:
"""Test that embedder returns vectors with dimension 3072."""
# Mock Gemini embedding response
mock_vector = [0.1] * 3072
mock_embed.return_value = [mock_vector]
from app.core.retrieval.embedder import embed_query
result = await embed_query("test query")
assert len(result) == 3072
mock_embed.assert_called_once()
@pytest.mark.asyncio
@patch("app.core.retrieval.embedder._embed_content_sync")
async def test_embedder_batches_chunks(mock_embed: MagicMock) -> None:
"""Test that embedder batches chunks in groups of 20."""
mock_vector = [0.1] * 3072
# Return correct number of embeddings for each batch
mock_embed.side_effect = lambda texts, task: [mock_vector] * len(texts)
from app.core.retrieval.embedder import embed_chunks
texts = [f"chunk {i}" for i in range(45)]
results = await embed_chunks(texts)
assert len(results) == 45
assert mock_embed.call_count == 3 # 20 + 20 + 5 = 3 batches
@pytest.mark.asyncio
@patch("app.core.retrieval.embedder._embed_content_sync")
async def test_embed_query_uses_retrieval_query_task(mock_embed: MagicMock) -> None:
"""Test that embed_query uses 'retrieval_query' task type."""
mock_embed.return_value = [[0.1] * 3072]
from app.core.retrieval.embedder import embed_query
await embed_query("test query")
mock_embed.assert_called_with(["test query"], "retrieval_query")
# ── Vector Store Tests ────────────────────────────────────────────────
@pytest.mark.asyncio
@patch("app.core.retrieval.vector_store.QdrantClient")
async def test_vector_store_upsert_and_search(mock_qdrant_cls: MagicMock) -> None:
"""Test that vector store can upsert chunks and search."""
mock_client = MagicMock()
mock_qdrant_cls.return_value = mock_client
# Mock collections check
mock_collection = MagicMock()
mock_collection.name = "academic_papers"
mock_client.get_collections.return_value.collections = [mock_collection]
from app.core.retrieval.vector_store import VectorStore
# Reset singleton for testing
import app.core.retrieval.vector_store as vs_module
vs_module._vector_store = None
store = VectorStore()
# Test upsert
chunks = ["chunk 1", "chunk 2"]
embeddings = [[0.1] * 3072, [0.2] * 3072]
metadata = {
"doc_id": "test-123",
"filename": "test.pdf",
"doc_type": "research_paper",
"source": "test",
"pub_year": 2023,
"ingested_at": "2024-01-01T00:00:00Z",
}
count = await store.upsert_chunks(chunks, embeddings, metadata)
assert count == 2
mock_client.upsert.assert_called_once()
# ── Query Router Tests ────────────────────────────────────────────────
def test_query_router_classifies_compare_query() -> None:
"""Test that heuristic classifier detects comparison queries."""
route = _heuristic_classify("Compare the methodologies used in these two papers")
assert route.requires_comparison is True
assert route.mode == "reasoning"
def test_query_router_classifies_standard_query() -> None:
"""Test that heuristic classifier identifies standard queries."""
route = _heuristic_classify("What dataset was used in this study?")
assert route.requires_comparison is False
assert route.mode == "standard"
def test_query_router_classifies_reasoning_query() -> None:
"""Test that heuristic classifier detects reasoning queries."""
route = _heuristic_classify("Why does the author claim that transformers are better?")
assert route.mode == "reasoning"
def test_query_router_detects_contrast_keyword() -> None:
"""Test that heuristic classifier detects 'contrast' keyword."""
route = _heuristic_classify("Contrast the results from experiment A and B")
assert route.requires_comparison is True
def test_query_router_detects_versus_keyword() -> None:
"""Test that heuristic classifier detects 'versus' keyword."""
route = _heuristic_classify("Method X versus Method Y performance")
assert route.requires_comparison is True
# ── Self-Corrector Tests ─────────────────────────────────────────────
@pytest.mark.asyncio
@patch("app.core.retrieval.self_corrector.get_llm_client")
async def test_self_corrector_rewrites_bad_query(mock_get_llm: MagicMock) -> None:
"""Test that self-corrector rewrites query when relevance is low."""
mock_llm = MagicMock()
mock_get_llm.return_value = mock_llm
# Mock generate for rewrite
mock_llm.generate = AsyncMock(
return_value="What specific neural network architecture is proposed for NLP tasks?"
)
mock_llm.primary_model = "llama-3.3-70b-versatile"
from app.core.retrieval.self_corrector import rewrite_query
rewritten = await rewrite_query(
"tell me about the thing",
"Query is too vague",
)
assert len(rewritten) > 0
assert rewritten != "tell me about the thing"
mock_llm.generate.assert_called_once()