nl-sql / scripts /merge_hybrid_eval.py
liovina's picture
Deploy NL_SQL HEAD to HF Space
942050b verified
Raw
History Blame Contribute Delete
3.27 kB
"""Merge a base eval report with a tier-specific override report.
Produces a hybrid report where chosen-tier records come from the override
file and the rest come from the base. Used for the codestral-default +
Sonnet-on-challenging recipe: codestral handles simple/moderate (where
both models tie) and Sonnet handles challenging (where it gains +3pp).
Usage::
uv run python scripts/merge_hybrid_eval.py \\
--base eval/reports/2026-05-11/G_dense_fewshot_verify_retry-verify-retry.json \\
--override eval/reports/2026-05-11/G_dense_fewshot_verify_retry-sonnet-challenging.json \\
--override-difficulty challenging \\
--suffix hybrid-codestral-sonnet
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
from nl_sql.eval.report import load_run_from_json, write_html_report, write_json_report
from nl_sql.eval.runner import EvalRecord, EvalRun, _summarise # type: ignore[attr-defined]
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base", type=Path, required=True)
parser.add_argument("--override", type=Path, required=True)
parser.add_argument(
"--override-difficulty",
choices=["simple", "moderate", "challenging"],
required=True,
)
parser.add_argument("--suffix", default="hybrid")
parser.add_argument("--reports", type=Path, default=Path("eval/reports"))
args = parser.parse_args(argv)
base = load_run_from_json(args.base)
over = load_run_from_json(args.override)
over_index = {r.question_id: r for r in over.records}
swapped = 0
merged: list[EvalRecord] = []
for rec in base.records:
if rec.difficulty == args.override_difficulty and rec.question_id in over_index:
merged.append(over_index[rec.question_id])
swapped += 1
else:
merged.append(rec)
sql_models = sorted({base.sql_model, over.sql_model})
hybrid = _summarise(
configuration=base.configuration,
sql_model=" + ".join(sql_models),
records=merged,
)
print(f"[info] base records: {len(base.records)} ({base.sql_model})")
print(f"[info] override records: {len(over.records)} ({over.sql_model})")
print(f"[info] swapped: {swapped} ({args.override_difficulty} tier)")
print(f"[info] merged: {len(merged)}")
print()
print(f"EA (final): {hybrid.overall.ea * 100:.1f}%")
for d in ("simple", "moderate", "challenging"):
slice_ = hybrid.per_difficulty[d]
print(f" {d:12} {slice_.ea * 100:.1f}% (n={slice_.n})")
json_path = write_json_report(hybrid, root=args.reports, name_suffix=args.suffix)
today_dir = json_path.parent
siblings: list[EvalRun] = []
for other in sorted(today_dir.glob("*.json")):
if other == json_path:
continue
try:
siblings.append(load_run_from_json(other))
except (KeyError, ValueError):
continue
html_path = write_html_report([*siblings, hybrid], root=args.reports)
print()
print(f"[json] {json_path}")
print(f"[html] {html_path}")
return 0
if __name__ == "__main__":
sys.exit(main())