Spaces:
Sleeping
Sleeping
| """Write the evaluation report (CLAUDE.md §6). | |
| Aggregates the three measurements into ``eval/REPORT.md``: | |
| 1. Retrieval Recall@10 (deterministic, no LLM) — from ``eval.recall_eval``. | |
| 2. Conversational Recall@10 + groundedness — from ``eval.replay`` (multi-turn, | |
| one router LLM call per turn). | |
| 3. Behavior probes — from ``eval.probes``. | |
| python eval/report.py # scripted replay | |
| python eval/report.py --llm-user # LLM-simulated user for the replay | |
| LLM-dependent sections degrade gracefully: if calls fail (e.g. rate limit), the | |
| affected traces/probes are flagged, and the deterministic retrieval section is | |
| always populated. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import sys | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(ROOT)) | |
| from eval import probes as probes_mod # noqa: E402 | |
| from eval import recall_eval, replay # noqa: E402 | |
| REPORT_PATH = ROOT / "eval" / "REPORT.md" | |
| def build_report(llm_user: bool = False) -> str: | |
| # 1. Deterministic retrieval recall (assembly + router query). | |
| retr = recall_eval.evaluate(k=10, use_router=False, assemble=True) | |
| # 2. Conversational replay (multi-turn through the real agent). | |
| conv = replay.run(mode="llm" if llm_user else "scripted") | |
| # 3. Behavior probes. | |
| probe_results = probes_mod.run_probes() | |
| lines: list[str] = [] | |
| lines += ["# Evaluation Report", ""] | |
| lines += [ | |
| "Generated by `python eval/report.py`. Three views: deterministic " | |
| "retrieval recall, multi-turn conversational recall, and behavior probes.", | |
| "", | |
| "## Headline", | |
| "", | |
| f"- **Retrieval Recall@10 (deterministic):** {retr['mean_recall']:.4f}", | |
| f"- **Conversational Recall@10 ({conv['mode']} user):** " | |
| f"{conv['mean_recall']:.4f} " | |
| f"(scored {conv['n_scored']}/{len(conv['rows'])}; " | |
| f"{conv['n_errored']} LLM-errored)", | |
| f"- **Groundedness:** {conv['groundedness']:.4f} (URLs in catalog)", | |
| f"- **Probes:** {sum(p.passed for p in probe_results)}/{len(probe_results)} passed", | |
| "", | |
| ] | |
| lines += ["## Retrieval Recall@10 (per trace)", ""] | |
| lines += ["| Trace | Recall | Hits/Exp | Missed |", "|--|--:|--:|--|"] | |
| for row in retr["rows"]: | |
| missed = ", ".join(row["missed"]) if row["missed"] else "—" | |
| lines.append(f"| {row['id']} | {row['recall']:.2f} | " | |
| f"{row['hits']}/{row['expected']} | {missed} |") | |
| lines.append("") | |
| lines += ["## Conversational Recall@10 (multi-turn replay)", ""] | |
| lines += ["| Trace | Recall | Hits/Exp | Turns | Ended | Recs | Note |", | |
| "|--|--:|--:|--:|--|--:|--|"] | |
| for row in conv["rows"]: | |
| note = ("LLM error (excluded)" if row["excluded"] | |
| else "recovered after transient error" if row["llm_error"] else "") | |
| lines.append(f"| {row['id']} | {row['recall']:.2f} | " | |
| f"{row['hits']}/{row['expected']} | {row['turns']} | " | |
| f"{row['ended']} | {row['n_recs']} | {note} |") | |
| lines.append("") | |
| lines += ["## Behavior probes", ""] | |
| lines += ["| Probe | Result | Detail |", "|--|--|--|"] | |
| for p in probe_results: | |
| lines.append(f"| {p.name} | {'PASS' if p.passed else 'FAIL'} | {p.detail} |") | |
| lines.append("") | |
| lines += [ | |
| "See `eval/TUNING_LOG.md` for the before/after that produced these " | |
| "numbers, and `APPROACH.md` for the method.", | |
| "", | |
| ] | |
| return "\n".join(lines) | |
| def main() -> None: | |
| ap = argparse.ArgumentParser(description=__doc__) | |
| ap.add_argument("--llm-user", action="store_true") | |
| args = ap.parse_args() | |
| report = build_report(llm_user=args.llm_user) | |
| REPORT_PATH.write_text(report, encoding="utf-8") | |
| print(report) | |
| print(f"\nWrote {REPORT_PATH}") | |
| if __name__ == "__main__": | |
| main() | |