AQIT 0.1.0
Loading...
Searching...
No Matches
boundary_eval_check.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2"""`aquin boundary-eval --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 = "boundary-eval-check.json"
12_PNG_NAME = "boundary-eval-check.png"
13
14
15def _normalize_boundary_eval_result(result: dict[str, Any]) -> dict[str, Any]:
16 if not isinstance(result, dict):
17 return {"error": "Invalid boundary-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 if isinstance(data.get("boundary"), dict):
26 data = data["boundary"]
27 card = result.get("card")
28 if isinstance(card, dict) and isinstance(card.get("data"), dict):
29 nested = card["data"].get("boundary")
30 if isinstance(nested, dict):
31 data = nested
32
33 return dict(data)
34
35
36def has_boundary_eval_payload(result: dict[str, Any]) -> bool:
38 if data.get("error"):
39 return False
40 return data.get("mean_robustness") is not None or bool(data.get("probes"))
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
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_boundary_eval_check(flat, png_path)
61 return json_path, png_path
62
63
64def _plot_boundary_eval_check(result: dict[str, Any], png_path: Path) -> None:
65 import matplotlib
66
67 matplotlib.use("Agg")
68 import matplotlib.pyplot as plt
69
70 if result.get("error"):
71 fig, ax = plt.subplots(figsize=(6, 2), facecolor="#0f1117")
72 ax.axis("off")
73 ax.text(0.5, 0.5, f"Error: {result['error']}", ha="center", va="center", wrap=True, color="#e5e7eb")
74 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
75 plt.close(fig)
76 return
77
78 probes = result.get("probes") or []
79 mean_rob = float(result.get("mean_robustness") or 0)
80 title = f"Boundary eval — mean robustness {mean_rob:.3f} · {len(probes)} probe(s)"
81
82 fig, axes = plt.subplots(1, 2, figsize=(11, max(4.5, min(10, len(probes) * 0.35 + 2))), facecolor="#0f1117")
83 fig.suptitle(title, color="#e5e7eb", fontsize=11, y=1.02)
84
85 _plot_robustness_panel(axes[0], probes, mean_rob)
86 _plot_degradation_panel(axes[1], probes)
87
88 for ax in axes:
89 ax.set_facecolor("#0f1117")
90 for spine in ax.spines.values():
91 spine.set_color("#374151")
92
93 fig.tight_layout()
94 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
95 plt.close(fig)
96
97
98def _plot_robustness_panel(ax: Any, probes: list[dict[str, Any]], mean_rob: float) -> None:
99 ax.set_title("Robustness by prompt", color="#e5e7eb", fontsize=10)
100
101 if not probes:
102 color = _score_color(mean_rob)
103 ax.barh([0], [mean_rob], color=color, height=0.45, alpha=0.92)
104 ax.set_xlim(0, 1.05)
105 ax.set_yticks([0])
106 ax.set_yticklabels(["mean"], fontsize=9, color="#d1d5db")
107 ax.set_xlabel("Robustness (0–1)", color="#9ca3af", fontsize=8)
108 ax.text(min(mean_rob + 0.02, 0.98), 0, f"{mean_rob:.3f}", va="center", fontsize=8, color="#9ca3af")
109 ax.tick_params(axis="x", colors="#6b7280", labelsize=8)
110 ax.grid(axis="x", alpha=0.2, color="#4b5563")
111 return
112
113 labels = [_trunc(str(p.get("prompt") or ""), 22) for p in probes]
114 vals = [float(p.get("robustness_score") or 0) for p in probes]
115 colors = [_score_color(v) for v in vals]
116 y_pos = list(range(len(probes)))
117
118 ax.barh(y_pos, vals, color=colors, height=0.65, alpha=0.9)
119 ax.axvline(mean_rob, color="#38bdf8", linestyle="--", linewidth=0.8, alpha=0.7)
120 ax.set_yticks(y_pos)
121 ax.set_yticklabels(labels, fontsize=7, color="#d1d5db")
122 ax.invert_yaxis()
123 ax.set_xlim(0, 1.05)
124 ax.set_xlabel("Robustness", color="#9ca3af", fontsize=8)
125 ax.tick_params(axis="x", colors="#6b7280", labelsize=8)
126 ax.grid(axis="x", alpha=0.2, color="#4b5563")
127
128
129def _plot_degradation_panel(ax: Any, probes: list[dict[str, Any]]) -> None:
130 ax.set_title("Confidence drop & KL under corruption", color="#e5e7eb", fontsize=10)
131
132 if not probes:
133 ax.axis("off")
134 ax.text(0.5, 0.5, "No probes", ha="center", va="center", color="#9ca3af")
135 return
136
137 labels = [_trunc(str(p.get("prompt") or ""), 12) for p in probes]
138 x = list(range(len(probes)))
139 width = 0.38
140 drops = [float(p.get("mean_confidence_drop") or 0) for p in probes]
141 kls = [float(p.get("mean_kl") or 0) for p in probes]
142
143 ax.bar([i - width / 2 for i in x], drops, width=width, color="#f87171", alpha=0.9, label="conf drop")
144 ax.bar([i + width / 2 for i in x], kls, width=width, color="#6366f1", alpha=0.9, label="mean KL")
145 ax.set_xticks(x)
146 ax.set_xticklabels(labels, fontsize=7, color="#d1d5db", rotation=30, ha="right")
147 ax.set_ylabel("Magnitude", color="#9ca3af", fontsize=8)
148 ax.legend(fontsize=7, facecolor="#0f1117", edgecolor="#374151", labelcolor="#d1d5db")
149 ax.tick_params(colors="#6b7280", labelsize=7)
150 ax.grid(axis="y", alpha=0.2, color="#4b5563")
151
152
153def _score_color(val: float) -> str:
154 if val >= 0.75:
155 return "#34d399"
156 if val >= 0.5:
157 return "#fbbf24"
158 return "#f87171"
159
160
161def _trunc(text: str, limit: int) -> str:
162 t = text.strip()
163 if len(t) <= limit:
164 return t or "·"
165 return t[: limit - 1] + "…"
None _plot_robustness_panel(Any ax, list[dict[str, Any]] probes, float mean_rob)
str _trunc(str text, int limit)
bool has_boundary_eval_payload(dict[str, Any] result)
tuple[Path, Path] write_boundary_eval_check(dict[str, Any] result, *, str|None tool_name, str|Path cwd)
None _plot_degradation_panel(Any ax, list[dict[str, Any]] probes)
None _plot_boundary_eval_check(dict[str, Any] result, Path png_path)
dict[str, Any] _normalize_boundary_eval_result(dict[str, Any] result)