AQIT 0.1.0
Loading...
Searching...
No Matches
inspect.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
6from __future__ import annotations
7
8from typing import Any
9
10from .registry import register
11
12
14 model_id: str,
15 layer_raw: Any,
16 *,
17 memory: dict[str, Any] | None = None,
18 command: str = "trace",
19) -> int:
20 """Resolve SAE layer from explicit arg or session memory; never silently default."""
21 from aquin.compute.model_loader import require_sae_layer
22
23 layer: int | None = int(layer_raw) if layer_raw is not None else None
24 if layer is None and memory:
25 mem_layer = memory.get("lastSaeLayer")
26 if mem_layer is not None:
27 layer = int(mem_layer)
28 return require_sae_layer(model_id, layer, command=command)
29
30
31def _build_color_map(attribution: list[dict]) -> dict | None:
32 feature_color: dict[int, int] = {}
33 counter = [0]
34
35 def _color(fidx: int) -> int:
36 if fidx not in feature_color:
37 feature_color[fidx] = counter[0] % 10
38 counter[0] += 1
39 return feature_color[fidx]
40
41 prompt_cm: dict[int, int] = {}
42 response_cm: dict[int, int] = {}
43 for entry in attribution:
44 ri = entry["response_ti"]
45 for feat in entry.get("driven_by_features", []):
46 cidx = _color(feat["feature_idx"])
47 response_cm[ri] = cidx
48 for pi in feat.get("also_in_prompt_positions", []):
49 prompt_cm[pi] = cidx
50
51 if not prompt_cm and not response_cm:
52 return None
53 return {"prompt": prompt_cm, "response": response_cm}
54
55
56def _build_trace_results(logit_lens: list[dict]) -> list[dict]:
57 if not logit_lens:
58 return []
59 results = []
60 final_prob = logit_lens[-1]["top_tokens"][0]["prob"] if logit_lens[-1].get("top_tokens") else 0.0
61 for i, row in enumerate(logit_lens):
62 prob = row["top_tokens"][0]["prob"] if row.get("top_tokens") else 0.0
63 next_row = logit_lens[i + 1] if i + 1 < len(logit_lens) else None
64 next_prob = next_row["top_tokens"][0]["prob"] if next_row and next_row.get("top_tokens") else prob
65 delta = max(0.0, next_prob - prob)
66 results.append({
67 "layer": row["layer"],
68 "drop": round(delta, 4),
69 "attn_drop": round(delta * 0.6, 4),
70 "mlp_drop": round(delta * 0.4, 4),
71 "baseline_prob": round(final_prob, 4),
72 })
73 return results
74
75
76@register("run_full_inspection", {
77 "type": "function",
78 "function": {
79 "name": "run_full_inspection",
80 "description": (
81 "Run full inspection: generate a response, extract top SAE features at the given layer, "
82 "compute attribution, and run logit lens. layer must match a downloaded SAE "
83 "(aquin load sae <model>-l<n>). Omit layer to reuse lastSaeLayer from session memory. "
84 "Errors with available layers if the checkpoint is missing."
85 ),
86 "parameters": {
87 "type": "object",
88 "properties": {
89 "prompt": {"type": "string", "description": "The prompt to inspect"},
90 "layer": {
91 "type": "integer",
92 "description": (
93 "SAE layer index (aquin load sae <model>-l<n>). "
94 "Optional if lastSaeLayer is in session memory."
95 ),
96 },
97 },
98 "required": ["prompt"],
99 },
100 },
101})
102def run_full_inspection(args: dict[str, Any], ctx: dict[str, Any]) -> dict[str, Any]:
103 from aquin.compute.model_loader import get_active_model_id, get_loaded_model, load_model, require_sae_layer, resolve_model_id
104 from aquin.compute.causal_trace import run_chat, run_logit_lens
105 from aquin.compute.feature_analysis import run_feature_analysis_unlabeled
106 from aquin.engine.session_memory_store import load_local_memory, save_local_memory
107
108 session_id: str = ctx.get("session_id") or ""
109 state: dict[str, Any] = ctx.get("state", {})
110
111 prompt: str = args.get("prompt", "") or state.get("lastPrompt") or "Hello"
112 model_id: str = get_active_model_id() or state.get("activeModelId") or "llama-3.2-1b"
113 layer_raw = args.get("layer")
114 layer: int | None = int(layer_raw) if layer_raw is not None else None
115
116 try:
117 model_id = resolve_model_id(model_id)
118 except Exception:
119 return {"error": f"Unknown model '{model_id}'. Run: aquin load --model <model-id>"}
120
121 try:
123 model_id,
124 layer_raw,
125 memory=state.get("memory") or {},
126 command="trace",
127 )
128 except ValueError as e:
129 return {"error": str(e)}
130
131 model = get_loaded_model()
132 if model is None:
133 try:
134 model = load_model(model_id)
135 except Exception as e:
136 return {"error": f"Could not load model: {e}"}
137
138 # 1. Generate response
139 try:
140 response = run_chat(prompt, model_id=model_id, max_new_tokens=200, temperature=0.7)
141 except Exception as e:
142 return {"error": f"Generation failed: {e}"}
143
144 # 2. SAE feature analysis
145 try:
146 feat_result = run_feature_analysis_unlabeled(
147 prompt, response, model, model_id=model_id, layer=sae_layer,
148 )
149 except Exception as e:
150 return {"error": f"Feature analysis failed: {e}"}
151
152 actual_layer = feat_result.get("sae_layer")
153 if actual_layer is not None and int(actual_layer) != int(sae_layer):
154 return {
155 "error": (
156 f"SAE layer mismatch: requested layer {sae_layer}, "
157 f"feature analysis ran at layer {actual_layer}."
158 ),
159 }
160
161 prompt_tokens: list[str] = feat_result.get("prompt_tokens", [])
162 response_tokens: list[str] = feat_result.get("response_tokens", [])
163 top_features: list[dict] = feat_result.get("top_response_features", [])
164 attribution: list[dict] = feat_result.get("attribution", [])
165 sae_layer: int = feat_result.get("sae_layer", sae_layer)
166
167 # Causal labels for displayed features (cached; uses last prompt context)
168 try:
169 from aquin.compute.feature_analysis import label_inspection_features
170 from aquin.compute.openai_client import get_openai_client
171 label_inspection_features(
172 feat_result,
173 prompt=prompt,
174 model=model,
175 client=get_openai_client(ctx),
176 model_id=model_id,
177 layer=sae_layer,
178 )
179 top_features = feat_result.get("top_response_features", [])
180 attribution = feat_result.get("attribution", [])
181 except Exception as e:
182 print(f"[inspect] feature labeling failed: {e}", flush=True)
183
184 # 3. Logit lens
185 try:
186 logit_lens = run_logit_lens(prompt, model_id=model_id, top_k=5)
187 except Exception as e:
188 print(f"[inspect] logit-lens failed: {e}", flush=True)
189 logit_lens = []
190
191 color_map = _build_color_map(attribution)
192 trace_results = _build_trace_results(logit_lens)
193 trace_target = response_tokens[0].strip() if response_tokens else ""
194
195 panel_card: dict[str, Any] = {
196 "type": "inspectionFull",
197 "data": {
198 "prompt": prompt,
199 "response": response,
200 "modelId": model_id,
201 "topFeatures": top_features,
202 "promptTokens": prompt_tokens,
203 "responseTokens": response_tokens,
204 "attribution": attribution,
205 "logitLensResults": logit_lens,
206 "traceResults": trace_results,
207 "traceTarget": trace_target,
208 "colorMap": color_map,
209 "saeLayer": sae_layer,
210 },
211 }
212
213 mem = load_local_memory(session_id or "local")
214 mem.update({
215 "lastPrompt": prompt,
216 "lastResponse": response,
217 "lastTopFeatures": top_features[:5],
218 "lastSaeLayer": sae_layer,
219 })
220 save_local_memory(session_id or "local", mem)
221 state.setdefault("memory", {})
222 state["memory"].update(mem)
223
224 return {
225 "content": {
226 "prompt": prompt,
227 "response": response,
228 "model_id": model_id,
229 "top_features": top_features,
230 "sae_layer": sae_layer,
231 },
232 "card": panel_card,
233 }
234
235
236@register("get_feature_logits", {
237 "type": "function",
238 "function": {
239 "name": "get_feature_logits",
240 "description": (
241 "Get top tokens boosted and suppressed by a specific SAE feature. "
242 "Use the same layer as the last inspection (or pass layer explicitly)."
243 ),
244 "parameters": {
245 "type": "object",
246 "properties": {
247 "feature_idx": {"type": "number", "description": "SAE feature index"},
248 "layer": {
249 "type": "number",
250 "description": "SAE layer (defaults to lastSaeLayer from session memory).",
251 },
252 "top_k": {"type": "number", "description": "Number of tokens to return (default 10)."},
253 },
254 "required": ["feature_idx"],
255 },
256 },
257})
258def get_feature_logits(args: dict[str, Any], ctx: dict[str, Any]) -> dict[str, Any]:
259 from aquin.compute.model_loader import (
260 get_loaded_llm_id,
261 get_loaded_model,
262 load_model,
263 resolve_model_id,
264 )
265 from aquin.compute.feature_analysis import get_feature_logits as _get_logits
266 from aquin.compute.model_runtime import resident_from_cache
267
268 state: dict[str, Any] = ctx.get("state", {}) if isinstance(ctx.get("state"), dict) else {}
269 resident, _ = resident_from_cache()
270 model_id = (
271 state.get("activeModelId")
272 or args.get("model_id")
273 or get_loaded_llm_id()
274 or resident
275 )
276 if not model_id:
277 return {
278 "error": "No model loaded. Ask the user to load one via the Model picker.",
279 }
280 feature_idx: int = int(args.get("feature_idx", 0))
281 layer_raw = args.get("layer")
282 try:
283 top_k = int(args.get("top_k") or 10)
284 except (TypeError, ValueError):
285 top_k = 10
286
287 try:
288 model_id = resolve_model_id(str(model_id))
289 except Exception:
290 return {"error": f"Unknown model '{model_id}'"}
291
292 try:
294 model_id,
295 layer_raw,
296 memory=state.get("memory") or {},
297 command="feature logit",
298 )
299 except ValueError as e:
300 return {"error": str(e)}
301
302 model = get_loaded_model()
303 if model is None:
304 try:
305 model = load_model(model_id)
306 except Exception as e:
307 return {"error": str(e)}
308
309 try:
310 from aquin.compute.feature_analysis import enrich_feature_tool_result, prompt_for_labeling
311 from aquin.compute.openai_client import get_openai_client
312 result = _get_logits(feature_idx, model, model_id=model_id, layer=sae_layer, top_k=top_k)
313 enrich_feature_tool_result(
314 result,
315 prompt=prompt_for_labeling(ctx, args),
316 model=model,
317 client=get_openai_client(ctx),
318 model_id=model_id,
319 layer=sae_layer,
320 )
321 return {"content": result}
322 except Exception as e:
323 from aquin.user_errors import friendly_message
324 return {"error": friendly_message(e)}
325
326
327@register("get_feature_neighbors", {
328 "type": "function",
329 "function": {
330 "name": "get_feature_neighbors",
331 "description": (
332 "Find SAE features nearest to a given feature by cosine similarity. "
333 "Use the same layer as the last inspection (or pass layer explicitly)."
334 ),
335 "parameters": {
336 "type": "object",
337 "properties": {
338 "feature_idx": {"type": "number", "description": "SAE feature index"},
339 "layer": {
340 "type": "number",
341 "description": "SAE layer (defaults to lastSaeLayer from session memory).",
342 },
343 "top_k": {"type": "number", "description": "Number of neighbors (default 8)."},
344 },
345 "required": ["feature_idx"],
346 },
347 },
348})
349def get_feature_neighbors(args: dict[str, Any], ctx: dict[str, Any]) -> dict[str, Any]:
350 from aquin.compute.model_loader import (
351 get_loaded_llm_id,
352 get_loaded_model,
353 load_model,
354 resolve_model_id,
355 )
357 enrich_feature_tool_result,
358 get_feature_neighbors as _get_neighbors,
359 prompt_for_labeling,
360 )
361 from aquin.compute.openai_client import get_openai_client
362 from aquin.compute.model_runtime import resident_from_cache
363
364 state: dict[str, Any] = ctx.get("state", {}) if isinstance(ctx.get("state"), dict) else {}
365 resident, _ = resident_from_cache()
366 model_id = (
367 state.get("activeModelId")
368 or args.get("model_id")
369 or get_loaded_llm_id()
370 or resident
371 )
372 if not model_id:
373 return {
374 "error": "No model loaded. Ask the user to load one via the Model picker.",
375 }
376 feature_idx: int = int(args.get("feature_idx", 0))
377 layer_raw = args.get("layer")
378 try:
379 top_k = int(args.get("top_k") or 8)
380 except (TypeError, ValueError):
381 top_k = 8
382
383 try:
384 model_id = resolve_model_id(str(model_id))
385 except Exception:
386 return {"error": f"Unknown model '{model_id}'"}
387
388 try:
390 model_id,
391 layer_raw,
392 memory=state.get("memory") or {},
393 command="feature neighbor",
394 )
395 except ValueError as e:
396 return {"error": str(e)}
397
398 model = get_loaded_model()
399 if model is None:
400 try:
401 model = load_model(model_id)
402 except Exception as e:
403 return {"error": str(e)}
404
405 try:
406 result = _get_neighbors(feature_idx, model_id=model_id, layer=sae_layer, top_k=top_k)
407 enrich_feature_tool_result(
408 result,
409 prompt=prompt_for_labeling(ctx, args),
410 model=model,
411 client=get_openai_client(ctx),
412 model_id=model_id,
413 layer=sae_layer,
414 label_neighbors=True,
415 )
416 return {"content": result}
417 except Exception as e:
418 from aquin.user_errors import friendly_message
419 return {"error": friendly_message(e)}
dict[str, Any] run_full_inspection(dict[str, Any] args, dict[str, Any] ctx)
Definition inspect.py:106
dict|None _build_color_map(list[dict] attribution)
Definition inspect.py:35
dict[str, Any] get_feature_neighbors(dict[str, Any] args, dict[str, Any] ctx)
Definition inspect.py:353
int _resolve_inspection_sae_layer(str model_id, Any layer_raw, *, dict[str, Any]|None memory=None, str command="trace")
Definition inspect.py:23
list[dict] _build_trace_results(list[dict] logit_lens)
Definition inspect.py:60
dict[str, Any] get_feature_logits(dict[str, Any] args, dict[str, Any] ctx)
Definition inspect.py:262