AQIT 0.1.0
Loading...
Searching...
No Matches
benchmark_check.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2"""`aquin benchmark --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 = "benchmark-check.json"
12_PNG_NAME = "benchmark-check.png"
13
14_METRICS = (
15 ("InterpScore", "score"),
16 ("Purity", "purity_score"),
17 ("MUI", "mui_score"),
19
20
21def _normalize_benchmark_result(result: dict[str, Any]) -> dict[str, Any]:
22 if not isinstance(result, dict):
23 return {"error": "Invalid benchmark result"}
24
25 data = result
26 if data.get("type") in ("benchmark", "benchmarkSuite") and isinstance(data.get("data"), dict):
27 data = data["data"]
28 if isinstance(data.get("content"), dict):
29 data = data["content"]
30 if data.get("type") in ("benchmark", "benchmarkSuite") and isinstance(data.get("data"), dict):
31 data = data["data"]
32
33 return dict(data)
34
35
36def has_benchmark_payload(result: dict[str, Any]) -> bool:
37 data = _normalize_benchmark_result(result)
38 if any(data.get(k) is not None for k in ("score", "purity_score", "mui_score")):
39 return True
40 return data.get("feature_idx") is not None
41
42
44 result: dict[str, Any],
45 *,
46 tool_name: str | None,
47 cwd: str | Path,
48) -> tuple[Path, Path]:
49 cwd = Path(cwd)
50 json_path = cwd / _JSON_NAME
51 png_path = cwd / _PNG_NAME
52
53 flat = _normalize_benchmark_result(result)
54 payload = {
55 "saved_at": datetime.now(timezone.utc).isoformat(),
56 "tool": tool_name,
57 **flat,
58 }
59 json_path.write_text(json.dumps(payload, indent=2, default=str), encoding="utf-8")
60 _plot_benchmark_check(flat, png_path)
61 return json_path, png_path
62
63
64def _score_color(val: float) -> str:
65 if val >= 0.75:
66 return "#34d399"
67 if val >= 0.5:
68 return "#fbbf24"
69 return "#f87171"
70
71
72def _plot_benchmark_check(result: dict[str, Any], png_path: Path) -> None:
73 import matplotlib
74
75 matplotlib.use("Agg")
76 import matplotlib.pyplot as plt
77
78 err = result.get("error")
79 has_scores = any(result.get(k) is not None for k in ("score", "purity_score", "mui_score"))
80
81 if err and not has_scores:
82 fig, ax = plt.subplots(figsize=(6, 2), facecolor="#0f1117")
83 ax.axis("off")
84 ax.text(0.5, 0.5, f"Error: {err}", ha="center", va="center", wrap=True, color="#e5e7eb")
85 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
86 plt.close(fig)
87 return
88
89 ref = result.get("feature_ref") or result.get("feature_idx", "?")
90 model = str(result.get("model_id") or "")
91 label = str(result.get("label") or "")
92
93 title = f"Feature benchmark — {ref}"
94 if model:
95 title += f" · {model}"
96 if label:
97 title += f"\n{label[:48]}"
98
99 fig, axes = plt.subplots(1, 2, figsize=(11, 4.5), facecolor="#0f1117")
100 fig.suptitle(title, color="#e5e7eb", fontsize=11, y=1.04)
101
102 _plot_metric_panel(axes[0], result)
103 _plot_activation_panel(axes[1], result)
104
105 if err:
106 fig.text(0.5, 0.01, f"Note: {err}", ha="center", fontsize=7, color="#fbbf24", wrap=True)
107
108 fig.tight_layout(rect=(0, 0.04 if err else 0, 1, 0.96))
109 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
110 plt.close(fig)
111
112
113def _plot_metric_panel(ax: Any, result: dict[str, Any]) -> None:
114 ax.set_facecolor("#0f1117")
115 ax.set_title("InterpScore · Purity · MUI", color="#e5e7eb", fontsize=10)
116
117 rows = [(name, result.get(key)) for name, key in _METRICS if result.get(key) is not None]
118 if not rows:
119 ax.axis("off")
120 ax.text(0.5, 0.5, "No scores", ha="center", va="center", color="#9ca3af")
121 return
122
123 labels = [r[0] for r in rows]
124 vals = [float(r[1]) for r in rows]
125 colors = [_score_color(v) for v in vals]
126 y_pos = list(range(len(rows)))
127
128 ax.barh(y_pos, vals, color=colors, height=0.65, alpha=0.92)
129 ax.set_yticks(y_pos)
130 ax.set_yticklabels(labels, fontsize=9, color="#d1d5db")
131 ax.invert_yaxis()
132 ax.set_xlim(0, 1.05)
133 ax.set_xlabel("Score (0–1)", color="#9ca3af", fontsize=8)
134 ax.tick_params(axis="x", colors="#6b7280", labelsize=8)
135 ax.grid(axis="x", alpha=0.2, color="#4b5563")
136 for spine in ax.spines.values():
137 spine.set_color("#374151")
138 for i, v in enumerate(vals):
139 ax.text(min(v + 0.02, 0.98), i, f"{v:.3f}", va="center", fontsize=8, color="#9ca3af")
140
141
142def _plot_activation_panel(ax: Any, result: dict[str, Any]) -> None:
143 ax.set_facecolor("#0f1117")
144 ax.set_title("Activation separation", color="#e5e7eb", fontsize=10)
145
146 pos_mean = result.get("positive_mean")
147 neg_mean = result.get("negative_mean")
148 if pos_mean is None and neg_mean is None:
149 pos_ex = result.get("positive_examples") or []
150 if pos_ex:
151 _plot_examples_panel(ax, pos_ex[:5])
152 return
153 ax.axis("off")
154 ax.text(0.5, 0.5, "No activation stats", ha="center", va="center", color="#9ca3af")
155 return
156
157 labels = ["fires on", "silent on"]
158 vals = [float(pos_mean or 0), float(neg_mean or 0)]
159 colors = ["#34d399", "#78716c"]
160 y_pos = [0, 1]
161 ax.barh(y_pos, vals, color=colors, height=0.55, alpha=0.9)
162 ax.set_yticks(y_pos)
163 ax.set_yticklabels(labels, fontsize=9, color="#d1d5db")
164 ax.invert_yaxis()
165 ax.set_xlabel("Mean activation", color="#9ca3af", fontsize=8)
166 ax.tick_params(axis="x", colors="#6b7280", labelsize=8)
167 ax.grid(axis="x", alpha=0.2, color="#4b5563")
168 for spine in ax.spines.values():
169 spine.set_color("#374151")
170 for i, v in enumerate(vals):
171 ax.text(v + max(vals) * 0.02 + 0.01, i, f"{v:.3f}", va="center", fontsize=8, color="#9ca3af")
172
173
174def _plot_examples_panel(ax: Any, examples: list[dict[str, Any]]) -> None:
175 ax.set_title("Top positive examples", color="#e5e7eb", fontsize=10)
176 labels = [_trunc(str(ex.get("sentence") or ""), 36) for ex in examples]
177 vals = [float(ex.get("activation") or 0) for ex in examples]
178 y_pos = list(range(len(examples)))
179 ax.barh(y_pos, vals, color="#6366f1", height=0.65, alpha=0.9)
180 ax.set_yticks(y_pos)
181 ax.set_yticklabels(labels, fontsize=7, color="#d1d5db")
182 ax.invert_yaxis()
183 ax.set_xlabel("Activation", color="#9ca3af", fontsize=8)
184 ax.tick_params(axis="x", colors="#6b7280", labelsize=7)
185 ax.grid(axis="x", alpha=0.2, color="#4b5563")
186 for spine in ax.spines.values():
187 spine.set_color("#374151")
188
189
190def _trunc(text: str, limit: int) -> str:
191 t = text.strip()
192 if len(t) <= limit:
193 return t or "·"
194 return t[: limit - 1] + "…"
tuple[Path, Path] write_benchmark_check(dict[str, Any] result, *, str|None tool_name, str|Path cwd)
str _trunc(str text, int limit)
None _plot_metric_panel(Any ax, dict[str, Any] result)
None _plot_benchmark_check(dict[str, Any] result, Path png_path)
str _score_color(float val)
None _plot_examples_panel(Any ax, list[dict[str, Any]] examples)
bool has_benchmark_payload(dict[str, Any] result)
None _plot_activation_panel(Any ax, dict[str, Any] result)
dict[str, Any] _normalize_benchmark_result(dict[str, Any] result)