fastrag.benchmark#
Benchmarking: load a dataset, score a pipeline against it, compare runs.
- class fastrag.benchmark.Benchmark(*, dataset, metrics, max_concurrent=4)[source]#
Bases:
objectRuns every sample in a dataset through a pipeline and scores it.
from fastrag.benchmark import Benchmark, QADataset from fastrag.benchmark.metrics import Latency, NDCG, Recall benchmark = Benchmark( dataset=QADataset.load("eval.json"), metrics=[Recall(k=5), NDCG(k=5), Latency()], max_concurrent=4, ) result = await benchmark.run(engine, name="recursive-500")
- Parameters:
dataset (QADataset) – The samples to run.
metrics (Sequence[Metric]) – What to score each sample on. Every name must be unique — a duplicate would silently overwrite the first in
BenchmarkResult.summary— and at least one metric’sneeds_relevant_idsmust be satisfiable by the dataset.max_concurrent (int) – How many samples run at once.
- async run(target, *, name)[source]#
Run every sample against
target, scoring it with every metric.Samples run under a concurrency-limited semaphore, gathered back in dataset order. A sample whose pipeline call raises is recorded as
"errored"with every scoreNone— no metric runs for it — so a failure is never scored as a zero; seeStatusCounts.- Parameters:
target (QueryEngine | Retriever) – A
QueryEnginefor metrics that need a generated answer, or a bareRetrieverfor retrieval-only metrics — cheaper, and the signal to reach for first (seefastrag.benchmark.metrics.retrieval).name (str) – This run’s identity, carried on the returned result.
- Raises:
ConfigurationError –
targetis a bareRetrieverand a metric needs an answer. Raised before any sample runs, since it is knowable without running one.- Return type:
- class fastrag.benchmark.BenchmarkResult(name, created_at, status, summary, samples, duration_s, higher_is_better=<factory>)[source]#
Bases:
objectThe outcome of one
run().nameis the only identity a result carries — deliberately no serialized pipeline configuration, since the benchmark script is already the readable, version-controlled record of what was run. Keep the script and itsresults/directory together, and name results after their configuration.- Parameters:
name (str)
created_at (datetime)
status (StatusCounts)
summary (dict[str, DistributionSummary])
samples (list[SampleResult])
duration_s (float)
- status: StatusCounts#
- summary: dict[str, DistributionSummary]#
- samples: list[SampleResult]#
- higher_is_better: dict[str, bool]#
Per metric name, whether a higher score is better. Read by
best(), which is the only place a result file’s meaning — not just its numbers — is needed after theMetricobjects that ran are gone.
- classmethod load(path)[source]#
Read a result written by
save().- Raises:
ConfigurationError –
pathis not a result file this version of the package can read.- Parameters:
- Return type:
- class fastrag.benchmark.ColumnMapping(question=None, expected_answers=None, relevant_ids=None)[source]#
Bases:
objectOverride auto-detection for one or more of
QADataset’s columns.Each field left
Noneis auto-detected by scanning its alias list against the loaded records’ keys; passing a value overrides only that field. Auto-detection raisesConfigurationErrorwhen two alias columns are both present, rather than silently picking one.relevant_ids’s alias list includescontextsfor compatibility with RAGAS-style eval sets — but a RAGAScontextscolumn holds retrieved text, not ids. Such a column matches nothing in Fastrag’s chunk ids and scores every retrieval metric0.0: passColumnMapping(relevant_ids=...)pointing at an actual id column, or leaverelevant_idsempty and skip the retrieval metrics for that dataset.
- class fastrag.benchmark.Comparison(results)[source]#
Bases:
objectAny number of
BenchmarkResult, compared.from fastrag.benchmark import Comparison comparison = Comparison.load("results/*.json") # or Comparison.of([r1, r2, r3]) print(comparison.to_markdown()) print(comparison.best("ndcg@5"))
- Parameters:
results (list[BenchmarkResult])
- results: list[BenchmarkResult]#
- classmethod of(results)[source]#
Build a comparison from results already in memory.
- Parameters:
results (Iterable[BenchmarkResult])
- Return type:
- classmethod load(pattern)[source]#
Load every result file matching a glob
pattern, sorted by path.- Raises:
ConfigurationError –
patternmatched no files — an empty comparison is the silent-wrong-answer shape, so this raises rather than returning one.- Parameters:
- Return type:
- to_markdown(stat='mean')[source]#
Render one row per run, one column per metric,
statof each.statis anyDistributionSummaryfield ("mean","p95", …); an unknown one raisesConfigurationErrorrather than printing zeros. A run with no such metric prints—. Whenstatis not"mean"every header carries it, e.g.latency_ms p95.
- best(metric, stat='mean')[source]#
The result whose
metricis best, per that metric’s own direction.- Raises:
ConfigurationError – no result carries
metric.- Parameters:
- Return type:
- save(path)[source]#
Write
to_markdown()’s default (mean) table topath.
- class fastrag.benchmark.EvalSample(question, expected_answers=<factory>, relevant_ids=<factory>, metadata=<factory>)[source]#
Bases:
objectOne question, with what a metric needs to score an answer to it.
relevant_idsis what enables the retrieval metrics (fastrag.benchmark.metrics.retrieval) — a sample with none is still usable for generation and operational metrics, just not those.- Parameters:
- class fastrag.benchmark.QADataset(samples)[source]#
Bases:
objectA list of
EvalSample, loadable from several formats.from fastrag.benchmark import ColumnMapping, QADataset QADataset.load("eval.csv") # auto-detected QADataset.load("eval.csv", ColumnMapping(question="user_query"))
- Parameters:
samples (list[EvalSample])
- samples: list[EvalSample]#
- classmethod load(path, columns=None)[source]#
Load a dataset, dispatching on
path’s suffix..jsonaccepts either{"samples": [...]}(the native format,save()’s output) or a bare[...]of records..jsonlreads one record per line..csvreads withcsv.DictReader..yaml/.parquetandfrom_huggingface()need thedatasetsextra — not yet implemented, kept behind the same gate this class already applies to its other deferred formats.- Raises:
ConfigurationError –
path’s suffix is.yamlor.parquet(needs thedatasetsextra, not yet implemented), or is anything else this method does not read.- Parameters:
columns (ColumnMapping | None)
- Return type:
- classmethod from_huggingface(path)[source]#
Load a dataset from Hugging Face Hub.
Not yet implemented — gated by the
datasetsextra, the same way the deferred.yaml/.parquetsuffixes already are.- Raises:
ConfigurationError – Always — the
datasetsextra is not yet wired up, so this raises naming the install command rather than pretending a loading path exists.- Parameters:
path (str)
- Return type:
- class fastrag.benchmark.SampleResult(question, status, scores, answer=None, retrieved_ids=<factory>, retrieved_uris=<factory>, latency_ms=0.0, error=None)[source]#
Bases:
objectOne sample’s outcome: its scores, and enough detail to diagnose them.
- Parameters:
- class fastrag.benchmark.StatusCounts(successful, errored, incomplete)[source]#
Bases:
objectHow a run’s samples broke down — never collapsed into one pass/fail count.
A question that errored is never scored as zero: it is tallied in
errored, not folded intoincompleteorsuccessful.
Modules
The |
|
The |
|
Evaluation datasets: a list of question/answer/relevant-id samples. |
|
Metrics: an object per measurement, scored per sample, aggregated per run. |
|
What one benchmark run produced, and how it is saved and reloaded. |