From 4f8f94a9fcc307111bc6baa1eaa407ee079892c0 Mon Sep 17 00:00:00 2001 From: Harsh Raj Singhania Date: Tue, 22 Sep 2026 14:06:22 +0530 Subject: [PATCH] fix: reject batch_size < 1 and handle empty-query benchmarks Fixes AgentPostmortem/Tenantq#22 and #23. --- src/tenantq/benchmark.py | 4 ++-- src/tenantq/embeddings.py | 2 ++ src/tenantq/ingest.py | 2 ++ tests/test_benchmark.py | 23 +++++++++++++++++++++++ tests/test_ingest.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 tests/test_ingest.py diff --git a/src/tenantq/benchmark.py b/src/tenantq/benchmark.py index e919335..37cb9c7 100644 --- a/src/tenantq/benchmark.py +++ b/src/tenantq/benchmark.py @@ -152,8 +152,8 @@ def run_benchmark( idx_recall.append(recall_at_k(retrieved, ref, 10)) total_time = sum(latencies) / 1000.0 - avg_r5 = sum(r5) / len(r5) - avg_r10 = sum(r10) / len(r10) + avg_r5 = (sum(r5) / len(r5)) if r5 else 0.0 + avg_r10 = (sum(r10) / len(r10)) if r10 else 0.0 RECALL_GAUGE.labels(mode=mode, k="5").set(avg_r5) RECALL_GAUGE.labels(mode=mode, k="10").set(avg_r10) result.modes.append( diff --git a/src/tenantq/embeddings.py b/src/tenantq/embeddings.py index d614f3a..6399c60 100644 --- a/src/tenantq/embeddings.py +++ b/src/tenantq/embeddings.py @@ -123,5 +123,7 @@ def build_embedder(kind: str, dense_model: str, sparse_model: str, dense_dim: in def batched(seq: Sequence, size: int) -> Iterable[Sequence]: + if size < 1: + raise ValueError("batch_size must be >= 1") for i in range(0, len(seq), size): yield seq[i : i + size] diff --git a/src/tenantq/ingest.py b/src/tenantq/ingest.py index c20ce8b..747ce5f 100644 --- a/src/tenantq/ingest.py +++ b/src/tenantq/ingest.py @@ -83,6 +83,8 @@ def ingest_documents( parallelism: int = 4, ) -> IngestReport: """Embed and upsert ``documents`` in parallel batches.""" + if batch_size < 1: + raise ValueError("batch_size must be >= 1") start = time.perf_counter() batches = list(batched(list(documents), batch_size)) diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index ff3d9db..08f254d 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -31,3 +31,26 @@ def test_benchmark_produces_numbers(ingested, settings, embedder, dataset): assert not math.isnan(modes["dense"].index_recall_at_10) # hybrid should be at least as good as the weaker single mode on recall@10 assert modes["hybrid"].recall_at_10 >= min(modes["dense"].recall_at_10, modes["sparse"].recall_at_10) + + +def test_run_benchmark_empty_queries_returns_empty_modes(settings): + from unittest.mock import MagicMock + + from tenantq.data import Dataset + + embedder = MagicMock() + embedder.embed_dense.return_value = [] + result = run_benchmark( + MagicMock(), + settings, + embedder, + Dataset(documents=[], queries=[]), + modes=("dense",), + ) + assert result.settings_summary["n_queries"] == 0 + assert len(result.modes) == 1 + mode = result.modes[0] + assert mode.n_queries == 0 + assert mode.recall_at_5 == 0.0 + assert mode.recall_at_10 == 0.0 + assert mode.qps == 0.0 diff --git a/tests/test_ingest.py b/tests/test_ingest.py new file mode 100644 index 0000000..1652512 --- /dev/null +++ b/tests/test_ingest.py @@ -0,0 +1,28 @@ +"""Ingestion input validation.""" + +from __future__ import annotations + +from unittest.mock import MagicMock + +import pytest + +from tenantq.embeddings import batched +from tenantq.ingest import ingest_documents + + +@pytest.mark.parametrize("batch_size", [0, -1]) +def test_ingest_documents_rejects_batch_size_below_one(settings, batch_size): + with pytest.raises(ValueError, match="batch_size must be >= 1"): + ingest_documents( + MagicMock(), + settings, + MagicMock(), + documents=[], + batch_size=batch_size, + ) + + +@pytest.mark.parametrize("size", [0, -3]) +def test_batched_rejects_non_positive_size(size): + with pytest.raises(ValueError, match="batch_size must be >= 1"): + list(batched([1, 2, 3], size))