AQIT 0.1.0
Loading...
Searching...
No Matches
perturbation_check.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2"""`aquin check perturbation --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 = "perturbation-check.json"
12_PNG_NAME = "perturbation-check.png"
13
14
16 result: dict[str, Any],
17 *,
18 tool_name: str | None,
19 cwd: str | Path,
20) -> tuple[Path, Path]:
21 cwd = Path(cwd)
22 json_path = cwd / _JSON_NAME
23 png_path = cwd / _PNG_NAME
24
25 payload = {
26 "saved_at": datetime.now(timezone.utc).isoformat(),
27 "tool": tool_name,
28 **result,
29 }
30 json_path.write_text(json.dumps(payload, indent=2, default=str), encoding="utf-8")
31 _plot_perturbation_check(result, tool_name, png_path)
32 return json_path, png_path
33
34
35
37 result: dict[str, Any],
38 tool_name: str | None,
39 png_path: Path,
40) -> None:
41 import matplotlib
42
43 matplotlib.use("Agg")
44 import matplotlib.pyplot as plt
45
46 if result.get("error"):
47 fig, ax = plt.subplots(figsize=(6, 2), facecolor="#0f1117")
48 ax.axis("off")
49 ax.text(0.5, 0.5, f"Error: {result['error']}", ha="center", va="center", wrap=True, color="#e5e7eb")
50 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
51 plt.close(fig)
52 return
53
54 _ = tool_name
55 _plot_llm_perturbation(result, png_path, plt)
56
57
58def _plot_llm_perturbation(result: dict[str, Any], png_path: Path, plt: Any) -> None:
59 raw = result.get("channels") or result.get("perturbation_results") or []
60 rows = sorted(
61 [
62 {
63 "channel": int(r.get("channel", i)),
64 "kl": float(r.get("kl_mean", r.get("kl_divergence")) or 0),
65 }
66 for i, r in enumerate(raw)
67 ],
68 key=lambda x: x["kl"],
69 reverse=True,
70 )[:24]
71
72 layer = result.get("layer", "—")
73 prompt = str(result.get("prompt") or "")[:56]
74 mean_kl = float(result.get("global_mean_kl") or 0)
75 max_kl = float(result.get("global_max_kl") or 0)
76
77 fig, ax = plt.subplots(figsize=(9, max(4, len(rows) * 0.28 + 1.5)), facecolor="#0f1117")
78 fig.suptitle(
79 f"Perturbation sensitivity (LLM) — layer {layer}",
80 color="#e5e7eb",
81 fontsize=11,
82 y=0.98,
83 )
84 ax.set_title(f'"{prompt}" · mean KL {mean_kl:.4f} · max {max_kl:.4f}', color="#9ca3af", fontsize=9, pad=8)
85
86 if not rows:
87 ax.axis("off")
88 ax.text(0.5, 0.5, "No channel results.", ha="center", va="center", color="#9ca3af")
89 else:
90 labels = [f"ch {r['channel']}" for r in rows]
91 vals = [r["kl"] for r in rows]
92 colors = ["#f87171" if v >= max_kl * 0.85 else "#6366f1" if v >= mean_kl else "#78716c" for v in vals]
93 y_pos = list(range(len(rows)))
94 ax.barh(y_pos, vals, color=colors, height=0.72)
95 ax.set_yticks(y_pos)
96 ax.set_yticklabels(labels, fontsize=8, color="#d1d5db")
97 ax.invert_yaxis()
98 ax.set_xlabel("KL divergence", color="#9ca3af", fontsize=9)
99 ax.tick_params(axis="x", colors="#6b7280", labelsize=8)
100 ax.grid(axis="x", alpha=0.2, color="#4b5563")
101 for spine in ax.spines.values():
102 spine.set_color("#374151")
103
104 fig.tight_layout()
105 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
106 plt.close(fig)
107
108
109
110def _short_token(tok: str, limit: int = 14) -> str:
111 t = tok.replace("Ġ", " ").replace("##", "").strip()
112 if len(t) <= limit:
113 return t or "·"
114 return t[: limit - 1] + "…"
None _plot_perturbation_check(dict[str, Any] result, str|None tool_name, Path png_path)
str _short_token(str tok, int limit=14)
tuple[Path, Path] write_perturbation_check(dict[str, Any] result, *, str|None tool_name, str|Path cwd)
None _plot_llm_perturbation(dict[str, Any] result, Path png_path, Any plt)