Merge pull request #260 from ggozad/feat/evals-huggingface

Introduce huggingface dataset for sharing evaluation dbs
This commit is contained in:
Yiorgis Gozadinos 2026-01-26 16:40:04 +02:00 committed by GitHub
commit 6f94f71d0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 133 additions and 3 deletions

View file

@ -1,6 +1,15 @@
# Changelog # Changelog
## [Unreleased] ## [Unreleased]
### Added
- **Evaluation Database Hosting**: Pre-built evaluation databases available on HuggingFace
- `evaluations download <dataset>` downloads pre-built databases from `ggozad/haiku-rag-eval-dbs`
- `evaluations upload <dataset>` uploads databases to HuggingFace (maintainer only)
- Supports `all` argument to download/upload all datasets at once
- Use `--force` flag to overwrite existing databases
- Avoids lengthy database rebuild times for users running benchmarks
### Changed ### Changed
- **Dependencies**: Updated core dependencies - **Dependencies**: Updated core dependencies

View file

@ -7,18 +7,48 @@ We evaluate `haiku.rag` on several datasets to measure both retrieval quality an
You can run evaluations with the `evaluations` CLI: You can run evaluations with the `evaluations` CLI:
```bash ```bash
evaluations repliqa evaluations run repliqa
evaluations wix evaluations run wix
``` ```
The evaluation flow is orchestrated with [`pydantic-evals`](https://github.com/pydantic/pydantic-ai/tree/main/libs/pydantic-evals), which we leverage for dataset management, scoring, and report generation. The evaluation flow is orchestrated with [`pydantic-evals`](https://github.com/pydantic/pydantic-ai/tree/main/libs/pydantic-evals), which we leverage for dataset management, scoring, and report generation.
### Pre-built Databases
Building evaluation databases from scratch can take a long time, especially for large datasets like OpenRAG Bench. Pre-built databases are available on HuggingFace:
```bash
# Download a specific dataset
evaluations download repliqa
# Download all datasets
evaluations download all
# Force re-download (overwrite existing)
evaluations download repliqa --force
```
Available datasets:
| Dataset | Size |
|---------|------|
| `repliqa` | ~30MB |
| `hotpotqa` | ~331MB |
| `wix` | ~511MB |
| `open_rag_bench` | ~14GB |
After downloading, run benchmarks with `--skip-db` to use the pre-built database:
```bash
evaluations run repliqa --skip-db
```
### Configuration ### Configuration
The benchmark script accepts several options: The benchmark script accepts several options:
```bash ```bash
evaluations repliqa --config /path/to/haiku.rag.yaml --db /path/to/custom.lancedb evaluations run repliqa --config /path/to/haiku.rag.yaml --db /path/to/custom.lancedb
``` ```
**Options:** **Options:**

View file

@ -1,4 +1,5 @@
import asyncio import asyncio
import shutil
from collections.abc import Mapping from collections.abc import Mapping
from pathlib import Path from pathlib import Path
from typing import Any, cast from typing import Any, cast
@ -6,6 +7,7 @@ from typing import Any, cast
import logfire import logfire
import typer import typer
from dotenv import find_dotenv, load_dotenv from dotenv import find_dotenv, load_dotenv
from huggingface_hub import HfApi, snapshot_download
from pydantic_evals import Case, Dataset as EvalDataset from pydantic_evals import Case, Dataset as EvalDataset
from pydantic_evals.evaluators import LLMJudge from pydantic_evals.evaluators import LLMJudge
from pydantic_evals.reporting import ReportCaseFailure from pydantic_evals.reporting import ReportCaseFailure
@ -24,6 +26,8 @@ from haiku.rag.utils import get_model
load_dotenv(find_dotenv(usecwd=True)) load_dotenv(find_dotenv(usecwd=True))
HF_REPO_ID = "ggozad/haiku-rag-eval-dbs"
logfire.configure(send_to_logfire="if-token-present", service_name="evals") logfire.configure(send_to_logfire="if-token-present", service_name="evals")
logfire.instrument_pydantic_ai() logfire.instrument_pydantic_ai()
configure_cli_logging() configure_cli_logging()
@ -449,5 +453,89 @@ def run(
) )
@app.command()
def download(
dataset: str = typer.Argument(..., help="Dataset key or 'all' to download all."),
force: bool = typer.Option(False, "--force", help="Overwrite existing database."),
) -> None:
"""Download pre-built evaluation database from HuggingFace."""
if dataset.lower() == "all":
specs = list(DATASETS.values())
else:
spec = DATASETS.get(dataset.lower())
if spec is None:
valid_datasets = ", ".join(sorted(DATASETS))
raise typer.BadParameter(
f"Unknown dataset '{dataset}'. Choose from: {valid_datasets}, all"
)
specs = [spec]
for spec in specs:
db = spec.db_path()
if db.exists() and not force:
console.print(
f"[yellow]Skipping {spec.key}: database already exists at {db}[/yellow]"
)
console.print("Use --force to overwrite.")
continue
console.print(f"[blue]Downloading {spec.key}...[/blue]")
try:
downloaded_path = snapshot_download(
repo_id=HF_REPO_ID,
repo_type="dataset",
allow_patterns=f"{spec.db_filename}/*",
)
except Exception as e:
console.print(f"[red]Failed to download {spec.key}: {e}[/red]")
continue
# Remove existing database if force is set
if db.exists():
shutil.rmtree(db)
# Copy from cache to target location
db.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(Path(downloaded_path) / spec.db_filename, db)
console.print(f"[green]Downloaded {spec.key} to {db}[/green]")
@app.command()
def upload(
dataset: str = typer.Argument(..., help="Dataset key or 'all' to upload all."),
) -> None:
"""Upload evaluation database to HuggingFace (maintainer only)."""
if dataset.lower() == "all":
specs = list(DATASETS.values())
else:
spec = DATASETS.get(dataset.lower())
if spec is None:
valid_datasets = ", ".join(sorted(DATASETS))
raise typer.BadParameter(
f"Unknown dataset '{dataset}'. Choose from: {valid_datasets}, all"
)
specs = [spec]
api = HfApi()
for spec in specs:
db = spec.db_path()
if not db.exists():
console.print(f"[red]Database not found at {db}[/red]")
continue
console.print(f"[blue]Uploading {spec.key}...[/blue]")
api.upload_folder(
folder_path=str(db),
path_in_repo=spec.db_filename,
repo_id=HF_REPO_ID,
repo_type="dataset",
)
console.print(f"[green]Uploaded {spec.key} to {HF_REPO_ID}[/green]")
if __name__ == "__main__": if __name__ == "__main__":
app() app()

View file

@ -11,6 +11,7 @@ dependencies = [
"haiku.rag-slim", "haiku.rag-slim",
"pydantic-ai-slim[evals,logfire]>=1.46.0", "pydantic-ai-slim[evals,logfire]>=1.46.0",
"datasets>=4.5.0", "datasets>=4.5.0",
"huggingface_hub>=0.20.0",
"typer>=0.19.2,<0.20.0", "typer>=0.19.2,<0.20.0",
"python-dotenv>=1.2.1", "python-dotenv>=1.2.1",
] ]

View file

@ -1345,6 +1345,7 @@ source = { editable = "evaluations" }
dependencies = [ dependencies = [
{ name = "datasets" }, { name = "datasets" },
{ name = "haiku-rag-slim" }, { name = "haiku-rag-slim" },
{ name = "huggingface-hub" },
{ name = "pydantic-ai-slim", extra = ["evals", "logfire"] }, { name = "pydantic-ai-slim", extra = ["evals", "logfire"] },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "typer" }, { name = "typer" },
@ -1354,6 +1355,7 @@ dependencies = [
requires-dist = [ requires-dist = [
{ name = "datasets", specifier = ">=4.5.0" }, { name = "datasets", specifier = ">=4.5.0" },
{ name = "haiku-rag-slim", editable = "haiku_rag_slim" }, { name = "haiku-rag-slim", editable = "haiku_rag_slim" },
{ name = "huggingface-hub", specifier = ">=0.20.0" },
{ name = "pydantic-ai-slim", extras = ["evals", "logfire"], specifier = ">=1.46.0" }, { name = "pydantic-ai-slim", extras = ["evals", "logfire"], specifier = ">=1.46.0" },
{ name = "python-dotenv", specifier = ">=1.2.1" }, { name = "python-dotenv", specifier = ">=1.2.1" },
{ name = "typer", specifier = ">=0.19.2,<0.20.0" }, { name = "typer", specifier = ">=0.19.2,<0.20.0" },