AQIT 0.1.0
Loading...
Searching...
No Matches
eval_check.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2"""`aquin eval custom --check` — save JSON + diagram to the current working directory."""
3
4from __future__ import annotations
5
6import json
7from datetime import datetime, timezone
8from pathlib import Path
9from typing import Any
10
11_JSON_NAME = "eval-check.json"
12_PNG_NAME = "eval-check.png"
13
14
15def _normalize_eval_result(result: dict[str, Any]) -> dict[str, Any]:
16 if not isinstance(result, dict):
17 return {"error": "Invalid eval result"}
18
19 if result.get("error"):
20 return dict(result)
21
22 data = result
23 if isinstance(result.get("content"), dict):
24 data = result["content"]
25 card = result.get("card")
26 if isinstance(card, dict) and isinstance(card.get("data"), dict):
27 data = card["data"]
28
29 return dict(data)
30
31
32def has_eval_payload(result: dict[str, Any]) -> bool:
33 data = _normalize_eval_result(result)
34 if data.get("error"):
35 return False
36 return bool(data.get("prompts")) or data.get("mean_score") is not None
37
38
40 result: dict[str, Any],
41 *,
42 tool_name: str | None,
43 cwd: str | Path,
44) -> tuple[Path, Path]:
45 cwd = Path(cwd)
46 json_path = cwd / _JSON_NAME
47 png_path = cwd / _PNG_NAME
48
49 flat = _normalize_eval_result(result)
50 payload = {
51 "saved_at": datetime.now(timezone.utc).isoformat(),
52 "tool": tool_name,
53 **flat,
54 }
55 json_path.write_text(json.dumps(payload, indent=2, default=str), encoding="utf-8")
56 _plot_eval_check(flat, png_path)
57 return json_path, png_path
58
59
60def _plot_eval_check(result: dict[str, Any], png_path: Path) -> None:
61 import matplotlib
62
63 matplotlib.use("Agg")
64 import matplotlib.pyplot as plt
65
66 if result.get("error"):
67 fig, ax = plt.subplots(figsize=(6, 2), facecolor="#0f1117")
68 ax.axis("off")
69 ax.text(0.5, 0.5, f"Error: {result['error']}", ha="center", va="center", wrap=True, color="#e5e7eb")
70 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
71 plt.close(fig)
72 return
73
74 name = str(result.get("name") or "eval")
75 model = str(result.get("model_id") or "")
76 threshold = float(result.get("threshold") or 0.5)
77 mean_score = float(result.get("mean_score") or 0)
78 pass_rate = float(result.get("pass_rate") or 0)
79 rows = result.get("prompts") or []
80
81 title = f"Custom eval — {name}"
82 if model:
83 title += f" · {model}"
84 subtitle = f"mean {mean_score:.0%} · pass rate {pass_rate:.0%} · threshold {threshold:.0%}"
85
86 fig, axes = plt.subplots(1, 2, figsize=(11, max(4.5, min(12, len(rows) * 0.38 + 2))), facecolor="#0f1117")
87 fig.suptitle(title, color="#e5e7eb", fontsize=11, y=1.02)
88 fig.text(0.5, 0.96, subtitle, ha="center", fontsize=8, color="#9ca3af")
89
90 _plot_prompt_scores(axes[0], rows, threshold)
91 _plot_summary_panel(axes[1], mean_score, pass_rate, threshold, rows)
92
93 for ax in axes:
94 ax.set_facecolor("#0f1117")
95 for spine in ax.spines.values():
96 spine.set_color("#374151")
97
98 fig.tight_layout(rect=(0, 0, 1, 0.93))
99 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
100 plt.close(fig)
101
102
103def _plot_prompt_scores(ax: Any, rows: list[dict[str, Any]], threshold: float) -> None:
104 ax.set_title("Per-prompt score", color="#e5e7eb", fontsize=10)
105
106 if not rows:
107 ax.axis("off")
108 ax.text(0.5, 0.5, "No prompts", ha="center", va="center", color="#9ca3af")
109 return
110
111 labels = [_trunc(str(r.get("prompt") or ""), 28) for r in rows]
112 vals = [float(r.get("score") or 0) for r in rows]
113 colors = ["#34d399" if r.get("passed") else "#f87171" for r in rows]
114
115 y_pos = list(range(len(rows)))
116 ax.barh(y_pos, vals, color=colors, height=0.65, alpha=0.9)
117 ax.axvline(threshold, color="#fbbf24", linestyle="--", linewidth=0.8, alpha=0.75)
118 ax.set_yticks(y_pos)
119 ax.set_yticklabels(labels, fontsize=7, color="#d1d5db")
120 ax.invert_yaxis()
121 ax.set_xlim(0, 1.05)
122 ax.set_xlabel("Score (0–1)", color="#9ca3af", fontsize=8)
123 ax.tick_params(axis="x", colors="#6b7280", labelsize=8)
124 ax.grid(axis="x", alpha=0.2, color="#4b5563")
125 for i, v in enumerate(vals):
126 ax.text(min(v + 0.02, 0.98), i, f"{v:.2f}", va="center", fontsize=7, color="#9ca3af")
127
128
130 ax: Any,
131 mean_score: float,
132 pass_rate: float,
133 threshold: float,
134 rows: list[dict[str, Any]],
135) -> None:
136 ax.set_title("Summary", color="#e5e7eb", fontsize=10)
137
138 metrics = [("mean score", mean_score), ("pass rate", pass_rate), ("threshold", threshold)]
139 labels = [m[0] for m in metrics]
140 vals = [m[1] for m in metrics]
141 colors = [_score_color(mean_score), _score_color(pass_rate), "#78716c"]
142
143 y_pos = list(range(len(metrics)))
144 ax.barh(y_pos, vals, color=colors, height=0.55, alpha=0.9)
145 ax.set_yticks(y_pos)
146 ax.set_yticklabels(labels, fontsize=9, color="#d1d5db")
147 ax.invert_yaxis()
148 ax.set_xlim(0, 1.05)
149 ax.set_xlabel("Value (0–1)", color="#9ca3af", fontsize=8)
150 ax.tick_params(axis="x", colors="#6b7280", labelsize=8)
151 ax.grid(axis="x", alpha=0.2, color="#4b5563")
152 for i, v in enumerate(vals):
153 ax.text(min(v + 0.02, 0.98), i, f"{v:.0%}", va="center", fontsize=8, color="#9ca3af")
154
155 n_pass = sum(1 for r in rows if r.get("passed"))
156 ax.text(
157 0.02, 0.02,
158 f"{n_pass}/{len(rows)} passed",
159 transform=ax.transAxes,
160 fontsize=8,
161 color="#9ca3af",
162 )
163
164
165def _score_color(val: float) -> str:
166 if val >= 0.75:
167 return "#34d399"
168 if val >= 0.5:
169 return "#fbbf24"
170 return "#f87171"
171
172
173def _trunc(text: str, limit: int) -> str:
174 t = text.strip()
175 if len(t) <= limit:
176 return t or "·"
177 return t[: limit - 1] + "…"
str _trunc(str text, int limit)
None _plot_prompt_scores(Any ax, list[dict[str, Any]] rows, float threshold)
None _plot_summary_panel(Any ax, float mean_score, float pass_rate, float threshold, list[dict[str, Any]] rows)
bool has_eval_payload(dict[str, Any] result)
Definition eval_check.py:36
tuple[Path, Path] write_eval_check(dict[str, Any] result, *, str|None tool_name, str|Path cwd)
Definition eval_check.py:48
str _score_color(float val)
dict[str, Any] _normalize_eval_result(dict[str, Any] result)
Definition eval_check.py:19
None _plot_eval_check(dict[str, Any] result, Path png_path)
Definition eval_check.py:64