AQIT 0.1.0
Loading...
Searching...
No Matches
cli_output.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2# This file is part of the Aquin Engine. Unauthorized copying, modification,
3# distribution, or use of this file, via any medium, is strictly prohibited.
4# Proprietary and confidential. See LICENSE for terms.
5
6"""Rich terminal formatters for raw CLI tool results."""
7
8from __future__ import annotations
9
10from typing import Any
11
12from rich.console import Console
13from rich.panel import Panel
14from rich.rule import Rule
15from rich.table import Table
16from rich.text import Text
17from rich.theme import Theme
18
19_THEME = Theme({
20 "title": "bold #facc15",
21 "heading": "bold bright_white",
22 "label": "dim #9ca3af",
23 "value": "#e5e7eb",
24 "good": "#34d399",
25 "warn": "#fbbf24",
26 "bad": "#f87171",
27 "muted": "dim #6b7280",
28 "accent": "bold #34d399",
29})
30
31
32def _console() -> Console:
33 return Console(theme=_THEME, highlight=False)
34
35
36def _pct(score: float | None) -> str:
37 if score is None:
38 return "—"
39 return f"{round(score * 100)}%"
41
42def _num(v: float | None, digits: int = 3) -> str:
43 if v is None:
44 return "—"
45 return f"{v:.{digits}f}"
47
48def _trunc(text: str, limit: int = 72) -> str:
49 text = " ".join(text.split())
50 if len(text) <= limit:
51 return text
52 return text[: limit - 1] + "…"
53
54
55def _score_style(score: float) -> str:
56 if score >= 0.7:
57 return "good"
58 if score >= 0.4:
59 return "warn"
60 return "bad"
61
62
63def _kl_style(kl: float) -> str:
64 if kl < 0.3:
65 return "good"
66 if kl < 0.6:
67 return "warn"
68 return "bad"
69
70
71def _status_style(status: str) -> str:
72 if status == "suppressed":
73 return "bad"
74 if status == "softened":
75 return "warn"
76 return "muted"
77
78
79def _normalize_feature_benchmark(data: dict[str, Any]) -> dict[str, Any]:
80 """Unwrap stale {type, data} envelopes from removed build_benchmark tool."""
81 if data.get("type") in ("benchmark", "benchmarkSuite") and isinstance(data.get("data"), dict):
82 inner = data["data"]
83 if any(inner.get(k) is not None for k in ("score", "purity_score", "mui_score", "feature_idx")):
84 return inner
85 return data
86
87
88def _unwrap(result: Any) -> dict[str, Any]:
89 if not isinstance(result, dict):
90 return {"content": result}
91 if "content" in result and isinstance(result["content"], dict):
92 return _normalize_feature_benchmark(result["content"])
93 data = {k: v for k, v in result.items() if k not in ("card", "capture")}
95
96
97def _print_error(console: Console, message: str) -> None:
98 from aquin.user_errors import sanitize_error_string
99 console.print(
100 Panel(Text(sanitize_error_string(message), style="bad"), title="[bad]Error[/]", border_style="bad")
102
103
104def _print_consistency(console: Console, data: dict[str, Any]) -> None:
105 score = data.get("consistency_score") or 0.0
106 query = data.get("query", "")
107 console.print(Rule("[heading]Consistency[/]", style="muted"))
108 if query:
109 console.print(f"[label]query[/] [value]{query}[/]")
110 console.print(
111 Text.assemble(
112 ("score ", "label"),
113 (_pct(score), _score_style(score)),
114 (" mean KL ", "label"),
115 (_num(data.get("mean_kl")), "value"),
116 (" max KL ", "label"),
117 (_num(data.get("max_kl")), "value"),
118 (" min KL ", "label"),
119 (_num(data.get("min_kl")), "value"),
120 )
121 )
122
123 variants = data.get("variants") or []
124 if not variants:
125 return
126
127 table = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
128 table.add_column("Variant", style="value", no_wrap=True)
129 table.add_column("KL", justify="right")
130 table.add_column("Entropy", justify="right")
131 table.add_column("Top token", style="muted")
132
133 for i, v in enumerate(variants):
134 kl = 0.0 if i == 0 else (v.get("kl_from_anchor") or 0.0)
135 label = "anchor" if i == 0 else f"v{i}"
136 template = v.get("template") or v.get("prompt") or label
137 top = (v.get("top_tokens") or [{}])[0]
138 top_tok = top.get("token", "")
139 top_prob = top.get("prob")
140 top_str = f"{top_tok!r}"
141 if top_prob is not None:
142 top_str += f" ({top_prob:.1%})"
143 table.add_row(
144 _trunc(template, 42),
145 Text(_num(kl), style=_kl_style(kl)),
146 _num(v.get("entropy"), 2),
147 top_str,
148 )
149 console.print(table)
150
151
152def _print_suppression(console: Console, data: dict[str, Any]) -> None:
153 baseline = data.get("baseline") or {}
154 topics = data.get("topics") or []
155 console.print(Rule("[heading]Suppression[/]", style="muted"))
156 if baseline:
157 console.print(
158 Text.assemble(
159 ("baseline length ", "label"),
160 (_num(baseline.get("mean_length"), 1), "value"),
161 (" hedge ", "label"),
162 (_num(baseline.get("mean_hedge"), 2), "value"),
163 (f" {len(topics)} topics", "muted"),
164 )
165 )
166
167 if not topics:
168 return
169
170 table = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
171 table.add_column("Topic", style="value")
172 table.add_column("Status")
173 table.add_column("Len×", justify="right")
174 table.add_column("Hedge×", justify="right")
175 table.add_column("Score", justify="right")
176
177 for t in topics:
178 status = t.get("status", "unfiltered")
179 table.add_row(
180 t.get("topic", ""),
181 Text(status, style=_status_style(status)),
182 _num(t.get("length_ratio"), 2),
183 _num(t.get("hedge_ratio"), 2),
184 _pct(t.get("suppression_score")),
185 )
186 console.print(table)
187
188 for t in topics:
189 probes = t.get("probes") or []
190 if not probes:
191 continue
192 p = probes[0]
193 console.print(
194 f"[label]{t.get('topic', 'topic')}[/] "
195 f"[muted]{_trunc(p.get('prompt', ''), 56)}[/]"
196 )
197 if p.get("response"):
198 console.print(f" [muted]{_trunc(p['response'], 88)}[/]")
199
200
201def _print_custom_eval(console: Console, data: dict[str, Any]) -> None:
202 from rich.table import Table
203
204 console.print(Rule("eval", style="dim"))
205 summary = Table(show_header=False, box=None, padding=(0, 2))
206 summary.add_row("[label]mean[/]", f"{data.get('mean_score', 0) * 100:.0f}%")
207 summary.add_row("[label]passed[/]", f"{data.get('pass_rate', 0) * 100:.0f}%")
208 summary.add_row("[label]threshold[/]", str(data.get("threshold", "—")))
209 console.print(summary)
210
211 prompts = data.get("prompts") or []
212 if prompts:
213 console.print(Rule("Results", style="dim"))
214 table = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
215 table.add_column("prompt", max_width=40, overflow="fold")
216 table.add_column("response", max_width=40, overflow="fold")
217 table.add_column("score", justify="right")
218 table.add_column("pass", justify="center")
219 for row in prompts:
220 score = row.get("score")
221 score_s = f"{score * 100:.0f}%" if isinstance(score, (int, float)) else "—"
222 passed = "✓" if row.get("passed") else "✗"
223 table.add_row(
224 str(row.get("prompt", "")),
225 str(row.get("response", "")),
226 score_s,
227 passed,
228 )
229 console.print(table)
230
231
232def _print_boundary(console: Console, data: dict[str, Any]) -> None:
233 mean_rob = data.get("mean_robustness") or 0.0
234 probes = data.get("probes") or []
235 console.print(Rule("[heading]Boundary[/]", style="muted"))
236 console.print(
237 Text.assemble(
238 ("robustness ", "label"),
239 (_pct(mean_rob), _score_style(mean_rob)),
240 (f" {len(probes)} probe{'s' if len(probes) != 1 else ''}", "muted"),
241 )
242 )
243
244 if not probes:
245 return
246
247 table = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
248 table.add_column("Prompt", style="value")
249 table.add_column("Robust", justify="right")
250 table.add_column("Conf", justify="right")
251 table.add_column("Drop", justify="right")
252 table.add_column("KL", justify="right")
253
254 for p in probes:
255 rob = p.get("robustness_score") or 0.0
256 table.add_row(
257 _trunc(p.get("prompt", ""), 36),
258 Text(_pct(rob), style=_score_style(rob)),
259 _pct(p.get("clean_confidence")),
260 _pct(p.get("mean_confidence_drop")),
261 _num(p.get("mean_kl")),
262 )
263 console.print(table)
264
265
266def _print_red_team(console: Console, data: dict[str, Any]) -> None:
267 if data.get("error"):
268 _print_error(console, str(data["error"]))
269 return
271 console.print(Rule("red team", style="dim"))
272 summary = Table(show_header=False, box=None, padding=(0, 2))
273 summary.add_row("[label]composite[/]", f"{data.get('composite_score', 0) * 100:.0f}%")
274 summary.add_row("[label]vectors[/]", str(len(data.get("vectors") or [])))
275 console.print(summary)
276
277 vectors = data.get("vectors") or []
278 if vectors:
279 console.print(Rule("Vectors", style="dim"))
280 table = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
281 table.add_column("vector")
282 table.add_column("score", justify="right")
283 table.add_column("status", justify="center")
284 table.add_column("detail", overflow="fold")
285 for row in vectors:
286 score = row.get("score")
287 score_s = f"{score * 100:.0f}%" if isinstance(score, (int, float)) else "—"
288 table.add_row(
289 str(row.get("label", row.get("id", ""))),
290 score_s,
291 str(row.get("status", "")),
292 str(row.get("detail", "")),
293 )
294 console.print(table)
295
296
297def _print_find_feature(console: Console, data: dict[str, Any]) -> None:
298 if data.get("error"):
299 console.print(f"[red]Error:[/] {data['error']}")
300 return
301 console.print(f"[bold]model[/] {data.get('model_id', '—')}")
302 console.print(f"[bold]layer[/] {data.get('layer', '—')}")
303 console.print(f"[bold]scorer[/] {data.get('scorer', '—')}")
304 if data.get("direction"):
305 console.print(f"[bold]direction[/] {data.get('direction')}")
306 if data.get("conditioning"):
307 console.print(f"[bold]condition[/] {data.get('conditioning')}")
308 behavior = data.get("behavior")
309 if isinstance(behavior, dict):
310 console.print(
311 f"[bold]behavior[/] {behavior.get('n_truthful', '—')} truthful · "
312 f"{behavior.get('n_deceptive', '—')} deceptive · "
313 f"{behavior.get('n_ambiguous', '—')} ambiguous "
314 f"({behavior.get('n_generated', '—')} generated)"
315 )
316 console.print(
317 f"[bold]probes[/] {data.get('n_honest', '—')} honest · "
318 f"{data.get('n_deceptive', '—')} deceptive"
319 )
320 chosen = data.get("chosen_feature_idx")
321 if chosen is not None:
322 console.print(f"\n[bold green]chosen[/] feature {chosen} Δ={data.get('chosen_delta')}")
323 if data.get("persisted_key"):
324 console.print(f"[bold]persisted[/] {data.get('persisted_key')} → {data.get('experiment_path')}")
325 if data.get("warning"):
326 console.print(f"\n[yellow]warning[/] {data.get('warning')}")
327 rankings = data.get("rankings") or []
328 if rankings:
329 console.print(f"\n[bold]top features[/]")
330 for i, row in enumerate(rankings[:10], 1):
331 interp = row.get("interp_score")
332 extra = f" interp={interp:.2f}" if interp is not None else ""
333 console.print(
334 f" {i:2}. f{row['feature_idx']:<5} "
335 f"honest={row['honest_mean']:.4f} deceptive={row['deceptive_mean']:.4f} "
336 f"Δ={row['delta']:+.4f}{extra}"
337 )
338
339
340def _print_extract_steer_vector(console: Console, data: dict[str, Any]) -> None:
341 if data.get("error"):
342 console.print(f"[red]Error:[/] {data['error']}")
343 return
344 console.print(f"[bold]saved[/] {data.get('output_path', '—')}")
345 console.print(f"[bold]model[/] {data.get('model_id', '—')}")
346 console.print(f"[bold]layer[/] {data.get('layer', '—')}")
347 console.print(f"[bold]feature[/] {data.get('feature_idx', '—')} {data.get('feature_label', '')}")
348 if data.get("probe_id"):
349 console.print(f"[bold]probe[/] {data.get('probe_id')}")
350 console.print(f"[bold]d_model[/] {data.get('d_model', '—')}")
351 console.print(f"[bold]norm[/] {data.get('vector_l2_norm', '-')}")
352 console.print("\n[dim]Apply with:[/] aquin steer --prompt \"...\" --vector <path> [--strength 20]")
353
354
355def _print_steer(console: Console, data: dict[str, Any]) -> None:
356 if data.get("error"):
357 console.print(f"[red]Error:[/] {data['error']}")
358 return
359 label = data.get("feature_label") or data.get("feature_ref") or f"F{data.get('feature_idx', '?')}"
360 console.print(f"[bold]{label}[/] layer {data.get('layer', '—')} strength {data.get('steer_strength', '—')}")
361 if data.get("vector_path"):
362 console.print(f"[dim]vector[/] {data.get('vector_path')}")
363 if data.get("vector_source_model_id"):
364 console.print(
365 f"[dim]source[/] {data.get('vector_source_model_id')} "
366 f"L{data.get('vector_source_layer')} f{data.get('vector_source_feature_idx')}"
367 )
368
369 eval_data = data.get("eval") if isinstance(data.get("eval"), dict) else None
370 if eval_data:
371 _print_steer_eval(console, eval_data)
372
373 if data.get("demo") and (data.get("prompt") or data.get("baseline_response") or data.get("steered_response")):
374 console.print(f"\n[bold]prompt[/] {data.get('prompt', '')}")
375 console.print("\n[bold]baseline[/]")
376 console.print(str(data.get("baseline_response") or data.get("baseline") or ""))
377 console.print("\n[bold]steered[/]")
378 console.print(str(data.get("steered_response") or data.get("steered") or ""))
379 elif not eval_data:
380 console.print(f"\n[bold]prompt[/] {data.get('prompt', '')}")
381 console.print("\n[bold]baseline[/]")
382 console.print(str(data.get("baseline_response") or data.get("baseline") or ""))
383 console.print("\n[bold]steered[/]")
384 console.print(str(data.get("steered_response") or data.get("steered") or ""))
385
386
387def _print_steer_eval(console: Console, eval_data: dict[str, Any]) -> None:
388 mode = eval_data.get("mode", "—")
389 n = eval_data.get("n_probes", 0)
390 base = eval_data.get("baseline") or {}
391 steered = eval_data.get("steered") or {}
392 console.print(f"\n[bold]eval[/] {mode} · {n} probe(s)")
393 if eval_data.get("prompts_path"):
394 console.print(f"[dim]probes[/] {eval_data['prompts_path']}")
395 if eval_data.get("pass_means"):
396 console.print(f"[dim]pass[/] {eval_data['pass_means']}")
397
398 def _pct(rate: Any) -> str:
399 try:
400 return f"{100.0 * float(rate):.1f}%"
401 except (TypeError, ValueError):
402 return "—"
403
404 console.print(
405 f"[bold]baseline[/] pass {_pct(base.get('pass_rate'))} "
406 f"({base.get('n_passed', '—')}/{base.get('n', n)})"
407 )
408 if mode == "behavior":
409 console.print(
410 f"[dim] [/]truthful {_pct(base.get('truthful_rate'))} "
411 f"deceptive {_pct(base.get('deceptive_rate'))} "
412 f"ambiguous {_pct(base.get('ambiguous_rate'))}"
413 )
414 elif base.get("mean_score") is not None:
415 console.print(f"[dim] [/]mean score {base.get('mean_score')}")
416
417 console.print(
418 f"[bold]steered[/] pass {_pct(steered.get('pass_rate'))} "
419 f"({steered.get('n_passed', '—')}/{steered.get('n', n)})"
420 )
421 if mode == "behavior":
422 console.print(
423 f"[dim] [/]truthful {_pct(steered.get('truthful_rate'))} "
424 f"deceptive {_pct(steered.get('deceptive_rate'))} "
425 f"ambiguous {_pct(steered.get('ambiguous_rate'))}"
426 )
427 elif steered.get("mean_score") is not None:
428 console.print(f"[dim] [/]mean score {steered.get('mean_score')}")
429
430 delta = eval_data.get("delta_pass_rate")
431 if delta is not None:
432 sign = "+" if float(delta) >= 0 else ""
433 console.print(f"[bold]Δ pass[/] {sign}{_pct(delta)}")
434 if eval_data.get("n_changed") is not None:
435 console.print(f"[dim]changed[/] {eval_data['n_changed']}/{n} responses differ")
436
437 probes = eval_data.get("probes") or []
438 if probes:
439 console.print("\n[bold]probes[/]")
440 for row in probes[:12]:
441 pid = row.get("probe_id", "?")
442 b = row.get("baseline") or {}
443 s = row.get("steered") or {}
444 if mode == "behavior":
445 console.print(
446 f" [{pid}] {b.get('behavior', '?')} → {s.get('behavior', '?')}"
447 f"{' · changed' if row.get('changed') else ''}"
448 )
449 else:
450 bp = "pass" if b.get("passed") else "fail"
451 sp = "pass" if s.get("passed") else "fail"
452 console.print(
453 f" [{pid}] {bp} ({b.get('score', '—')}) → {sp} ({s.get('score', '—')})"
454 f"{' · changed' if row.get('changed') else ''}"
455 )
456 if len(probes) > 12:
457 console.print(f" [dim]… {len(probes) - 12} more[/]")
458
459
460def _print_benchmarks(console: Console, data: dict[str, Any]) -> None:
461 if data.get("error"):
462 _print_error(console, str(data["error"]))
463 return
465 has_scores = any(data.get(k) is not None for k in ("score", "purity_score", "mui_score"))
466 if not has_scores and not data.get("feature_idx"):
468 console,
469 "No benchmark scores in result. Reinstall/pull latest CLI — "
470 "`aquin benchmark` runs InterpScore + Purity + MUI (needs model + SAE loaded).",
471 )
472 return
473
474 feature_ref = data.get("feature_ref") or data.get("feature_idx")
475 model_id = data.get("model_id", "")
476 label = data.get("label") or "—"
477 console.print(Rule("[heading]Benchmark[/]", style="muted"))
478 console.print(
479 Text.assemble(
480 ("feature ", "label"),
481 (str(feature_ref), "accent"),
482 (" model ", "label"),
483 (model_id, "value"),
484 )
485 )
486 if label and label != "—":
487 console.print(f"[heading]{label}[/]")
488
489 scores = [
490 ("InterpScore", data.get("score")),
491 ("Purity", data.get("purity_score")),
492 ("MUI", data.get("mui_score")),
493 ]
494 table = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
495 table.add_column("Metric", style="value")
496 table.add_column("Score", justify="right")
497 for name, val in scores:
498 if val is None:
499 continue
500 table.add_row(name, Text(_pct(val), style=_score_style(val)))
501 console.print(table)
502
503 if data.get("positive_mean") is not None or data.get("negative_mean") is not None:
504 console.print(
505 Text.assemble(
506 ("fires on ", "label"),
507 (_num(data.get("positive_mean")), "value"),
508 (" silent on ", "label"),
509 (_num(data.get("negative_mean")), "muted"),
510 )
511 )
512
513 pos = data.get("positive_examples") or []
514 if pos:
515 console.print("[label]positive examples[/]")
516 for ex in pos[:3]:
517 console.print(
518 f" [value]{_trunc(ex.get('sentence', ''), 64)}[/] "
519 f"[muted]{_num(ex.get('activation'), 2)}[/]"
520 )
521
522
523def _status_style(status: str) -> str:
524 if status == "dead":
525 return "bad"
526 if status == "collapsed":
527 return "warn"
528 return "good"
529
530
532 console: Console,
533 title: str,
534 rows: list[dict[str, Any]],
535 columns: list[tuple[str, str]],
536) -> None:
537 if not rows:
538 return
539 if title:
540 console.print(Rule(f"[heading]{title}[/]", style="muted"))
541 table = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
542 for header, _ in columns:
543 table.add_column(header, justify="right" if header != columns[0][0] else "left")
544 for row in rows:
545 cells = []
546 for i, (_, key) in enumerate(columns):
547 val = row.get(key)
548 if key == "status" and isinstance(val, str):
549 cells.append(Text(val, style=_status_style(val)))
550 elif isinstance(val, float) and "ratio" in key:
551 cells.append(f"{val * 100:.1f}%")
552 elif isinstance(val, float):
553 cells.append(_num(val, 4))
554 else:
555 cells.append(str(val) if val is not None else "—")
556 table.add_row(*cells)
557 console.print(table)
558
559
560def _is_llm_layer_analysis_payload(data: dict[str, Any]) -> bool:
561 """LLM layer-analysis payload detection."""
562 if data.get("stability"):
563 return True
564 ood = data.get("ood")
565 return isinstance(ood, dict) and (
566 "layers" in ood or "n_in_prompts" in ood or "mean_separation" in ood
567 )
568
569
570def _vec_norm(vec: list[float]) -> float:
571 return float(sum(x * x for x in vec) ** 0.5)
572
573
574def _layer_cosine(a: list[float], b: list[float]) -> float:
575 dot = sum(x * y for x, y in zip(a, b))
576 na = _vec_norm(a)
577 nb = _vec_norm(b)
578 return dot / (na * nb + 1e-9)
579
580
581
582
583
584
585
586
587
588def _normalize_attn_matrix(matrix: list[list[float]]) -> list[list[float]]:
589 return [[v / (sum(row) or 1.0) for v in row] for row in matrix]
590
591
593
594
595
596def _print_layer_analysis(console: Console, data: dict[str, Any]) -> None:
597 stability = data.get("stability") or {}
598 ood = data.get("ood") or {}
599 localize = data.get("localize") if isinstance(data.get("localize"), dict) else None
601 if stability:
602 if stability.get("padded_defaults"):
603 console.print(
604 "[dim]Stability PCA needs ≥2 prompts — padded with default probes "
605 f"({stability.get('n_user_prompts', 1)} user + defaults).[/]",
606 )
607 console.print(
608 Text.assemble(
609 ("prompts ", "label"),
610 (str(stability.get("n_prompts", "—")), "value"),
611 (" dead ", "label"),
612 (str(stability.get("dead_count", 0)), "bad" if stability.get("dead_count") else "good"),
613 (" collapsed ", "label"),
614 (str(stability.get("collapsed_count", 0)), "warn" if stability.get("collapsed_count") else "good"),
615 )
616 )
618 console,
619 "Activation stability",
620 stability.get("layers") or [],
621 [
622 ("Layer", "layer"),
623 ("PC1%", "top1_variance_ratio"),
624 (f"Top-{stability.get('top_k', 10)}%", "topk_variance_ratio"),
625 ("Std", "activation_std"),
626 ("Status", "status"),
627 ],
628 )
629
630 if ood:
631 mean_sep = ood.get("mean_separation") or 0.0
632 sep_style = "good" if mean_sep >= 0.6 else "warn" if mean_sep >= 0.35 else "bad"
633 console.print(
634 Text.assemble(
635 ("in-domain ", "label"),
636 (str(ood.get("n_in_prompts", "—")), "value"),
637 (" ood ", "label"),
638 (str(ood.get("n_ood_prompts", "—")), "value"),
639 (" mean sep ", "label"),
640 (_num(mean_sep, 4), sep_style),
641 (" peak layer ", "label"),
642 (str(ood.get("peak_layer", "—")), "accent"),
643 )
644 )
646 console,
647 "OOD similarity",
648 ood.get("layers") or [],
649 [
650 ("Layer", "layer"),
651 ("In-cos", "in_cos"),
652 ("OOD-cos", "ood_cos"),
653 ("Cross", "cross_cos"),
654 ("Sep", "separation"),
655 ("MMD", "mmd"),
656 ],
657 )
658
659 if localize:
660 _print_localize_collapse(console, localize)
661
662
663def _print_localize_collapse(console: Console, data: dict[str, Any]) -> None:
664 if data.get("error") and not data.get("layers"):
665 console.print(f"[red]localize:[/] {data['error']}")
666 return
668 console.print(
669 Text.assemble(
670 ("\nlocalize ", "label"),
671 (str(data.get("mode", "contrastive")), "accent"),
672 (" honest ", "label"),
673 (str(data.get("n_honest", "—")), "value"),
674 (" deceptive ", "label"),
675 (str(data.get("n_deceptive", "—")), "value"),
676 )
677 )
678 if data.get("prompts_path"):
679 console.print(f"[dim]probes[/] {data['prompts_path']}")
680 console.print(
681 Text.assemble(
682 ("peak ", "label"),
683 (str(data.get("peak_layer", "—")), "good"),
684 (" collapse ", "label"),
685 (str(data.get("collapse_layer", "—")), "bad"),
686 (" drop ", "label"),
687 (_num(data.get("drop_from_peak"), 4), "warn"),
688 (" weak/collapsed ", "label"),
689 (f"{data.get('n_weak', 0)}/{data.get('n_collapsed', 0)}", "value"),
690 )
691 )
692
694 console,
695 "Deception signal by layer",
696 data.get("layers") or [],
697 [
698 ("Layer", "layer"),
699 ("Signal", "signal"),
700 ("L2 sep", "l2_sep"),
701 ("Cos sep", "cos_sep"),
702 ("Status", "status"),
703 ],
704 )
705
706 stress = data.get("stressor") if isinstance(data.get("stressor"), dict) else None
707 if stress:
708 console.print(
709 Text.assemble(
710 ("stressor max collapse ", "label"),
711 (f"L{stress.get('max_collapse_layer', '—')}", "bad"),
712 (" Δ ", "label"),
713 (_num(stress.get("max_collapse_delta"), 4), "warn"),
714 )
715 )
717 console,
718 "Stressor collapse",
719 stress.get("layers") or [],
720 [
721 ("Layer", "layer"),
722 ("Baseline", "baseline_signal"),
723 ("Stressor", "stressor_signal"),
724 ("Δ collapse", "collapse_delta"),
725 ("Status", "status"),
726 ],
727 )
728
729 direction = data.get("direction") if isinstance(data.get("direction"), dict) else None
730 if direction:
731 console.print(
732 Text.assemble(
733 ("direction ", "label"),
734 (str(direction.get("source", "—")), "value"),
735 (" layer ", "label"),
736 (str(direction.get("layer", "—")), "accent"),
737 (" Δ proj ", "label"),
738 (_num(direction.get("proj_delta"), 4), "value"),
739 )
740 )
741
742 feats = data.get("features_at_collapse") or []
743 if feats:
744 console.print(
745 f"[dim]top features at collapse layer L{data.get('features_layer', data.get('collapse_layer'))}[/]"
746 )
747 for row in feats[:8]:
748 console.print(
749 f" [{row.get('feature_idx', '?')}] "
750 f"Δ={row.get('delta', 0):+.4f} "
751 f"honest={row.get('honest_mean', 0):.4f} "
752 f"deceptive={row.get('deceptive_mean', 0):.4f}"
753 )
754
755 if data.get("error"):
756 console.print(f"[yellow]localize note:[/] {data['error']}")
757
758
759def _print_weight_diff(console: Console, data: dict[str, Any]) -> None:
760 console.print(Rule("[heading]Weight checkpoint diff[/]", style="muted"))
761 header_parts: list[tuple[str, str]] = [
762 ("model ", "label"),
763 (str(data.get("baseModelId", "—")), "value"),
764 (" checkpoint ", "label"),
765 (str(data.get("ftCheckpointName", "—")), "accent"),
766 ]
767 if data.get("modelMode"):
768 header_parts.extend(
769 [
770 (" mode ", "label"),
771 (str(data.get("modelMode", "—")), "value"),
772 ]
773 )
774 if data.get("deltaMode"):
775 header_parts.extend(
776 [
777 (" Δ mode ", "label"),
778 (str(data.get("deltaMode", "—")), "value"),
779 ]
780 )
781 console.print(Text.assemble(*header_parts))
782
783 verdict = data.get("mergeVerdict")
784 if verdict:
785 verdict_s = str(verdict).upper()
786 verdict_style = {"PASS": "green", "WARN": "yellow", "FAIL": "red"}.get(verdict_s, "value")
787 console.print(Text.assemble(("merge verdict ", "label"), (verdict_s, verdict_style)))
788
789 console.print(
790 Text.assemble(
791 ("matrices ", "label"),
792 (str(data.get("nMatrices", "—")), "value"),
793 (" total ‖Δ‖ ", "label"),
794 (_num(data.get("totalDeltaL2"), 6), "value"),
795 (" max ‖Δ‖ ", "label"),
796 (_num(data.get("maxDeltaL2"), 6), "value"),
797 (" mean stable rank ", "label"),
798 (_num(data.get("meanDeltaStableRank"), 4), "value"),
799 )
800 )
801 for w in data.get("warnings") or []:
802 console.print(f"[yellow]⚠ {w}[/]")
803 if data.get("behavioralError"):
804 console.print(f"[muted]behavioral diff skipped: {data['behavioralError']}[/]")
805 behavioral = data.get("behavioralDiff") or {}
806 if behavioral:
807 console.print(
808 Text.assemble(
809 ("behavioral ", "label"),
810 ("consistency ", "label"),
811 (_num(behavioral.get("consistencyScore"), 4), "value"),
812 (" robustness ", "label"),
813 (_num(behavioral.get("robustnessScore"), 4), "value"),
814 )
815 )
816 if data.get("saved_to"):
817 console.print(f"[muted]saved → {data['saved_to']}[/]")
818
820 console,
821 "Layer profile",
822 data.get("layerProfile") or [],
823 [("Layer", "layer"), ("‖Δ‖", "delta_l2"), ("N", "n_matrices")],
824 )
825 collapse = data.get("collapseSignals") or []
826 if collapse:
828 console,
829 "Rank / collapse signals",
830 collapse,
831 [
832 ("Layer", "layer"),
833 ("Matrix", "matrix"),
834 ("‖Δ‖", "deltaL2"),
835 ("Stable rank", "deltaStableRank"),
836 ],
837 )
839 console,
840 "Top changed matrices",
841 data.get("topChanged") or data.get("matrices") or [],
842 [
843 ("Layer", "layer"),
844 ("Matrix", "matrix"),
845 ("‖Δ‖", "delta_l2"),
846 ("Rel", "delta_l2_relative"),
847 ("Stable rank", "delta_stable_rank"),
848 ],
849 )
850
851
852def _print_merge_analysis(console: Console, data: dict[str, Any]) -> None:
853 _print_weight_diff(console, data)
854
855
856def _print_trajectory_analysis(console: Console, data: dict[str, Any]) -> None:
857 console.print(Rule("[heading]Checkpoint trajectory[/]", style="muted"))
858 console.print(
859 Text.assemble(
860 ("model ", "label"),
861 (str(data.get("baseModelId", "—")), "value"),
862 (" checkpoints ", "label"),
863 (str(data.get("nCheckpoints", "—")), "value"),
864 (" analyzed ", "label"),
865 (str(data.get("nAnalyzed", "—")), "value"),
866 )
867 )
868 if data.get("peakStep") is not None:
869 console.print(
870 Text.assemble(
871 ("peak step ", "label"),
872 (str(data.get("peakStep")), "value"),
873 (" peak total ‖Δ‖ ", "label"),
874 (_num(data.get("peakTotalDeltaL2"), 6), "value"),
875 )
876 )
877 if data.get("saved_to"):
878 console.print(f"[muted]saved → {data['saved_to']}[/]")
879 rows = []
880 for step in data.get("steps") or []:
881 if step.get("error"):
882 rows.append(
883 {
884 "step": step.get("step", "—"),
885 "name": step.get("name", "—"),
886 "totalDeltaL2": f"error: {step.get('error')}",
887 "deltaFromPrevious": "—",
888 }
889 )
890 else:
891 rows.append(
892 {
893 "step": step.get("step", "—"),
894 "name": step.get("name", "—"),
895 "totalDeltaL2": step.get("totalDeltaL2"),
896 "deltaFromPrevious": step.get("deltaFromPrevious"),
897 }
898 )
900 console,
901 "Steps",
902 rows,
903 [
904 ("Step", "step"),
905 ("Name", "name"),
906 ("Total ‖Δ‖", "totalDeltaL2"),
907 ("Δ from prev", "deltaFromPrevious"),
908 ],
909 )
910
911
912def _print_residual_drift(console: Console, data: dict[str, Any]) -> None:
913 console.print(Rule("[heading]Residual drift (base vs FT)[/]", style="muted"))
914 console.print(
915 Text.assemble(
916 ("model ", "label"),
917 (str(data.get("baseModelId", "—")), "value"),
918 (" checkpoint ", "label"),
919 (str(data.get("ftCheckpointName", "—")), "accent"),
920 (" mode ", "label"),
921 (str(data.get("modelMode", "—")), "value"),
922 (" activation ", "label"),
923 (str(data.get("activationMode", "—")), "value"),
924 )
925 )
926 console.print(
927 Text.assemble(
928 ("probes ", "label"),
929 (str(data.get("nProbes", "—")), "value"),
930 (" layers ", "label"),
931 (str(data.get("nLayers", "—")), "value"),
932 (" mean drift ", "label"),
933 (_num(data.get("meanDrift"), 6), "value"),
934 (" max drift ", "label"),
935 (_num(data.get("maxDrift"), 6), "value"),
936 (" peak layer ", "label"),
937 (str(data.get("peakLayer", "—")), "accent"),
938 )
939 )
940 if data.get("saved_to"):
941 console.print(f"[muted]saved → {data['saved_to']}[/]")
942
944 console,
945 "Layer profile",
946 data.get("layerProfile") or [],
947 [
948 ("Layer", "layer"),
949 ("Mean dist", "mean_cosine_distance"),
950 ("Max dist", "max_cosine_distance"),
951 ("Mean sim", "mean_cosine_sim"),
952 ],
953 )
955 console,
956 "Top drift layers",
957 data.get("topLayers") or [],
958 [("Layer", "layer"), ("Mean dist", "mean_cosine_distance")],
959 )
961 console,
962 "Per-probe drift",
963 data.get("perProbe") or [],
964 [
965 ("#", "probe_index"),
966 ("Preview", "probe_preview"),
967 ("Mean", "mean_drift"),
968 ("Max", "max_drift"),
969 ("Peak L", "peak_layer"),
970 ],
971 )
972
973
974def _print_confidence_analysis(console: Console, data: dict[str, Any]) -> None:
975 console.print(
976 Text.assemble(
977 ("model ", "label"),
978 (str(data.get("model_id", "—")), "value"),
979 (" mode ", "label"),
980 (str(data.get("mode", "llm")), "accent"),
981 (" probes ", "label"),
982 (str(data.get("n_probes", "—")), "value"),
983 (" mean conf ", "label"),
984 (_num(data.get("mean_confidence"), 4), "value"),
985 (" ECE proxy ", "label"),
986 (_num(data.get("aggregate_ece_proxy"), 4), "value"),
987 (" low-conf ", "label"),
988 (str(data.get("low_confidence_count", "—")), "accent"),
989 )
990 )
991 if data.get("join_sae"):
992 console.print(f"[muted]SAE join layer {data.get('sae_layer')}[/]")
993 if data.get("saved_to"):
994 console.print(f"[muted]saved → {data['saved_to']}[/]")
995
997 console,
998 "Stressor summary",
999 data.get("stressor_summary") or [],
1000 [
1001 ("Stressor", "stressor"),
1002 ("N", "n_probes"),
1003 ("Mean conf", "mean_confidence"),
1004 ("Entropy", "mean_entropy"),
1005 ("ECE", "ece_proxy"),
1006 ("Δ conf", "confidence_delta"),
1007 ("Low-conf", "low_confidence_count"),
1008 ],
1009 )
1010
1011 cols = [
1012 ("ID", "id"),
1013 ("Stressor", "stressor"),
1014 ("Conf", "mean_confidence"),
1015 ("Max P", "max_prob"),
1016 ("Entropy", "entropy"),
1017 ("ECE", "ece_proxy"),
1018 ]
1019 if data.get("join_sae"):
1020 cols.extend([("L0", "mean_l0"), ("Top feat", "top_feature_idx")])
1021
1023 console,
1024 "Per-probe metrics",
1025 data.get("probes") or [],
1026 cols,
1027 )
1028
1029
1030def _print_sae_stats(console: Console, data: dict[str, Any]) -> None:
1031 console.print(Rule("[heading]SAE layer statistics[/]", style="muted"))
1032 console.print(
1033 Text.assemble(
1034 ("model ", "label"),
1035 (str(data.get("model_id", "—")), "value"),
1036 (" mode ", "label"),
1037 (str(data.get("mode", "—")), "accent"),
1038 (" probes ", "label"),
1039 (str(data.get("n_probes", "—")), "value"),
1040 (" layers ", "label"),
1041 (str(data.get("layers_requested") or "—"), "value"),
1042 )
1043 )
1044 if data.get("saved_to"):
1045 console.print(f"[muted]saved → {data['saved_to']}[/]")
1046
1048 console,
1049 "Layer profile (mean L0 across probes)",
1050 data.get("layer_profile") or [],
1051 [
1052 ("Layer", "layer"),
1053 ("Mean L0", "mean_l0"),
1054 ("Sparsity", "sparsity"),
1055 ("Mean act", "mean_activation"),
1056 ],
1057 )
1058
1059 for layer_entry in data.get("layer_stats") or []:
1060 if not layer_entry.get("sae_available"):
1061 console.print(f"[warn]Layer {layer_entry.get('layer')}: {layer_entry.get('error', 'no SAE')}[/]")
1062 continue
1063 layer = layer_entry.get("layer")
1064 top = layer_entry.get("top_features") or []
1065 if not top:
1066 continue
1067 console.print(Rule(f"[heading]Layer {layer} — top features[/]", style="muted"))
1069 console,
1070 None,
1071 top,
1072 [("Feature", "feature_idx"), ("Mean act", "mean_activation")],
1073 )
1074
1075
1076def _print_perturbation(console: Console, data: dict[str, Any]) -> None:
1077 raw = data.get("channels") or data.get("perturbation_results") or []
1078 rows = [
1079 {
1080 "channel": r.get("channel"),
1081 "kl": r.get("kl_mean", r.get("kl_divergence")),
1082 }
1083 for r in raw
1084 ]
1085 console.print(
1086 Text.assemble(
1087 ("layer ", "label"),
1088 (str(data.get("layer", "—")), "value"),
1089 (" channels tested ", "label"),
1090 (str(len(rows)), "value"),
1091 (" mean KL ", "label"),
1092 (_num(data.get("global_mean_kl"), 4), "value"),
1093 (" max KL ", "label"),
1094 (_num(data.get("global_max_kl"), 4), "value"),
1095 )
1096 )
1098 console,
1099 "Channel sensitivity (KL)",
1100 rows[:20],
1101 [
1102 ("Channel", "channel"),
1103 ("KL", "kl"),
1104 ],
1105 )
1106 if len(rows) > 20:
1107 console.print(f"[muted] … {len(rows) - 20} more channels (use --check)[/]")
1108
1109
1110def _print_attention(console: Console, data: dict[str, Any]) -> None:
1111 heads = data.get("heads") or []
1112 top_sink = data.get("top_sink_heads") or []
1113 top_ind = data.get("top_induction_heads") or []
1114 console.print(
1115 Text.assemble(
1116 ("prompt ", "label"),
1117 (_trunc(data.get("prompt", ""), 48), "value"),
1118 (" layers ", "label"),
1119 (str(data.get("n_layers", "—")), "value"),
1120 (" heads/layer ", "label"),
1121 (str(data.get("n_heads", "—")), "value"),
1122 )
1123 )
1124 if top_sink:
1125 console.print(Rule("[heading]Top sink heads[/]", style="muted"))
1127 console,
1128 "",
1129 top_sink,
1130 [
1131 ("Layer", "layer"),
1132 ("Head", "head"),
1133 ("Sink", "sink_score"),
1134 ("Induction", "induction_score"),
1135 ],
1136 )
1137 n_ind_pairs = data.get("n_induction_pairs", 0)
1138 if n_ind_pairs == 0:
1139 console.print(
1140 "[muted]Induction: 0 — no repeated tokens in prompt "
1141 "(try e.g. \"The cat sat. The cat\")[/]"
1142 )
1143 elif top_ind:
1144 console.print(Rule("[heading]Top induction heads[/]", style="muted"))
1146 console,
1147 "",
1148 top_ind,
1149 [
1150 ("Layer", "layer"),
1151 ("Head", "head"),
1152 ("Sink", "sink_score"),
1153 ("Induction", "induction_score"),
1154 ],
1155 )
1156 if not top_sink and not top_ind and heads:
1157 console.print(f"[muted]{len(heads)} heads scored (use --check)[/]")
1158
1159
1160def _print_check_weights(console: Console, data: dict[str, Any]) -> None:
1161 verdict = str(data.get("verdict", "—"))
1162 verdict_style = {
1163 "clean": "ok",
1164 "suspicious": "warn",
1165 "high_risk": "bad",
1166 }.get(verdict, "value")
1167 console.print(
1168 Text.assemble(
1169 ("verdict ", "label"),
1170 (verdict, verdict_style),
1171 (" composite risk ", "label"),
1172 (f"{float(data.get('composite_risk', 0)):.1%}", "value"),
1173 (" flagged ", "label"),
1174 (f"{data.get('pct_flagged', 0)}%", "value"),
1175 )
1176 )
1177 console.print(
1178 Text.assemble(
1179 ("layers ", "label"),
1180 (str(data.get("layers_analysed", "—")), "value"),
1181 (" high ", "label"),
1182 (str(data.get("high_risk_count", 0)), "bad"),
1183 (" suspicious ", "label"),
1184 (str(data.get("suspicious_count", 0)), "warn"),
1185 (" clean ", "label"),
1186 (str(data.get("clean_count", 0)), "ok"),
1187 )
1188 )
1189 flags = data.get("all_flags") or []
1190 if flags:
1191 console.print(Rule("[heading]Flags[/]", style="muted"))
1192 for flag in flags[:8]:
1193 console.print(f" [warn]•[/] {flag}")
1194 if len(flags) > 8:
1195 console.print(f" [muted]… and {len(flags) - 8} more[/]")
1196
1197 scored = data.get("scored_tensors") or []
1198 flagged = [t for t in scored if t.get("status") in ("high_risk", "suspicious")]
1199 if flagged:
1200 console.print(Rule("[heading]Top flagged tensors[/]", style="muted"))
1202 console,
1203 "",
1204 sorted(flagged, key=lambda t: t.get("risk_score", 0), reverse=True)[:12],
1205 [
1206 ("Layer", "layer_idx"),
1207 ("Tensor", "name"),
1208 ("Risk", "risk_score"),
1209 ("Status", "status"),
1210 ],
1211 )
1212 elif scored:
1213 console.print(f"[muted]{len(scored)} tensors scored — all clean[/]")
1214
1215 rank = data.get("rank")
1216 if isinstance(rank, dict) and rank.get("n_layers") is not None:
1217 console.print(Rule("[heading]Weight matrix rank[/]", style="muted"))
1218 backend = rank.get("backend")
1219 if backend:
1220 console.print(f"[muted]backend[/] {backend}")
1221 if rank.get("skipped"):
1222 console.print(f"[warn]{rank.get('reason', 'rank scan skipped')}[/]")
1223 console.print(
1224 Text.assemble(
1225 ("layers ", "label"),
1226 (str(rank.get("n_layers", "—")), "value"),
1227 (" collapsed ", "label"),
1228 (str(rank.get("collapsed_count", 0)), "bad" if rank.get("collapsed_count") else "ok"),
1229 (" threshold ", "label"),
1230 (str(rank.get("collapse_threshold", 0.1)), "value"),
1231 )
1232 )
1233
1234
1235def _print_umap(console: Console, data: dict[str, Any]) -> None:
1236 n_features = data.get("n_features")
1237 n_points = data.get("n_points") or len(data.get("points") or [])
1238 layer = data.get("layer")
1239 source = data.get("source", "computed")
1240 console.print(Rule("[heading]UMAP projection[/]", style="muted"))
1241 console.print(
1242 Text.assemble(
1243 ("features ", "label"),
1244 (str(n_features if n_features is not None else "—"), "value"),
1245 (" plotted ", "label"),
1246 (str(n_points), "accent"),
1247 (" layer ", "label"),
1248 (str(layer if layer is not None else "—"), "value"),
1249 (" source ", "label"),
1250 (str(source), "muted"),
1251 )
1252 )
1253 if n_features and n_points and n_points < n_features:
1254 console.print(
1255 f"[muted]{n_points} points synced for web "
1256 f"({n_features} total features; use --check for full coords)[/]"
1257 )
1258
1259
1260
1261
1262def _print_simulate(console: Console, data: dict[str, Any]) -> None:
1263 from aquin.compute.card_mapper import simulation_full_card_data
1264
1265 if data.get("status") == "error" or data.get("error"):
1266 _print_error(console, str(data.get("error", "simulation failed")))
1267 if data.get("run_id"):
1268 console.print(f"[muted]partial run saved: {data['run_id']}[/]")
1269 return
1270
1271 sim = simulation_full_card_data(data)
1272 run_id = data.get("run_id") or sim.get("savedRunId") or "—"
1273
1274 console.print(Rule("[heading]Summary[/]", style="muted"))
1275 summary = Table(show_header=False, box=None, padding=(0, 1))
1276 summary.add_column(style="label")
1277 summary.add_column(style="value")
1278 summary.add_row("run_id", str(run_id))
1279 summary.add_row("model", sim.get("modelClass") or sim.get("modelId") or "—")
1280 summary.add_row("method", str(sim.get("method") or "—"))
1281 if data.get("topic"):
1282 summary.add_row("topic", str(data["topic"]))
1283 cfg = sim.get("config") or {}
1284 if cfg.get("rank"):
1285 summary.add_row("LoRA", f"r={cfg['rank']} α={cfg.get('alpha', '?')}")
1286 if cfg.get("lr") is not None:
1287 summary.add_row("lr", f"{float(cfg['lr']):.2e}")
1288 console.print(summary)
1289
1290 dq = sim.get("datasetQuality")
1291 if isinstance(dq, dict):
1292 console.print(Rule("[heading]Dataset[/]", style="muted"))
1293 dq_table = Table(show_header=False, box=None, padding=(0, 1))
1294 dq_table.add_column(style="label")
1295 dq_table.add_column(style="value")
1296 for key, label in (
1297 ("nSamples", "samples"),
1298 ("diversityScore", "diversity"),
1299 ("harmfulCount", "harmful"),
1300 ("shortInstructions", "short instructions"),
1301 ):
1302 if dq.get(key) is not None:
1303 val = dq[key]
1304 if isinstance(val, float):
1305 val = f"{val:.3f}"
1306 dq_table.add_row(label, str(val))
1307 console.print(dq_table)
1308
1309 sae = sim.get("saePrediction")
1310 if isinstance(sae, dict) and sae.get("topFeatures"):
1311 feats = sae["topFeatures"][:8]
1312 console.print(Rule("[heading]Top SAE feature shifts[/]", style="muted"))
1313 ft = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
1314 ft.add_column("Feature")
1315 ft.add_column("Direction")
1316 ft.add_column("Score", justify="right")
1317 for f in feats:
1318 idx = f.get("feature_idx", "?")
1319 direction = f.get("direction", "")
1320 score = f.get("score")
1321 if isinstance(score, (int, float)) and abs(score) >= 1e-4:
1322 score_s = f"{float(score):+.4f}"
1323 elif isinstance(score, (int, float)):
1324 score_s = f"{float(score):+.2e}"
1325 else:
1326 score_s = "—"
1327 style = "good" if direction == "strengthen" else "warn"
1328 ft.add_row(str(idx), Text(direction, style=style), score_s)
1329 console.print(ft)
1330
1331 infl = sim.get("influenceScores")
1332 if isinstance(infl, dict) and infl.get("topSamples"):
1333 console.print(Rule("[heading]High-impact samples[/]", style="muted"))
1334 it = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
1335 it.add_column("Idx")
1336 it.add_column("Influence", justify="right")
1337 it.add_column("Direction")
1338 it.add_column("Instruction")
1339 for s in infl["topSamples"][:5]:
1340 direction = s.get("direction", "")
1341 style = "bad" if direction == "harmful" else "good"
1342 instr = str(s.get("instruction", ""))[:60]
1343 inf = s.get("influence")
1344 inf_s = f"{float(inf):+.4f}" if isinstance(inf, (int, float)) and abs(float(inf)) < 1e4 else (
1345 f"{float(inf):+.4e}" if isinstance(inf, (int, float)) else "—"
1346 )
1347 it.add_row(str(s.get("idx", "")), inf_s, Text(direction, style=style), instr)
1348 console.print(it)
1349
1350 ls = sim.get("lossSharpness")
1351 if isinstance(ls, dict):
1352 lam = ls.get("maxEigenvalue")
1353 lam_s = f"{float(lam):.2e}" if isinstance(lam, (int, float)) else "?"
1354 console.print(
1355 f"[label]Loss landscape:[/] [value]{ls.get('sharpnessLabel', '?')}[/] "
1356 f"[muted](λ={lam_s})[/]"
1357 )
1358
1359 md = sim.get("modelDiff")
1360 if isinstance(md, dict):
1361 parts = []
1362 for label, key in (
1363 ("consistency", "consistencyScore"),
1364 ("suppression", "suppressionScore"),
1365 ("robustness", "robustnessScore"),
1366 ):
1367 val = md.get(key)
1368 if isinstance(val, (int, float)):
1369 parts.append(f"{label}={val:.2f}")
1370 if parts:
1371 console.print(f"[label]Attack surface:[/] [value]{' · '.join(parts)}[/]")
1372
1373 losses = sim.get("lossHistory") or []
1374 if losses:
1375 final_loss = losses[-1]
1376 console.print(
1377 f"[label]Gradient steps:[/] [value]{len(losses)}[/] "
1378 f"[muted]final loss {float(final_loss):.4f}[/]"
1379 )
1380
1381 cal = sim.get("calibration")
1382 if isinstance(cal, dict) and cal.get("base_ece") is not None:
1383 console.print(
1384 f"[label]Calibration:[/] [value]base ECE {float(cal['base_ece']):.4f}[/] "
1385 f"[muted]→[/] [value]ft ECE {float(cal['ft_ece']):.4f}[/] "
1386 f"[muted](Δ {float(cal.get('ece_delta', 0)):+.4f})[/]"
1387 )
1388 low = cal.get("low_confidence_rows") or []
1389 if low:
1390 console.print(f"[muted]{len(low)} low-confidence row(s)[/]")
1391
1392
1393def _format_saved_at(iso: str) -> str:
1394 if not iso:
1395 return "—"
1396 try:
1397 from datetime import datetime
1398 dt = datetime.fromisoformat(iso.replace("Z", "+00:00"))
1399 return dt.strftime("%Y-%m-%d %H:%M")
1400 except ValueError:
1401 return iso[:16] if len(iso) >= 16 else iso
1402
1403
1404def _print_simulation_list(console: Console, data: dict[str, Any]) -> None:
1405 runs = data.get("runs") or []
1406 if not runs:
1407 console.print(
1408 "[muted]No saved simulation runs. Run[/] [accent]aquin simulate[/] "
1409 "[muted]to create one.[/]"
1410 )
1411 return
1412
1413 console.print(Rule("[heading]Saved simulations[/]", style="muted"))
1414 table = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
1415 table.add_column("Run ID", style="accent", no_wrap=True)
1416 table.add_column("Model", style="value")
1417 table.add_column("Saved", style="muted", no_wrap=True)
1418 table.add_column("Samples", justify="right", style="value")
1419 table.add_column("Topic / dataset", style="muted")
1420
1421 for entry in runs:
1422 if isinstance(entry, str):
1423 table.add_row(entry, "—", "—", "—", "—")
1424 continue
1425 run_id = str(entry.get("run_id") or "—")
1426 model = _trunc(str(entry.get("model_id") or "—"), 28)
1427 saved = _format_saved_at(str(entry.get("saved_at") or ""))
1428 n_samples = entry.get("n_samples")
1429 samples = str(n_samples) if n_samples is not None else "—"
1430 topic = entry.get("topic")
1431 ds = entry.get("dataset_path") or ""
1432 if topic:
1433 subtitle = _trunc(str(topic), 36)
1434 elif ds:
1435 subtitle = _trunc(str(ds).replace("\\", "/").split("/")[-1], 36)
1436 else:
1437 subtitle = "—"
1438 method = entry.get("method")
1439 if method and method != "LoRA":
1440 model = f"{model} [{method}]"
1441 table.add_row(run_id, model, saved, samples, subtitle)
1442
1443 console.print(table)
1444 count = data.get("count", len(runs))
1445 console.print()
1446 console.print(
1447 f"[muted]{count} run(s). Load one with:[/] "
1448 f"[accent]aquin replay simulation --run_id <id>[/]"
1449 )
1450
1451
1452def _print_loaded_simulation(console: Console, data: dict[str, Any]) -> None:
1453 if data.get("status") == "not_found":
1454 _print_error(console, f"Run not found: {data.get('run_id', '?')}")
1455 return
1456 run_id = data.get("run_id", "")
1457 payload = {k: v for k, v in data.items() if k != "run_id"}
1458 wrapped = {
1459 "run_id": run_id,
1460 "result": payload,
1461 "events": [],
1462 "model_id": payload.get("model_id") or (payload.get("meta") or {}).get("modelId"),
1463 }
1464 _print_simulate(console, wrapped)
1465
1466
1467def _fmt_influence(val: float | None) -> str:
1468 if val is None:
1469 return "—"
1470 v = float(val)
1471 if abs(v) >= 1e4 or (abs(v) > 0 and abs(v) < 1e-4):
1472 return f"{v:+.4e}"
1473 return f"{v:+.4f}"
1474
1475
1476def _short_param_name(name: str) -> str:
1477 import re
1478 m = re.search(r"layers\.(\d+)", name)
1479 layer = f"L{m.group(1)}" if m else ""
1480 parts = name.split(".")
1481 tail = ".".join(parts[-2:]) if len(parts) >= 2 else name
1482 return f"{layer}.{tail}" if layer else _trunc(name, 44)
1483
1484
1485def _print_simulation_compare(console: Console, data: dict[str, Any]) -> None:
1486 comp = data.get("comparison") if isinstance(data.get("comparison"), dict) else None
1487 if not comp:
1488 _print_generic(console, "compare simulation", data)
1489 return
1490
1491 console.print(Rule("[heading]Simulation comparison[/]", style="muted"))
1492 header = Table(show_header=False, box=None, padding=(0, 1))
1493 header.add_column(style="label")
1494 header.add_column(style="value")
1495 header.add_row(str(comp.get("label_a") or "Before"), str(data.get("run_id_a") or "—"))
1496 header.add_row(str(comp.get("label_b") or "After"), str(data.get("run_id_b") or "—"))
1497 console.print(header)
1498
1499 run_a = comp.get("run_a") if isinstance(comp.get("run_a"), dict) else {}
1500 run_b = comp.get("run_b") if isinstance(comp.get("run_b"), dict) else {}
1501 if run_a or run_b:
1502 meta = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
1503 meta.add_column("")
1504 meta.add_column(str(comp.get("label_a") or "A"), style="value")
1505 meta.add_column(str(comp.get("label_b") or "B"), style="value")
1506 for label, key in (
1507 ("model", "model_id"),
1508 ("samples", "n_samples"),
1509 ("final loss", "final_loss"),
1510 ("sharpness", "sharpness"),
1511 ("influence", "influence_method"),
1512 ):
1513 va, vb = run_a.get(key), run_b.get(key)
1514 if va is not None or vb is not None:
1515 meta.add_row(label, str(va if va is not None else "—"), str(vb if vb is not None else "—"))
1516 console.print(meta)
1517
1518 loss_delta = comp.get("lossDelta")
1519 if loss_delta is not None or run_a.get("n_samples") != run_b.get("n_samples"):
1520 parts = []
1521 sa, sb = run_a.get("n_samples"), run_b.get("n_samples")
1522 if sa is not None and sb is not None and sa != sb:
1523 parts.append(f"samples {sa}→{sb}")
1524 if loss_delta is not None:
1525 parts.append(f"final loss {run_a.get('final_loss')}→{run_b.get('final_loss')} (Δ{float(loss_delta):+.4f})")
1526 if parts:
1527 console.print(f"[label]Dataset shift:[/] [value]{' · '.join(parts)}[/]")
1528
1529 console.print(
1530 f"[label]Flipped:[/] [value]{comp.get('nFlippedFeatures', 0)} features[/] "
1531 f"[muted]·[/] [value]{comp.get('nFlippedInfluence', 0)} influence[/] "
1532 f"[muted]· max feature Δ[/] [value]{comp.get('maxFeatureDelta', 0)}[/]"
1533 )
1534 if comp.get("similarRuns"):
1535 console.print(
1536 "[muted]Runs are nearly identical — same dataset + config reproduced the same prediction.[/]"
1537 )
1538 elif comp.get("saeOverlapIdentical") and not comp.get("similarRuns"):
1539 n_only_a = comp.get("nFeaturesOnlyA", 0)
1540 n_only_b = comp.get("nFeaturesOnlyB", 0)
1541 n_overlap = comp.get("nFeaturesOverlap", 0)
1542 msg = (
1543 f"[muted]{n_overlap} overlapping SAE features have identical scores"
1544 )
1545 if n_only_a or n_only_b:
1546 msg += f" · {n_only_a} only in Before · {n_only_b} only in After"
1547 msg += " — loss/sample count still differ.[/]"
1548 console.print(msg)
1549
1550 scores = comp.get("modelScores") or {}
1551 deltas = comp.get("attackSurfaceDeltas") if isinstance(comp.get("attackSurfaceDeltas"), dict) else {}
1552 atk = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
1553 atk.add_column("Metric")
1554 atk.add_column(str(comp.get("label_a") or "A"), justify="right")
1555 atk.add_column(str(comp.get("label_b") or "B"), justify="right")
1556 atk.add_column("Δ", justify="right", style="muted")
1557 has_atk = False
1558 for label, key in (
1559 ("Consistency", "consistencyScore"),
1560 ("Suppression", "suppressionScore"),
1561 ("Robustness", "robustnessScore"),
1562 ):
1563 side_a = scores.get("a") if isinstance(scores, dict) else {}
1564 side_b = scores.get("b") if isinstance(scores, dict) else {}
1565 va = side_a.get(key) if isinstance(side_a, dict) else None
1566 vb = side_b.get(key) if isinstance(side_b, dict) else None
1567 if isinstance(va, (int, float)) or isinstance(vb, (int, float)):
1568 has_atk = True
1569 delta_key = key.replace("Score", "")
1570 d = deltas.get(delta_key)
1571 delta_s = f"{float(d):+.3f}" if isinstance(d, (int, float)) else "—"
1572 atk.add_row(
1573 label,
1574 _pct(va) if isinstance(va, (int, float)) else "—",
1575 _pct(vb) if isinstance(vb, (int, float)) else "—",
1576 delta_s,
1577 )
1578 if has_atk:
1579 console.print(Rule("[heading]Attack surface[/]", style="muted"))
1580 console.print(atk)
1581
1582 sharp = comp.get("sharpness") if isinstance(comp.get("sharpness"), dict) else {}
1583 if sharp.get("label_a") or sharp.get("label_b"):
1584 console.print(
1585 f"[label]Loss landscape:[/] [value]{sharp.get('label_a', '?')}[/] "
1586 f"[muted]→[/] [value]{sharp.get('label_b', '?')}[/]"
1587 )
1588
1589 diffs = comp.get("featureDiffs") or []
1590 nonzero_feats = [
1591 f for f in diffs
1592 if f.get("score_delta") is not None and abs(float(f["score_delta"])) > 1e-9
1593 ]
1594 if nonzero_feats:
1595 console.print(Rule("[heading]SAE feature shifts (by |Δ|)[/]", style="muted"))
1596 ft = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
1597 ft.add_column("Feature")
1598 ft.add_column("Score A", justify="right")
1599 ft.add_column("Score B", justify="right")
1600 ft.add_column("Δ", justify="right")
1601 ft.add_column("Dir")
1602 for f in nonzero_feats[:12]:
1603 sa, sb = f.get("score_a"), f.get("score_b")
1604 delta = f.get("score_delta")
1605 dir_s = f"{f.get('direction_a', '?')}→{f.get('direction_b', '?')}"
1606 if f.get("flipped"):
1607 dir_s = Text(dir_s + " ⚡", style="warn")
1608 ft.add_row(
1609 str(f.get("feature_idx", "?")),
1610 f"{float(sa):+.4f}" if isinstance(sa, (int, float)) else "—",
1611 f"{float(sb):+.4f}" if isinstance(sb, (int, float)) else "—",
1612 f"{float(delta):+.4e}" if isinstance(delta, (int, float)) else "—",
1613 dir_s,
1614 )
1615 console.print(ft)
1616 else:
1617 only_a = [f for f in diffs if f.get("only_in") == "a"]
1618 only_b = [f for f in diffs if f.get("only_in") == "b"]
1619 n_overlap = comp.get("nFeaturesOverlap", 0)
1620 if n_overlap and comp.get("saeOverlapIdentical"):
1621 console.print(
1622 f"[muted]{n_overlap} overlapping SAE features — identical scores[/]"
1623 )
1624 elif diffs:
1625 console.print(f"[muted]{len(diffs)} SAE features in union — no score deltas above noise[/]")
1626 for title, rows, score_key in (
1627 (f"Top SAE features only in {comp.get('label_a', 'Before')}", only_a[:8], "score_a"),
1628 (f"Top SAE features only in {comp.get('label_b', 'After')}", only_b[:8], "score_b"),
1629 ):
1630 if not rows:
1631 continue
1632 console.print(Rule(f"[heading]{title}[/]", style="muted"))
1633 ot = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
1634 ot.add_column("Feature")
1635 ot.add_column("Score", justify="right")
1636 ot.add_column("Dir")
1637 for f in rows:
1638 sc = f.get(score_key)
1639 ot.add_row(
1640 str(f.get("feature_idx", "?")),
1641 f"{float(sc):+.4f}" if isinstance(sc, (int, float)) else "—",
1642 str(f.get("direction_a" if score_key == "score_a" else "direction_b") or "—"),
1643 )
1644 console.print(ot)
1645
1646 infl_avail = comp.get("influenceAvailable") if isinstance(comp.get("influenceAvailable"), dict) else {}
1647 infl = comp.get("influenceDiffs") or []
1648 paired_infl = [
1649 s for s in infl
1650 if s.get("influence_a") is not None and s.get("influence_b") is not None
1651 ]
1652 if infl_avail.get("a") and not infl_avail.get("b"):
1653 console.print(
1654 f"[warn]{comp.get('label_b', 'After')} has no saved influence scores[/] "
1655 f"[muted](re-run simulate on that config to compare influence)[/]"
1656 )
1657 elif infl_avail.get("b") and not infl_avail.get("a"):
1658 console.print(
1659 f"[warn]{comp.get('label_a', 'Before')} has no saved influence scores[/]"
1660 )
1661 if paired_infl:
1662 console.print(Rule("[heading]Influence changes[/]", style="muted"))
1663 it = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
1664 it.add_column("Idx")
1665 it.add_column("A", justify="right")
1666 it.add_column("B", justify="right")
1667 it.add_column("Δ", justify="right")
1668 it.add_column("Instruction")
1669 for s in paired_infl[:8]:
1670 it.add_row(
1671 str(s.get("idx", "")),
1672 _fmt_influence(s.get("influence_a")),
1673 _fmt_influence(s.get("influence_b")),
1674 _fmt_influence(s.get("delta")),
1675 _trunc(str(s.get("instruction") or ""), 40),
1676 )
1677 console.print(it)
1678 elif infl_avail.get("a") or infl_avail.get("b"):
1679 console.print("[muted]No overlapping influence samples to compare[/]")
1680
1681 lr = comp.get("lrDiffs") or []
1682 if lr:
1683 console.print(Rule("[heading]Effective LR deltas[/]", style="muted"))
1684 lt = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
1685 lt.add_column("Param")
1686 lt.add_column("A", justify="right")
1687 lt.add_column("B", justify="right")
1688 lt.add_column("Δ", justify="right")
1689 for row in lr[:6]:
1690 lt.add_row(
1691 _short_param_name(str(row.get("param", ""))),
1692 _num(row.get("lr_a"), 6),
1693 _num(row.get("lr_b"), 6),
1694 _num(row.get("delta"), 6),
1695 )
1696 console.print(lt)
1697 elif comp.get("similarRuns"):
1698 console.print("[muted]Effective LR identical across shared parameters[/]")
1699
1700
1701def _print_generic(console: Console, verb: str, data: dict[str, Any]) -> None:
1703 _print_layer_analysis(console, data)
1704 return
1705 if data.get("perturbation_results") or data.get("channels"):
1706 _print_perturbation(console, data)
1707 return
1708 if data.get("layer_norms"):
1710 console,
1711 "Residual norms",
1712 data["layer_norms"],
1713 [("Layer", "layer"), ("Norm", "norm")],
1714 )
1715 return
1716
1717 console.print(Rule(f"[heading]{verb}[/]", style="muted"))
1718 skip_keys: set[str] = set()
1719 for key, val in data.items():
1720 if key == "error" or not isinstance(val, list) or not val:
1721 continue
1722 if all(isinstance(item, dict) for item in val):
1723 keys = set()
1724 for item in val:
1725 keys.update(item.keys())
1726 if len(keys) <= 6:
1727 cols = [(k, k) for k in sorted(keys)]
1728 _print_dict_list_table(console, key.replace("_", " ").title(), val, cols)
1729 skip_keys.add(key)
1730
1731 table = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
1732 table.add_column("Key", style="label")
1733 table.add_column("Value", style="value")
1734 has_rows = False
1735 for key, val in data.items():
1736 if key == "error" or key in skip_keys:
1737 continue
1738 if isinstance(val, dict):
1739 table.add_row(key, f"[muted]{len(val)} fields (use --check)[/]")
1740 has_rows = True
1741 elif isinstance(val, list):
1742 table.add_row(key, f"[muted]{len(val)} items (use --check)[/]")
1743 has_rows = True
1744 elif isinstance(val, float):
1745 table.add_row(key, _num(val))
1746 has_rows = True
1747 elif isinstance(val, (str, int, bool)) or val is None:
1748 table.add_row(key, str(val) if val is not None else "—")
1749 has_rows = True
1750
1751 if has_rows:
1752 console.print(table)
1753 elif not skip_keys:
1754 console.print("[muted]No displayable fields. Use --check for full result.[/]")
1755
1756
1757def _print_dataset_generate(console: Console, data: dict[str, Any]) -> None:
1758 path = data.get("path") or "—"
1759 topic = data.get("topic") or "—"
1760 count = data.get("count", 0)
1761 console.print(Rule("[heading]Generated dataset[/]", style="muted"))
1762 console.print(f"[label]Topic[/] [value]{topic}[/]")
1763 console.print(f"[label]Rows[/] [value]{count}[/]")
1764 console.print(f"[label]File[/] [accent]{path}[/]")
1765 rows = data.get("rows") or []
1766 if rows:
1767 table = Table(show_header=True, header_style="label", box=None, padding=(0, 1))
1768 table.add_column("#", style="muted", justify="right", width=3)
1769 table.add_column("Instruction", style="value", max_width=48)
1770 table.add_column("Response", style="muted", max_width=48)
1771 for i, row in enumerate(rows[:8], 1):
1772 instr = str(row.get("instruction", ""))
1773 resp = str(row.get("response", ""))
1774 if len(instr) > 48:
1775 instr = instr[:45] + "..."
1776 if len(resp) > 48:
1777 resp = resp[:45] + "..."
1778 table.add_row(str(i), instr, resp)
1779 console.print(table)
1780 if len(rows) > 8:
1781 console.print(f"[muted]… and {len(rows) - 8} more row(s)[/]")
1782
1783
1784def print_tool_result(verb: str, result: Any, *, tool_name: str | None = None) -> None:
1785 """Pretty-print a tool result using the same Rich theme as aquin chat."""
1786 console = _console()
1787 data = _unwrap(result)
1789 if isinstance(data, dict) and data.get("error"):
1790 _print_error(console, str(data["error"]))
1791 return
1792
1793 console.print()
1794 title = verb.replace("-", " ").title()
1795 model_id = data.get("model_id") if isinstance(data, dict) else None
1796 header = f"[title]{title}[/]"
1797 if model_id:
1798 header += f" [muted]{model_id}[/]"
1799 console.print(Panel.fit(header, border_style="dim"))
1800
1801 if verb == "audit":
1802 if data.get("consistency"):
1803 _print_consistency(console, data["consistency"])
1804 if data.get("suppression"):
1805 _print_suppression(console, data["suppression"])
1806 if data.get("boundary"):
1807 _print_boundary(console, data["boundary"])
1808 console.print()
1809 return
1810
1811 if verb == "consistency-eval":
1812 _print_consistency(console, data)
1813 console.print()
1814 return
1815
1816 if verb == "suppression-eval":
1817 _print_suppression(console, data)
1818 console.print()
1819 return
1820
1821 if verb == "boundary-eval":
1822 _print_boundary(console, data)
1823 console.print()
1824 return
1825
1826 if verb == "eval":
1827 _print_custom_eval(console, data)
1828 console.print()
1829 return
1830
1831 if verb in ("benchmark", "benchmarks"):
1832 _print_benchmarks(console, data)
1833 console.print()
1834 return
1835
1836 if verb == "feature locate":
1837 _print_find_feature(console, data)
1838 console.print()
1839 return
1840
1841 if verb == "extract-steer-vector" or tool_name == "extract_steer_vector":
1842 _print_extract_steer_vector(console, data)
1843 console.print()
1844 return
1845
1846 if verb in ("steer", "multi-steer"):
1847 _print_steer(console, data)
1848 console.print()
1849 return
1850
1851 if verb == "red-team":
1852 _print_red_team(console, data)
1853 console.print()
1854 return
1855
1856 if verb == "layer-analysis":
1857 _print_layer_analysis(console, data)
1858 console.print()
1859 return
1860
1861 if verb == "sae-stats":
1862 _print_sae_stats(console, data)
1863 console.print()
1864 return
1865
1866 if verb in ("confidence-analysis", "check confidence"):
1867 _print_confidence_analysis(console, data)
1868 console.print()
1869 return
1870
1871 if verb == "diff weight":
1872 _print_weight_diff(console, data)
1873 console.print()
1874 return
1875
1876 if verb == "check trajectory":
1877 _print_trajectory_analysis(console, data)
1878 console.print()
1879 return
1880
1881 if verb == "diff residue":
1882 _print_residual_drift(console, data)
1883 console.print()
1884 return
1885
1886 if verb == "perturbation":
1887 _print_perturbation(console, data)
1888 console.print()
1889 return
1890
1891 if verb == "attention":
1892 _print_attention(console, data)
1893 console.print()
1894 return
1895
1896 if verb == "check-weights":
1897 _print_check_weights(console, data)
1898 console.print()
1899 return
1900
1901 if verb == "umap":
1902 _print_umap(console, data)
1903 console.print()
1904 return
1905
1906 if verb == "simulate":
1907 _print_simulate(console, data)
1908 console.print()
1909 return
1910
1911 if verb in ("list simulation", "list simulations", "list-runs"):
1912 _print_simulation_list(console, data)
1913 console.print()
1914 return
1915
1916 if verb in ("replay simulation", "load simulation", "load-run"):
1917 _print_loaded_simulation(console, data)
1918 console.print()
1919 return
1920
1921 if verb in ("compare simulation", "compare-runs"):
1922 _print_simulation_compare(console, data)
1923 console.print()
1924 return
1925
1926 if verb == "dataset-generate":
1927 _print_dataset_generate(console, data)
1928 console.print()
1929 return
1930
1931 if isinstance(data, dict):
1932 _print_generic(console, verb, data)
1933 else:
1934 console.print(data)
1935 console.print()
None _print_dict_list_table(Console console, str title, list[dict[str, Any]] rows, list[tuple[str, str]] columns)
str _status_style(str status)
Definition cli_output.py:75
None _print_custom_eval(Console console, dict[str, Any] data)
str _trunc(str text, int limit=72)
Definition cli_output.py:52
None _print_benchmarks(Console console, dict[str, Any] data)
None _print_steer_eval(Console console, dict[str, Any] eval_data)
None _print_umap(Console console, dict[str, Any] data)
None _print_generic(Console console, str verb, dict[str, Any] data)
None _print_consistency(Console console, dict[str, Any] data)
None _print_residual_drift(Console console, dict[str, Any] data)
None _print_check_weights(Console console, dict[str, Any] data)
float _layer_cosine(list[float] a, list[float] b)
None _print_weight_diff(Console console, dict[str, Any] data)
None _print_boundary(Console console, dict[str, Any] data)
str _pct(float|None score)
Definition cli_output.py:40
str _short_param_name(str name)
None _print_perturbation(Console console, dict[str, Any] data)
None _print_confidence_analysis(Console console, dict[str, Any] data)
None _print_find_feature(Console console, dict[str, Any] data)
None _print_extract_steer_vector(Console console, dict[str, Any] data)
None _print_merge_analysis(Console console, dict[str, Any] data)
None print_tool_result(str verb, Any result, *, str|None tool_name=None)
str _score_style(float score)
Definition cli_output.py:59
None _print_simulation_list(Console console, dict[str, Any] data)
None _print_trajectory_analysis(Console console, dict[str, Any] data)
None _print_localize_collapse(Console console, dict[str, Any] data)
float _vec_norm(list[float] vec)
dict[str, Any] _unwrap(Any result)
Definition cli_output.py:92
list[list[float]] _normalize_attn_matrix(list[list[float]] matrix)
None _print_error(Console console, str message)
None _print_simulation_compare(Console console, dict[str, Any] data)
None _print_red_team(Console console, dict[str, Any] data)
str _kl_style(float kl)
Definition cli_output.py:67
Console _console()
Definition cli_output.py:36
dict[str, Any] _normalize_feature_benchmark(dict[str, Any] data)
Definition cli_output.py:83
None _print_steer(Console console, dict[str, Any] data)
None _print_layer_analysis(Console console, dict[str, Any] data)
None _print_simulate(Console console, dict[str, Any] data)
None _print_loaded_simulation(Console console, dict[str, Any] data)
str _format_saved_at(str iso)
None _print_suppression(Console console, dict[str, Any] data)
str _fmt_influence(float|None val)
None _print_dataset_generate(Console console, dict[str, Any] data)
None _print_attention(Console console, dict[str, Any] data)
None _print_sae_stats(Console console, dict[str, Any] data)
str _num(float|None v, int digits=3)
Definition cli_output.py:46
bool _is_llm_layer_analysis_payload(dict[str, Any] data)