AQIT 0.1.0
Loading...
Searching...
No Matches
card_mapper.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"""
7Maps tool result dicts to PanelCardData-shaped dicts for the web UI.
8Returns None for tools that produce no card (UI-only events, state mutations, etc.).
9"""
10from __future__ import annotations
11
12# Tools that produce no card — UI events and state-only mutations
13_NO_CARD_TOOLS: frozenset[str] = frozenset({
14 # session memory
15 "write_session_memory",
16 "read_session_memory",
17})
18
19# Direct type mappings — tool_name → card type string
20_TYPE_MAP: dict[str, str] = {
21 "run_full_inspection": "inspectionFull",
22 "run_benchmarks_on_top_feature": "interpScore",
23 "run_consistency_eval": "evals",
24 "run_suppression_eval": "evals",
25 "run_boundary_eval": "evals",
26 "run_audit": "evals",
27 "get_feature_logits": "featureLogits",
28 "get_feature_neighbors": "featureNeighbors",
29 "run_steer_and_show": "steer",
30 "extract_steer_vector": "steerVector",
31 "run_multi_steer": "steer",
32 "check_weights": "trojan",
33 "ensure_umap_loaded": "umap",
34 "run_layer_analysis": "layerAnalysis",
35 "run_perturbation_sensitivity": "perturbation",
36 "run_attention_routing": "attentionRouting",
37 "run_simulation": "simulationFull",
38 "list_simulation_runs": "simulationList",
39 "load_simulation_run": "simulationFull",
40 "compare_simulations": "simulationComparison",
41 "run_custom_eval": "customEval",
42 "run_red_team": "redTeam",
43 "run_sae_diff": "saeDiff",
44 "run_find_feature": "findFeature",
45 "run_sae_stats": "saeStats",
46 "run_weight_diff": "weightDiff",
47 "run_merge_analysis": "mergeAnalysis",
48 "run_trajectory_analysis": "trajectoryAnalysis",
49 "run_residual_drift": "residualDrift",
50 "run_confidence_analysis": "confidenceAnalysis",
51}
52
53
54def _simulation_method_from_meta(meta: dict) -> str:
55 algo = meta.get("algoConfig") if isinstance(meta.get("algoConfig"), dict) else {}
56 title = str(algo.get("title") or "")
57 for name in ("QLoRA", "Full FT", "SFT", "RLHF", "DPO", "Distil", "CPT", "LoRA"):
58 if name in title:
59 return name
60 return "LoRA"
61
62
63def _simulation_config_from_meta(meta: dict) -> dict:
64 config: dict = {"lr": 2e-4, "epochs": 3, "optimizer": "adamw"}
65 algo = meta.get("algoConfig") if isinstance(meta.get("algoConfig"), dict) else {}
66 rows = algo.get("config")
67 if not isinstance(rows, list):
68 return config
69 for item in rows:
70 if not isinstance(item, dict):
71 continue
72 key = str(item.get("k", "")).lower()
73 val = item.get("v")
74 if key == "lr":
75 try:
76 config["lr"] = float(str(val).replace("e", "E"))
77 except (TypeError, ValueError):
78 pass
79 elif key == "rank":
80 try:
81 config["rank"] = int(val)
82 except (TypeError, ValueError):
83 pass
84 elif key == "alpha":
85 try:
86 config["alpha"] = int(val)
87 except (TypeError, ValueError):
88 pass
89 elif key == "epochs" or "epoch" in key:
90 try:
91 config["epochs"] = int(str(val).split()[0])
92 except (TypeError, ValueError):
93 pass
94 return config
95
96
97def simulation_list_card_data(result: dict) -> dict:
98 """Map list_simulation_runs → SimulationListResult for the web panel."""
99 raw = result.get("runs") or []
100 runs: list[dict] = []
101 for entry in raw:
102 if isinstance(entry, str):
103 runs.append({"run_id": entry})
104 elif isinstance(entry, dict):
105 runs.append({
106 "run_id": str(entry.get("run_id") or ""),
107 "saved_at": entry.get("saved_at"),
108 "model_id": entry.get("model_id"),
109 "topic": entry.get("topic"),
110 "dataset_path": entry.get("dataset_path"),
111 "kind": entry.get("kind"),
112 "n_samples": entry.get("n_samples"),
113 "harmful_count": entry.get("harmful_count"),
114 "method": entry.get("method"),
115 })
116 return {
117 "runs": runs,
118 "count": int(result.get("count") if result.get("count") is not None else len(runs)),
119 }
120
121
122def steer_card_data(result: dict) -> dict | None:
123 """Map run_steer_and_show / run_multi_steer → steer panel card fields."""
124 baseline = result.get("baseline") or result.get("baseline_response") or ""
125 steered = result.get("steered") or result.get("steered_response") or ""
126 if not baseline and not steered:
127 return None
128
129 feature_idx = int(result.get("feature_idx", 0))
130 feature_label = str(result.get("feature_label") or result.get("feature_ref") or "")
131 features = result.get("features")
132 if not feature_label and isinstance(features, list) and features:
133 parts: list[str] = []
134 for f in features:
135 if not isinstance(f, dict):
136 continue
137 parts.append(str(f.get("label") or f.get("feature_ref") or f"F{f.get('feature_idx', '?')}"))
138 if feature_idx == 0 and f.get("feature_idx") is not None:
139 feature_idx = int(f["feature_idx"])
140 feature_label = ", ".join(parts) if parts else f"F{feature_idx}"
141
142 prompt = str(result.get("prompt") or "")
143 words_changed = result.get("words_changed")
144 if words_changed is None and baseline and steered:
145 b_words = baseline.split()
146 s_words = steered.split()
147 words_changed = sum(1 for a, b in zip(b_words, s_words) if a != b) + abs(len(b_words) - len(s_words))
148
149 data: dict = {
150 "featureIdx": feature_idx,
151 "featureLabel": feature_label,
152 "prompt": prompt,
153 "baseline": str(baseline),
154 "steered": str(steered),
155 }
156 if words_changed is not None:
157 data["wordsChanged"] = int(words_changed)
158 if result.get("vector_path"):
159 data["vectorPath"] = str(result["vector_path"])
160 if result.get("vector_source_model_id"):
161 data["vectorSourceModelId"] = str(result["vector_source_model_id"])
162 return data
163
164
165def steer_vector_card_data(result: dict) -> dict | None:
166 if result.get("error"):
167 return None
168 path = result.get("output_path")
169 if not path:
170 return None
171 return {
172 "modelId": result.get("model_id"),
173 "layer": result.get("layer"),
174 "featureIdx": result.get("feature_idx"),
175 "featureLabel": result.get("feature_label"),
176 "probeId": result.get("probe_id"),
177 "outputPath": path,
178 "dModel": result.get("d_model"),
179 "vectorL2Norm": result.get("vector_l2_norm"),
180 "norm": result.get("norm"),
181 "status": result.get("status", "done"),
182 }
183
184
185def load_simulation_card_data(result: dict) -> dict:
186 """Map load_simulation_run → SimulationFullResult."""
187 if result.get("status") == "not_found" or result.get("error"):
188 return {}
189 run_id = str(result.get("run_id") or "")
190 payload = {k: v for k, v in result.items() if k not in ("run_id", "error", "status")}
191 wrapped = {
192 "model_id": payload.get("model_id") or (payload.get("meta") or {}).get("modelId"),
193 "run_id": run_id,
194 "result": payload,
195 "events": [],
196 }
197 data = simulation_full_card_data(wrapped)
198 data["savedRunId"] = run_id
199 return data
200
201
202
203
204
205
206def simulation_comparison_card_data(result: dict) -> dict:
207 """Map compare_simulations → SimulationComparisonResult."""
208 comp = result.get("comparison")
209 if isinstance(comp, dict) and comp.get("featureDiffs") is not None:
210 return comp
211 return {}
212
213
214def simulation_full_card_data(result: dict) -> dict:
215 """Map CLI simulate result → SimulationFullResult for the web panel."""
216 assembled = result.get("result") if isinstance(result.get("result"), dict) else {}
217 events = result.get("events") if isinstance(result.get("events"), list) else []
219 meta = dict(assembled.get("meta") or {})
220 loss_history: list[float] = list(assembled.get("lossHistory") or [])
221 grad_heatmap: list[dict] = []
222 signals: list[dict] = list(assembled.get("signals") or [])
223 logs: list[str] = []
224
225 for ev in events:
226 if not isinstance(ev, dict):
227 continue
228 t = ev.get("type")
229 if t == "meta":
230 meta.update({k: v for k, v in ev.items() if k != "type"})
231 elif t == "step" and ev.get("loss") is not None:
232 loss_history.append(float(ev["loss"]))
233 elif t == "gradHeatmap":
234 grad_heatmap.append({k: v for k, v in ev.items() if k != "type"})
235 elif t == "signal":
236 signals.append({
237 "signalType": ev.get("signalType", ""),
238 "severity": ev.get("severity", ""),
239 "message": ev.get("message", ""),
240 "step": ev.get("step", 0),
241 })
242 elif t == "log" and ev.get("line"):
243 logs.append(str(ev["line"]))
244
245 model_id = str(result.get("model_id") or meta.get("modelId") or "")
246 if not model_id and meta.get("modelClass"):
247 model_id = str(meta["modelClass"])
248
249 model_diff = assembled.get("modelDiff")
250 if isinstance(model_diff, dict):
251 model_diff = {**model_diff, "isSimulation": model_diff.get("isSimulation", True)}
252
253 return {
254 "modelId": model_id,
255 "method": _simulation_method_from_meta(meta),
256 "modelClass": meta.get("modelClass") or (model_id.split("/")[-1] if model_id else "model"),
257 "trainableParams": meta.get("trainableParams"),
258 "config": _simulation_config_from_meta(meta),
259 "datasetQuality": assembled.get("datasetQuality"),
260 "saePrediction": assembled.get("saePrediction"),
261 "influenceScores": assembled.get("influenceScores"),
262 "rlhfPrediction": assembled.get("rlhfPrediction"),
263 "effectiveLR": assembled.get("effectiveLR"),
264 "lossSharpness": assembled.get("lossSharpness"),
265 "modelDiff": model_diff,
266 "gradHeatmap": grad_heatmap or assembled.get("gradHeatmap"),
267 "lossHistory": loss_history,
268 "signals": signals,
269 "logs": logs,
270 "calibration": assembled.get("calibration"),
271 "savedRunId": result.get("run_id"),
272 "isSimulation": True,
273 }
274
275
276def to_card(tool_name: str, result: dict) -> dict | None:
277 """
278 Convert a tool result dict into a PanelCardData-shaped dict.
279 Returns None for tools that don't produce a visual card.
280 """
281 if tool_name in _NO_CARD_TOOLS:
282 return None
283
284 # If the result is already a UI event (from UI-only pass-through), skip
285 if result.get("type") == "ui_event":
286 return None
287
288 # If the result signals not-implemented, no card
289 if result.get("status") == "not_implemented":
290 return None
291
292 card_type = _TYPE_MAP.get(tool_name)
293 if card_type is None:
294 # Unknown tool or one that returns its own card structure
295 return None
296
297 # Special case: tools that wrap data under a sub-key
298 if tool_name == "run_full_inspection":
299 data = result.get("content", result)
300 return {"type": card_type, "data": data}
301
302 if tool_name == "get_feature_logits":
303 data = result.get("content", result)
304 if isinstance(data, dict):
305 boosts = data.get("boosts") or data.get("top") or []
306 suppresses = data.get("suppresses") or data.get("bottom") or []
307 data = {**data, "boosts": boosts, "suppresses": suppresses}
308 return {"type": card_type, "data": data}
309
310 if tool_name == "get_feature_neighbors":
311 data = result.get("content", result)
312 return {"type": card_type, "data": data}
313
314 if tool_name == "check_weights":
315 if result.get("error"):
316 return None
317 src = result.get("trojan", result)
318 if not isinstance(src, dict):
319 return None
320 data = {
321 "model_id": str(result.get("model_id", src.get("model_id", ""))),
322 "generated_at": str(result.get("generated_at", "")),
323 "layers_analysed": src.get("layers_analysed", 0),
324 "composite_risk": src.get("composite_risk", 0),
325 "verdict": src.get("verdict", "clean"),
326 "pct_flagged": src.get("pct_flagged", 0),
327 "high_risk_count": src.get("high_risk_count", 0),
328 "suspicious_count": src.get("suspicious_count", 0),
329 "clean_count": src.get("clean_count", 0),
330 "all_flags": src.get("all_flags", []),
331 "scored_tensors": src.get("scored_tensors", []),
332 "signals": src.get("signals", []),
333 }
334 return {"type": card_type, "data": data}
335
336 if tool_name == "run_audit":
337 return {
338 "type": card_type,
339 "data": {
340 "consistency": result.get("consistency"),
341 "suppression": result.get("suppression"),
342 "boundary": result.get("boundary"),
343 },
344 }
345
346 if tool_name == "run_consistency_eval":
347 return {"type": card_type, "data": {"consistency": result}}
348 if tool_name == "run_suppression_eval":
349 return {"type": card_type, "data": {"suppression": result}}
350 if tool_name == "run_boundary_eval":
351 return {"type": card_type, "data": {"boundary": result}}
352
353 if tool_name in ("run_steer_and_show", "run_multi_steer"):
354 if result.get("error"):
355 return None
356 data = steer_card_data(result)
357 if data is None:
358 return None
359 return {"type": card_type, "data": data}
360
361 if tool_name == "extract_steer_vector":
362 if result.get("error"):
363 return None
364 data = steer_vector_card_data(result)
365 if data is None:
366 return None
367 return {"type": card_type, "data": data}
368
369 if tool_name == "run_benchmarks_on_top_feature":
370 if result.get("error"):
371 return None
372 payload = result
373 if result.get("type") == "benchmark" and isinstance(result.get("data"), dict):
374 payload = result["data"]
375 return {"type": card_type, "data": payload}
376
377 if tool_name == "run_find_feature":
378 if result.get("error"):
379 return None
380 return {
381 "type": card_type,
382 "data": {
383 "modelId": result.get("model_id"),
384 "layer": result.get("layer"),
385 "scorer": result.get("scorer"),
386 "direction": result.get("direction"),
387 "conditioning": result.get("conditioning"),
388 "behavior": result.get("behavior"),
389 "nHonest": result.get("n_honest"),
390 "nDeceptive": result.get("n_deceptive"),
391 "promptsPath": result.get("prompts_path"),
392 "checkpoint": result.get("checkpoint"),
393 "chosenFeatureIdx": result.get("chosen_feature_idx"),
394 "chosenDelta": result.get("chosen_delta"),
395 "warning": result.get("warning"),
396 "persistedKey": result.get("persisted_key"),
397 "experimentPath": result.get("experiment_path"),
398 "rankings": result.get("rankings") or [],
399 "status": result.get("status", "done"),
400 },
401 }
402
403 if tool_name == "run_layer_analysis":
404 return {
405 "type": card_type,
406 "data": {
407 "stability": result.get("stability"),
408 "ood": result.get("ood"),
409 "localize": result.get("localize"),
410 },
411 }
412
413 if tool_name == "run_sae_stats":
414 if result.get("error"):
415 return None
416 return {
417 "type": card_type,
418 "data": {
419 "modelId": result.get("model_id"),
420 "mode": result.get("mode"),
421 "nProbes": result.get("n_probes"),
422 "topK": result.get("top_k"),
423 "layersRequested": result.get("layers_requested") or [],
424 "layerProfile": result.get("layer_profile") or [],
425 "layerStats": result.get("layer_stats") or [],
426 "heatmap": result.get("heatmap") or {},
427 "probes": result.get("probes") or [],
428 "savedTo": result.get("saved_to"),
429 },
430 }
431
432 if tool_name == "run_confidence_analysis":
433 if result.get("error"):
434 return None
435 return {
436 "type": card_type,
437 "data": {
438 "modelId": result.get("model_id"),
439 "mode": result.get("mode"),
440 "nProbes": result.get("n_probes"),
441 "threshold": result.get("threshold"),
442 "meanConfidence": result.get("mean_confidence"),
443 "aggregateEceProxy": result.get("aggregate_ece_proxy"),
444 "lowConfidenceCount": result.get("low_confidence_count"),
445 "joinSae": result.get("join_sae"),
446 "saeLayer": result.get("sae_layer"),
447 "stressorSummary": result.get("stressor_summary") or [],
448 "heatmap": result.get("heatmap") or {},
449 "probes": result.get("probes") or [],
450 "savedTo": result.get("saved_to"),
451 },
452 }
453
454 if tool_name == "run_weight_diff":
455 if result.get("error"):
456 return None
457 return {
458 "type": card_type,
459 "data": {
460 "baseModelId": result.get("baseModelId"),
461 "ftCheckpointName": result.get("ftCheckpointName"),
462 "checkpointPath": result.get("checkpointPath"),
463 "modelMode": result.get("modelMode"),
464 "deltaMode": result.get("deltaMode"),
465 "trainingStep": result.get("trainingStep"),
466 "nMatrices": result.get("nMatrices"),
467 "totalDeltaL2": result.get("totalDeltaL2"),
468 "maxDeltaL2": result.get("maxDeltaL2"),
469 "meanDeltaStableRank": result.get("meanDeltaStableRank"),
470 "layerProfile": result.get("layerProfile") or [],
471 "topChanged": result.get("topChanged") or [],
472 "matrices": result.get("matrices") or [],
473 "savedTo": result.get("saved_to"),
474 },
475 }
476
477 if tool_name == "run_merge_analysis":
478 if result.get("error"):
479 return None
480 behavioral = result.get("behavioralDiff")
481 return {
482 "type": card_type,
483 "data": {
484 "baseModelId": result.get("baseModelId"),
485 "ftCheckpointName": result.get("ftCheckpointName"),
486 "checkpointPath": result.get("checkpointPath"),
487 "modelMode": result.get("modelMode"),
488 "deltaMode": result.get("deltaMode"),
489 "trainingStep": result.get("trainingStep"),
490 "mergeVerdict": result.get("mergeVerdict"),
491 "warnings": result.get("warnings") or [],
492 "nMatrices": result.get("nMatrices"),
493 "totalDeltaL2": result.get("totalDeltaL2"),
494 "maxDeltaL2": result.get("maxDeltaL2"),
495 "meanDeltaStableRank": result.get("meanDeltaStableRank"),
496 "collapseSignals": result.get("collapseSignals") or [],
497 "topChanged": result.get("topChanged") or [],
498 "layerProfile": result.get("layerProfile") or [],
499 "withBehavioral": result.get("withBehavioral"),
500 "behavioralDiff": behavioral,
501 "behavioralError": result.get("behavioralError"),
502 "savedTo": result.get("saved_to"),
503 },
504 }
505
506 if tool_name == "run_trajectory_analysis":
507 if result.get("error"):
508 return None
509 return {
510 "type": card_type,
511 "data": {
512 "baseModelId": result.get("baseModelId"),
513 "nCheckpoints": result.get("nCheckpoints"),
514 "nAnalyzed": result.get("nAnalyzed"),
515 "peakStep": result.get("peakStep"),
516 "peakTotalDeltaL2": result.get("peakTotalDeltaL2"),
517 "steps": result.get("steps") or [],
518 "savedTo": result.get("saved_to"),
519 },
520 }
521
522 if tool_name == "run_residual_drift":
523 if result.get("error"):
524 return None
525 return {
526 "type": card_type,
527 "data": {
528 "baseModelId": result.get("baseModelId"),
529 "ftCheckpointName": result.get("ftCheckpointName"),
530 "checkpointPath": result.get("checkpointPath"),
531 "modelMode": result.get("modelMode"),
532 "activationMode": result.get("activationMode"),
533 "trainingStep": result.get("trainingStep"),
534 "nProbes": result.get("nProbes"),
535 "nLayers": result.get("nLayers"),
536 "meanDrift": result.get("meanDrift"),
537 "maxDrift": result.get("maxDrift"),
538 "peakLayer": result.get("peakLayer"),
539 "layerProfile": result.get("layerProfile") or [],
540 "topLayers": result.get("topLayers") or [],
541 "perProbe": result.get("perProbe") or [],
542 "savedTo": result.get("saved_to"),
543 },
544 }
545
546 if tool_name == "ensure_umap_loaded":
547 points = result.get("points") or []
548 return {
549 "type": card_type,
550 "data": {
551 "modelId": str(result.get("model_id", "")),
552 "nFeatures": int(result.get("n_features") or len(points)),
553 "nPoints": int(result.get("n_points") or len(points)),
554 "points": points,
555 },
556 }
557
558 if tool_name == "run_simulation":
559 if result.get("error"):
560 return None
561 return {"type": card_type, "data": simulation_full_card_data(result)}
562
563 if result.get("error"):
564 return None
565 summary = result.get("result")
566 if not isinstance(summary, dict) or summary.get("meanApSim") is None:
567 return None
568 return {"type": card_type, "data": summary}
569
570 if tool_name == "list_simulation_runs":
571 return {"type": card_type, "data": simulation_list_card_data(result)}
572
573 if tool_name == "load_simulation_run":
574 if result.get("status") == "not_found" or result.get("error"):
575 return None
576 data = load_simulation_card_data(result)
577 if not data:
578 return None
579 return {"type": card_type, "data": data}
580
581 if tool_name == "compare_simulations":
582 if result.get("error"):
583 return None
584 kind = result.get("kind")
585 comp = result.get("comparison") if isinstance(result.get("comparison"), dict) else {}
587 if not data:
588 return None
589 return {"type": card_type, "data": data}
590
591 if tool_name == "dataset_generate":
592 if result.get("error"):
593 return None
594 return {"type": "datasetGenerated", "data": result}
595
596 anisotropy = result.get("anisotropy")
597 intrinsic = result.get("intrinsicDim") or result.get("intrinsic_dim")
598 model_id = result.get("model_id")
599 if isinstance(anisotropy, dict) and model_id and not anisotropy.get("model_id"):
600 anisotropy = {**anisotropy, "model_id": model_id}
601 if isinstance(intrinsic, dict) and model_id and not intrinsic.get("model_id"):
602 intrinsic = {**intrinsic, "model_id": model_id}
603 if not anisotropy and not intrinsic:
604 return None
605 return {
606 "type": card_type,
607 "data": {
608 "anisotropy": anisotropy,
609 "intrinsicDim": intrinsic,
610 },
611 }
612
613 # Default: wrap full result as data
614 return {"type": card_type, "data": result}
dict|None steer_card_data(dict result)
dict simulation_list_card_data(dict result)
dict|None steer_vector_card_data(dict result)
dict simulation_full_card_data(dict result)
dict _simulation_config_from_meta(dict meta)
dict load_simulation_card_data(dict result)
str _simulation_method_from_meta(dict meta)
dict simulation_comparison_card_data(dict result)
dict|None to_card(str tool_name, dict result)