qmd_py.bench¶
The marq bench harness: fixture loading, per-backend scoring
(precision@k/recall/MRR/F1), and the four search backends compared. See
Bench & doctor for the user-facing
reference and fixture format.
bench ¶
Search-quality benchmark harness: precision@k/recall/MRR/F1 across
backends (bm25/vector/hybrid/full) against a fixture file - port of the
TS reference's src/bench/{bench,score,types}.ts. Framework-agnostic
(no click/CLI dependency here, matching search/*.py's convention) -
progress is reported via an optional on_progress callback the CLI
layer supplies.
BenchmarkQuery
dataclass
¶
BenchmarkQuery(
id: str,
query: str,
type: str,
description: str,
expected_files: list[str],
expected_in_top_k: int,
)
One query in a benchmark fixture, with its expected results.
Attributes:
-
id(str) –Stable identifier, used to label result rows.
-
query(str) –The query text. May use the multi-line typed syntax.
-
type(str) –Free-form label for grouping (e.g. "lexical", "semantic").
-
description(str) –What this query is meant to probe.
-
expected_files(list[str]) –Paths that should be retrieved. Matched loosely by
paths_match(), so collection prefixes need not agree. -
expected_in_top_k(int) –The k used for precision@k and hits@k.
BenchmarkFixture
dataclass
¶
BenchmarkFixture(
description: str,
version: int,
queries: list[BenchmarkQuery],
collection: str | None = None,
)
A parsed benchmark fixture file.
Attributes:
-
description(str) –What this fixture set covers.
-
version(int) –Fixture format version.
-
queries(list[BenchmarkQuery]) –The queries to run.
-
collection(str | None) –Restrict every query to this collection, or None to search all of them. A per-file default, overridable at the call.
ScoreMetrics
dataclass
¶
ScoreMetrics(
precision_at_k: float,
recall: float,
recall_at_1: float,
recall_at_3: float,
recall_at_5: float,
mrr: float,
f1: float,
hits_at_k: int,
matched_files: list[str],
unmatched_expected_files: list[str],
)
Retrieval quality for one query against one backend.
Attributes:
-
precision_at_k(float) –Hits in the top k over
min(k, len(expected)), so a query expecting fewer files than k can still reach 1.0. -
recall(float) –Fraction of expected files found anywhere in the results.
-
recall_at_1(float) –Recall counting only the top result.
-
recall_at_3(float) –Recall counting the top three.
-
recall_at_5(float) –Recall counting the top five.
-
mrr(float) –Reciprocal rank of the first correct hit; 0.0 if none.
-
f1(float) –Harmonic mean of
precision_at_kandrecall. -
hits_at_k(int) –Expected files found within the top k.
-
matched_files(list[str]) –Expected files that were found, anywhere.
-
unmatched_expected_files(list[str]) –Expected files that were missed - the useful column when diagnosing a regression.
BackendResult
dataclass
¶
BackendResult(
precision_at_k: float,
recall: float,
recall_at_1: float,
recall_at_3: float,
recall_at_5: float,
mrr: float,
f1: float,
hits_at_k: int,
total_expected: int,
latency_ms: float,
top_files: list[str],
matched_files: list[str],
unmatched_expected_files: list[str],
)
One backend's ScoreMetrics for one query, plus what it returned.
Attributes:
-
precision_at_k(float) –See
ScoreMetrics. -
recall(float) –See
ScoreMetrics. -
recall_at_1(float) –See
ScoreMetrics. -
recall_at_3(float) –See
ScoreMetrics. -
recall_at_5(float) –See
ScoreMetrics. -
mrr(float) –See
ScoreMetrics. -
f1(float) –See
ScoreMetrics. -
hits_at_k(int) –See
ScoreMetrics. -
total_expected(int) –How many files the fixture expected, for reading the ratios above in context.
-
latency_ms(float) –Wall-clock time for this backend's run. Includes LLM round trips, so hybrid backends are not comparable with bm25 on latency alone.
-
top_files(list[str]) –Retrieved paths, best first, truncated for display.
-
matched_files(list[str]) –Expected files found.
-
unmatched_expected_files(list[str]) –Expected files missed.
QueryResult
dataclass
¶
Every backend's outcome for a single fixture query.
Attributes:
-
id(str) –The fixture query's identifier.
-
query(str) –Its query text.
-
type(str) –Its grouping label.
-
backends(dict[str, BackendResult]) –Backend name to result - the comparison the benchmark exists to produce.
BenchmarkResult
dataclass
¶
BenchmarkResult(
timestamp: str,
fixture: str,
results: list[QueryResult],
summary: dict[str, dict[str, float]],
)
A complete benchmark run.
Attributes:
-
timestamp(str) –ISO-8601 UTC time the run finished, for comparing runs.
-
fixture(str) –Path of the fixture used.
-
results(list[QueryResult]) –Per-query results, in fixture order.
-
summary(dict[str, dict[str, float]]) –Backend name to averaged metrics across all queries - the headline table.
load_fixture ¶
Load and validate a benchmark fixture from JSON.
Raises:
-
ValueError–The file has no
queriesarray. -
KeyError–A query is missing a required field.
-
JSONDecodeError–The file is not valid JSON.
Source code in src/qmd_py/bench.py
normalize_path ¶
Reduce a path to a comparable form for scoring.
marq://collection/docs/readme.md becomes docs/readme.md:
lowercased, stripped of the scheme, collection prefix and surrounding
slashes, so a fixture can name files without knowing which
collection they were indexed into.
Source code in src/qmd_py/bench.py
paths_match ¶
Whether a retrieved path satisfies an expected one.
Deliberately loose: equal after normalization, or either being a
suffix of the other. That lets a fixture write readme.md for a
document actually stored at docs/readme.md - convenient for
hand-written fixtures, but it can credit a near-miss, so treat scores
as comparative rather than absolute.
Source code in src/qmd_py/bench.py
score_results ¶
Score one ranked result list against its expected files.
Parameters:
-
result_files(list[str]) –Retrieved paths, best first.
-
expected_files(list[str]) –Paths that should have been retrieved.
-
top_k(int) –Cutoff for the @k metrics.
Returns:
-
ScoreMetrics–All metrics for this query. An empty
expected_filesyields -
ScoreMetrics–zeros rather than dividing by zero.
Source code in src/qmd_py/bench.py
run_benchmark
async
¶
run_benchmark(
session: AsyncSession,
user: CurrentUser,
llm_client: LlmClient,
settings: Settings,
fixture_path: str | Path,
*,
collection: str | None = None,
backend_names: list[str] | None = None,
on_progress: Callable[[str, str, float], None]
| None = None,
) -> BenchmarkResult
Run every fixture query against every selected backend.
Framework-agnostic on purpose - no click dependency - so the CLI layer supplies progress reporting rather than this owning output.
Parameters:
-
fixture_path(str | Path) –JSON fixture to load.
-
collection(str | None, default:None) –Restrict all queries to one collection, overriding the fixture's own setting.
-
backend_names(list[str] | None, default:None) –Which backends to run (
bm25,vector,hybrid,full). None runs all of them. -
on_progress(Callable[[str, str, float], None] | None, default:None) –Called as
(query_id, backend_name, latency_ms)after each backend finishes, for incremental output.
Returns:
-
BenchmarkResult–Per-query results plus the averaged summary.
Note
Runs real searches against real infrastructure, LLM calls included - a full fixture against the hybrid backends takes minutes, not seconds.
Source code in src/qmd_py/bench.py
bench_result_to_json ¶
format_bench_table ¶
Render per-query, per-backend metrics as a fixed-width table.
Source code in src/qmd_py/bench.py
format_bench_summary ¶
Render the averaged per-backend summary as a fixed-width table.