askmydocs / scripts /generate_benchmark.py
Prabu17's picture
Upload folder using huggingface_hub
da4731b verified
Raw History Blame Contribute Delete
5.11 kB
"""
Generate evals/reports/benchmark.md β€” a human-readable comparison of all experiments.
Run:
python scripts/generate_benchmark.py
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
RESULTS_DIR = ROOT / "evals" / "results"
REPORT_PATH = ROOT / "evals" / "reports" / "benchmark.md"
EXPERIMENTS = ["vector", "bm25", "hybrid", "final"]
def _load_results(experiment: str, results_dir: str | Path | None = None) -> dict | None:
"""Load the latest results JSON for the given experiment name.
Results are saved to evals/results/experiment_{experiment}_{id}.json
(e.g. experiment_final_a1b2c3d4.json), so we glob for that pattern
directly rather than looking in a subdirectory.
``results_dir`` overrides the default ``evals/results`` location.
"""
root = Path(results_dir) if results_dir else RESULTS_DIR
if not root.exists():
return None
# Find all JSON files matching the experiment pattern and pick the latest
pattern = f"experiment_{experiment}_*.json"
files = sorted(root.glob(pattern), key=lambda p: p.stat().st_mtime)
if not files:
return None
return json.loads(files[-1].read_text(encoding="utf-8"))
def _fmt(v: float | None, decimals: int = 4) -> str:
if v is None:
return "N/A"
return f"{v:.{decimals}f}"
def _section(title: str) -> str:
return f"\n## {title}\n"
def _table_row(values: list[str]) -> str:
return "| " + " | ".join(values) + " |"
def _table_header(cols: list[str]) -> str:
return "| " + " | ".join(cols) + " |\n" + "| " + " | ".join("---" for _ in cols) + " |"
def generate_report(results_dir: str | Path | None = None) -> Path:
"""Render ``evals/reports/benchmark.md`` from the experiment result files.
Returns the path the report was written to. Callers that only need the
artifact (e.g. the Celery task) should use this; ``main()`` is the CLI
wrapper around it.
``results_dir`` overrides the default ``evals/results`` location.
"""
REPORT_PATH.parent.mkdir(parents=True, exist_ok=True)
lines: list[str] = [
"# RAG Evaluation Benchmark",
"",
_section("Experiment Summary"),
_table_header([
"Experiment", "Questions", "Duration (s)",
"Recall@10", "Hit Rate@5", "Citation Correctness",
]),
"",
]
experiments_data: dict[str, dict] = {}
for exp in EXPERIMENTS:
data = _load_results(exp, results_dir)
if data is None:
lines.append(
_table_row([exp, "β€”", "β€”", "β€”", "β€”", "β€”", "No results found"])
)
continue
experiments_data[exp] = data
agg = data.get("aggregate_retrieval", {})
cit = data.get("aggregate_citation", {})
lines.append(
_table_row([
exp,
str(data.get("question_count", "?")),
f"{data.get('duration_seconds', 0):.1f}",
_fmt(agg.get("recall_at_k")),
_fmt(agg.get("hit_rate")),
_fmt(cit.get("citation_correctness")),
])
)
lines.append("")
# Per-difficulty breakdown
if experiments_data:
lines.append(_section("Per-Difficulty Breakdown"))
for exp, data in experiments_data.items():
per_diff = data.get("per_difficulty", {})
if not per_diff:
continue
lines.append(f"\n### {exp}\n")
lines.append(_table_header([
"Difficulty", "Count", "Recall@10",
"Hit Rate", "Citation Correctness",
]))
for diff_name in ["easy", "medium", "hard", "unanswerable"]:
dd = per_diff.get(diff_name, {})
if not dd:
continue
lines.append(
_table_row([
diff_name,
str(dd.get("count", "?")),
_fmt(dd.get("recall_at_k")),
_fmt(dd.get("hit_rate")),
_fmt(dd.get("citation_correctness")),
])
)
lines.append("")
# Ragas metrics
ragas_exp = experiments_data.get("final", {})
ragas_metrics = ragas_exp.get("aggregate_ragas", {})
if ragas_metrics:
lines.append(_section("Ragas Metrics (Experiment D β€” Full Pipeline)"))
lines.append(_table_header(["Metric", "Value"]))
for key, val in sorted(ragas_metrics.items()):
lines.append(_table_row([key, _fmt(val)]))
lines.append("")
REPORT_PATH.write_text("\n".join(lines) + "\n", encoding="utf-8")
return REPORT_PATH
def main() -> int:
report_path = generate_report()
print(f"Benchmark written to {report_path}")
return 0
if __name__ == "__main__":
sys.exit(main())