AQIT 0.1.0
Loading...
Searching...
No Matches
trace_check.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2"""`aquin trace --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 = "trace-check.json"
12_PNG_NAME = "trace-check.png"
13
14
15def _flatten_trace_result(result: dict[str, Any]) -> dict[str, Any]:
16 if result.get("error"):
17 return dict(result)
18
19 content = result.get("content") or {}
20 card_data = (result.get("card") or {}).get("data") or {}
21
22 def pick(snake: str, camel: str, default: Any = None) -> Any:
23 val = content.get(snake)
24 if val is None or val == "" or val == []:
25 val = card_data.get(camel, default)
26 return default if val is None else val
27
28 flat = {
29 "prompt": pick("prompt", "prompt", ""),
30 "response": pick("response", "response", ""),
31 "model_id": pick("model_id", "modelId", ""),
32 "sae_layer": pick("sae_layer", "saeLayer"),
33 "top_features": pick("top_features", "topFeatures", []),
34 "prompt_tokens": card_data.get("promptTokens", content.get("prompt_tokens", [])),
35 "response_tokens": card_data.get("responseTokens", content.get("response_tokens", [])),
36 "attribution": card_data.get("attribution", content.get("attribution", [])),
37 "logit_lens": card_data.get("logitLensResults", content.get("logit_lens", [])),
38 "trace_results": card_data.get("traceResults", content.get("trace_results", [])),
39 "trace_target": card_data.get("traceTarget", content.get("trace_target", "")),
40 "color_map": card_data.get("colorMap", content.get("color_map")),
41 }
42 if result.get("error"):
43 flat["error"] = result["error"]
44 return flat
45
46
48 result: dict[str, Any],
49 *,
50 tool_name: str | None,
51 cwd: str | Path,
52) -> tuple[Path, Path]:
53 cwd = Path(cwd)
54 json_path = cwd / _JSON_NAME
55 png_path = cwd / _PNG_NAME
56
57 flat = _flatten_trace_result(result)
58 payload = {
59 "saved_at": datetime.now(timezone.utc).isoformat(),
60 "tool": tool_name,
61 **flat,
62 }
63 json_path.write_text(json.dumps(payload, indent=2, default=str), encoding="utf-8")
64 _plot_trace_check(flat, png_path)
65 return json_path, png_path
66
67
68def _plot_trace_check(result: dict[str, Any], png_path: Path) -> None:
69 import matplotlib
70
71 matplotlib.use("Agg")
72 import matplotlib.pyplot as plt
73
74 if result.get("error"):
75 fig, ax = plt.subplots(figsize=(6, 2), facecolor="#0f1117")
76 ax.axis("off")
77 ax.text(0.5, 0.5, f"Error: {result['error']}", ha="center", va="center", wrap=True, color="#e5e7eb")
78 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
79 plt.close(fig)
80 return
81
82 model = str(result.get("model_id") or "")
83 prompt = str(result.get("prompt") or "")[:64]
84 response = str(result.get("response") or "")[:80]
85 sae_layer = result.get("sae_layer", "?")
86
87 fig = plt.figure(figsize=(12, 7), facecolor="#0f1117")
88 gs = fig.add_gridspec(2, 2, hspace=0.38, wspace=0.28)
89 ax_feat = fig.add_subplot(gs[0, 0])
90 ax_lens = fig.add_subplot(gs[0, 1])
91 ax_trace = fig.add_subplot(gs[1, 0])
92 ax_text = fig.add_subplot(gs[1, 1])
93
94 fig.suptitle(f"Full trace — {model} · SAE layer {sae_layer}", color="#e5e7eb", fontsize=11, y=0.98)
95
96 _plot_top_features(ax_feat, result.get("top_features") or [])
97 _plot_logit_lens(ax_lens, result.get("logit_lens") or [], result.get("trace_target") or "")
98 _plot_trace(ax_trace, result.get("trace_results") or [])
99 _plot_summary(ax_text, prompt, response, result.get("attribution") or [])
100
101 for ax in (ax_feat, ax_lens, ax_trace):
102 ax.set_facecolor("#0f1117")
103 for spine in ax.spines.values():
104 spine.set_color("#374151")
105
106 ax_text.set_facecolor("#0f1117")
107 for spine in ax_text.spines.values():
108 spine.set_color("#374151")
109
110 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
111 plt.close(fig)
112
113
114def _plot_top_features(ax: Any, features: list[dict[str, Any]]) -> None:
115 rows = sorted(features, key=lambda f: float(f.get("activation") or 0), reverse=True)[:12]
116 if not rows:
117 ax.axis("off")
118 ax.text(0.5, 0.5, "No top features", ha="center", va="center", color="#9ca3af")
119 ax.set_title("Top SAE features", color="#e5e7eb", fontsize=10)
120 return
121
122 labels = []
123 vals = []
124 for f in rows:
125 ref = f.get("feature_ref") or f.get("feature_idx", "?")
126 label = f.get("causal_label") or f.get("label") or ""
127 tok = f.get("token") or ""
128 suffix = f" · {tok!r}" if tok else (f" · {label[:18]}" if label else "")
129 labels.append(f"{ref}{suffix}"[:28])
130 vals.append(float(f.get("activation") or 0))
131
132 y_pos = list(range(len(rows)))
133 ax.barh(y_pos, vals, color="#6366f1", height=0.72, alpha=0.9)
134 ax.set_yticks(y_pos)
135 ax.set_yticklabels(labels, fontsize=7, color="#d1d5db")
136 ax.invert_yaxis()
137 ax.set_xlabel("Activation", color="#9ca3af", fontsize=8)
138 ax.set_title("Top SAE features", color="#e5e7eb", fontsize=10)
139 ax.tick_params(axis="x", colors="#6b7280", labelsize=7)
140 ax.grid(axis="x", alpha=0.2, color="#4b5563")
141
142
143def _plot_logit_lens(ax: Any, logit_lens: list[dict[str, Any]], trace_target: str) -> None:
144 if not logit_lens:
145 ax.axis("off")
146 ax.text(0.5, 0.5, "No logit lens data", ha="center", va="center", color="#9ca3af")
147 ax.set_title("Logit lens", color="#e5e7eb", fontsize=10)
148 return
149
150 layers = []
151 probs = []
152 top_token = trace_target or ""
153 for row in logit_lens:
154 layer = int(row.get("layer", len(layers)))
155 tops = row.get("top_tokens") or []
156 if not tops:
157 continue
158 best = tops[0]
159 layers.append(layer)
160 probs.append(float(best.get("prob") or 0))
161 if not top_token:
162 top_token = str(best.get("token") or "")
163
164 if not layers:
165 ax.axis("off")
166 ax.text(0.5, 0.5, "No logit lens data", ha="center", va="center", color="#9ca3af")
167 ax.set_title("Logit lens", color="#e5e7eb", fontsize=10)
168 return
169
170 ax.plot(layers, probs, color="#34d399", marker="o", markersize=3, linewidth=1.5)
171 ax.set_xlabel("Layer", color="#9ca3af", fontsize=8)
172 ax.set_ylabel("P(top token)", color="#9ca3af", fontsize=8)
173 title = f'Logit lens — top token {top_token!r}' if top_token else "Logit lens"
174 ax.set_title(title[:48], color="#e5e7eb", fontsize=9)
175 ax.tick_params(colors="#6b7280", labelsize=7)
176 ax.grid(alpha=0.2, color="#4b5563")
177
178
179def _plot_trace(ax: Any, trace_results: list[dict[str, Any]]) -> None:
180 if not trace_results:
181 ax.axis("off")
182 ax.text(0.5, 0.5, "No trace data", ha="center", va="center", color="#9ca3af")
183 ax.set_title("Layer probability delta", color="#e5e7eb", fontsize=10)
184 return
185
186 layers = [int(r.get("layer", i)) for i, r in enumerate(trace_results)]
187 drops = [float(r.get("drop") or 0) for r in trace_results]
188 ax.bar(layers, drops, color="#fbbf24", width=0.75, alpha=0.85)
189 ax.set_xlabel("Layer", color="#9ca3af", fontsize=8)
190 ax.set_ylabel("Δ prob", color="#9ca3af", fontsize=8)
191 ax.set_title("Layer probability delta", color="#e5e7eb", fontsize=10)
192 ax.tick_params(colors="#6b7280", labelsize=7)
193 ax.grid(axis="y", alpha=0.2, color="#4b5563")
194
195
196def _plot_summary(ax: Any, prompt: str, response: str, attribution: list[dict[str, Any]]) -> None:
197 ax.axis("off")
198 ax.set_title("Summary", color="#e5e7eb", fontsize=10, loc="left")
199
200 lines = [
201 f'Prompt: "{prompt}"',
202 "",
203 f'Response: "{response}"',
204 "",
205 f"Attribution: {len(attribution)} response tokens with driving features",
206 ]
207 if attribution:
208 lines.append("")
209 for a in attribution[:4]:
210 tok = a.get("response_token", "")
211 driven = a.get("driven_by_features") or []
212 refs = ", ".join(
213 str(d.get("feature_ref") or d.get("feature_idx", "?"))
214 for d in driven[:3]
215 )
216 lines.append(f" {tok!r} ← {refs}")
217
218 ax.text(
219 0.02,
220 0.96,
221 "\n".join(lines),
222 transform=ax.transAxes,
223 va="top",
224 ha="left",
225 fontsize=8,
226 color="#d1d5db",
227 family="monospace",
228 wrap=True,
229 )
None _plot_logit_lens(Any ax, list[dict[str, Any]] logit_lens, str trace_target)
None _plot_summary(Any ax, str prompt, str response, list[dict[str, Any]] attribution)
None _plot_trace_check(dict[str, Any] result, Path png_path)
None _plot_trace(Any ax, list[dict[str, Any]] trace_results)
dict[str, Any] _flatten_trace_result(dict[str, Any] result)
None _plot_top_features(Any ax, list[dict[str, Any]] features)
tuple[Path, Path] write_trace_check(dict[str, Any] result, *, str|None tool_name, str|Path cwd)