AQIT 0.1.0
Loading...
Searching...
No Matches
local_server.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"""
7Local HTTP server on 127.0.0.1 — started as a daemon thread during `aquin chat`.
8Default port: AQUIN_ENGINE_PORT → ~/.aquin/engine-info.json → 17832 (not 8002).
9Handles routes from inspection-backend so the web app can call GPU tools
10without a VM.
11"""
12from __future__ import annotations
13
14import json
15import threading
16from http.server import BaseHTTPRequestHandler, HTTPServer
17from typing import Any
18
19from aquin.engine.engine_info import DEFAULT_ENGINE_PORT, resolve_engine_port
20
21PORT = DEFAULT_ENGINE_PORT
22_server: HTTPServer | None = None
23_thread: threading.Thread | None = None
24# True only when this process is the standalone model daemon (run_forever),
25# not the short-lived thread server started during `aquin chat`.
26_is_daemon: bool = False
27# Atomic switch accept (HTTP is usually one-at-a-time; bg tools can interleave).
28_switch_accept_lock = threading.Lock()
29# Shared label cache (populated by label-features/stream, read by feature-neighbors)
30_session_label_cache: dict[tuple[int, str], str] = {}
31
33def _current_loaded_model_id() -> str | None:
34 from aquin.compute.model_runtime import resident_from_cache
35
36 resident, _ = resident_from_cache()
37 return resident
38
39
40def _load_model_in_background(requested_model_id: str) -> None:
41 try:
42 from aquin.compute.model_runtime import load_weights
43 from aquin.engine.engine_info import set_daemon_state
45 slug = load_weights(requested_model_id)
46 try:
47 set_daemon_state(status="running", model_id=slug)
48 except Exception:
49 pass
50 except Exception as exc:
51 try:
52 from aquin.compute.model_runtime import fail_load
53
54 fail_load(str(requested_model_id), str(exc) or exc.__class__.__name__)
55 except Exception:
56 pass
57 print(f"[daemon] model load failed: {exc}", flush=True)
58
59
60def _read_body(handler: BaseHTTPRequestHandler) -> dict:
61 length = int(handler.headers.get("Content-Length", 0))
62 raw = handler.rfile.read(length) if length else b"{}"
63 try:
64 return json.loads(raw)
65 except Exception:
66 return {}
67
68
69def _respond(handler: BaseHTTPRequestHandler, data: Any, status: int = 200) -> None:
70 # allow_nan=False path: strip NaN/Inf so Electron/Node never see invalid JSON.
71 from aquin.engine.json_safe import dumps_json_safe
72
73 body = dumps_json_safe(data).encode()
74 handler.send_response(status)
75 handler.send_header("Content-Type", "application/json")
76 handler.send_header("Content-Length", str(len(body)))
77 handler.send_header("Access-Control-Allow-Origin", "*")
78 handler.end_headers()
79 handler.wfile.write(body)
80
81
82def _respond_error(handler: BaseHTTPRequestHandler, msg: str, status: int = 500) -> None:
83 _respond(handler, {"error": msg}, status)
84
85
86class _Handler(BaseHTTPRequestHandler):
87 def log_message(self, *args):
88 pass # suppress access log
89
90 def do_OPTIONS(self):
91 self.send_response(204)
92 self.send_header("Access-Control-Allow-Origin", "*")
93 self.send_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
94 self.send_header("Access-Control-Allow-Headers", "Content-Type, Authorization, x-tab-id")
95 self.end_headers()
96
97 def do_GET(self):
98 path = self.path.split("?")[0]
99 params = {}
100 if "?" in self.path:
101 import urllib.parse
102 params = dict(urllib.parse.parse_qsl(self.path.split("?", 1)[1]))
103
104 if path == "/health":
105 self._handle_health()
106 elif path == "/model/status":
108 elif path == "/sae-layers":
109 self._handle_sae_layers(params)
110 elif path == "/model/load":
111 self._handle_model_load(params)
112 else:
113 _respond_error(self, f"Not found: {path}", 404)
114
115 def do_POST(self):
116 path = self.path.split("?")[0]
117
118 handlers = {
119 "/feature-logits": self._handle_feature_logits,
120 "/feature-neighbors": self._handle_feature_neighbors,
121 "/umap": self._handle_umap,
122 "/label-features/stream": self._handle_label_features_stream,
123 "/features": self._handle_features,
124 "/logit-lens": self._handle_logit_lens,
125 "/trace": self._handle_trace,
126 "/attribution": self._handle_attribution,
127 "/interp-score": self._handle_interp_score,
128 "/steer": self._handle_steer,
129 "/steer-multi": self._handle_steer_multi,
130 "/evals/consistency": self._handle_evals_consistency,
131 "/evals/suppression": self._handle_evals_suppression,
132 "/evals/boundary": self._handle_evals_boundary,
133 "/chat/inspect": self._handle_chat_inspect,
134 # Persistent model daemon control + generic tool dispatch
135 "/dispatch": self._handle_dispatch,
136 "/tools/execute": self._handle_dispatch,
137 "/model/switch": self._handle_model_switch,
138 "/sae/load": self._handle_sae_load,
139 "/prompt": self._handle_prompt,
140 "/unload": self._handle_unload,
141 "/shutdown": self._handle_shutdown,
142 }
143
144 handler_fn = handlers.get(path)
145 if handler_fn:
146 handler_fn()
147 else:
148 _respond_error(self, f"Unknown path: {path}", 404)
149
150 # ── GET handlers ──────────────────────────────────────────────────────────
151
152 def _handle_health(self):
153 from aquin.compute.model_runtime import health_payload
154
155 _respond(self, health_payload(daemon=_is_daemon, port=PORT))
157 def _handle_model_status(self):
158 from aquin.compute.model_runtime import health_payload, snapshot
159
160 payload = health_payload(daemon=_is_daemon, port=PORT)
161 snap = snapshot()
162 _respond(
163 self,
164 {
165 "model_id": payload.get("model_id"),
166 "daemon": _is_daemon,
167 "model_status": payload.get("model_status"),
168 "loading_model_id": payload.get("loading_model_id"),
169 "last_error": payload.get("last_error"),
170 "model_kind": snap.kind,
171 "elapsed_s": payload.get("elapsed_s"),
172 "vram": payload.get("vram"),
173 },
174 )
175
176 # ── Persistent daemon: generic dispatch + model lifecycle ─────────────────
177
178 def _handle_dispatch(self):
179 body = _read_body(self)
180 name = body.get("name")
181 args = body.get("args") or {}
182 ctx = body.get("ctx") or {}
183 if not name:
184 _respond_error(self, "name required", 400)
185 return
186 try:
187 from aquin.engine.tools.registry import dispatch, _load_stubs
188 _load_stubs()
189 try:
190 from aquin.compute.loader_shim import apply as _shim_apply
191 _shim_apply()
192 except Exception:
193 pass
194 result = dispatch(name, args, ctx)
195 _respond(self, {"ok": True, "result": result})
196 except NotImplementedError as e:
197 _respond(self, {"ok": False, "error": str(e) or "not implemented"})
198 except Exception as e:
199 _respond(self, {"ok": False, "error": str(e)})
200
201 def _handle_model_switch(self):
202 body = _read_body(self)
203 model_id = body.get("model_id")
204 if not model_id:
205 _respond_error(self, "model_id required", 400)
206 return
207 try:
208 from aquin.compute.model_runtime import (
209 ModelPhase,
210 begin_load,
211 finish_load,
212 resolve_kind,
213 snapshot,
214 )
215
216 resolved, kind = resolve_kind(model_id)
217 with _switch_accept_lock:
218 current = snapshot()
219 if current.phase == ModelPhase.LOADING:
220 if current.target_id == resolved:
221 _respond(
222 self,
223 {
224 "ok": True,
225 "accepted": True,
226 "status": "loading",
227 "model_id": resolved,
228 },
229 )
230 return
231 _respond(
232 self,
233 {
234 "ok": False,
235 "error": (
236 f"background engine is already loading {current.target_id}. "
237 "Cancel that load first (Remove / Cancel in the Model picker)."
238 ),
239 },
240 )
241 return
242
243 loaded = _current_loaded_model_id()
244 if loaded == resolved:
245 finish_load(resolved, kind)
246 _respond(
247 self,
248 {
249 "ok": True,
250 "accepted": False,
251 "status": "ready",
252 "model_id": resolved,
253 },
254 )
255 return
256
257 # Mark LOADING before spawn so concurrent switch/tools see it.
258 begin_load(model_id)
259
260 threading.Thread(
261 target=_load_model_in_background,
262 args=(model_id,),
263 daemon=True,
264 ).start()
265 _respond(
266 self,
267 {"ok": True, "accepted": True, "status": "loading", "model_id": resolved},
268 )
269 except Exception as e:
270 _respond(self, {"ok": False, "error": str(e)})
271
272 def _handle_sae_load(self):
273 body = _read_body(self)
274 model_id = body.get("model_id")
275 layer = body.get("layer")
276 if not model_id or layer is None:
277 _respond_error(self, "model_id and layer required", 400)
278 return
279 try:
280 from aquin.compute.model_loader import (
281 resolve_model_id, get_loaded_model, load_model, load_sae, evict_sae_cache,
282 )
283 mid = resolve_model_id(model_id)
284 evict_sae_cache(mid, int(layer)) # force a fresh read after a (re)bind
285 model = get_loaded_model() or load_model(mid)
286 load_sae(model, int(layer), mid)
287 _respond(self, {"ok": True, "model_id": mid, "layer": int(layer)})
288 except Exception as e:
289 _respond(self, {"ok": False, "error": str(e)})
290
291 def _handle_prompt(self):
292 """Quick try-out completion against the resident model."""
293 body = _read_body(self)
294 prompt = (body.get("prompt") or "").strip()
295 if not prompt:
296 _respond_error(self, "prompt required", 400)
297 return
298 try:
299 from aquin.compute.causal_trace import run_chat
300 from aquin.compute.model_loader import get_active_model_id
301
302 model_id = (body.get("model_id") or "").strip() or _current_loaded_model_id() or get_active_model_id()
303 if not model_id:
304 _respond(self, {"ok": False, "error": "no model loaded — run: aquin load model <id>"})
305 return
306 max_new = int(body.get("max_new_tokens") or body.get("max_tokens") or 200)
307 temperature = float(body.get("temperature", 0.7))
308 response = run_chat(
309 prompt,
310 model_id=model_id,
311 max_new_tokens=max_new,
312 temperature=temperature,
313 )
314 _respond(
315 self,
316 {
317 "ok": True,
318 "model_id": model_id,
319 "prompt": prompt,
320 "response": response,
321 },
322 )
323 except Exception as e:
324 _respond(self, {"ok": False, "error": str(e)})
325
326 def _handle_unload(self):
327 try:
328 from aquin.compute.model_runtime import unload_weights
329 from aquin.engine.engine_info import set_daemon_state
331 unload_weights()
332 try:
333 set_daemon_state(status="running", model_id=None)
334 except Exception:
335 pass
336 _respond(self, {"ok": True})
337 except Exception as e:
338 _respond(self, {"ok": False, "error": str(e)})
339
340 def _handle_shutdown(self):
341 _respond(self, {"ok": True})
342 threading.Thread(target=stop, daemon=True).start()
343
344 def _handle_sae_layers(self, params: dict):
345 model_id = params.get("model_id", "llama-3.2-1b")
346 try:
347 from aquin.compute.model_loader import get_config, resolve_model_id
348 model_id = resolve_model_id(model_id)
349 cfg = get_config(model_id)
350 layer = cfg.get("sae_layer", 8)
351 _respond(self, {"model_id": model_id, "layers": [layer], "default": layer})
352 except Exception as e:
353 _respond_error(self, str(e))
354
355 def _handle_model_load(self, params: dict):
356 model_id = params.get("model_id", "llama-3.2-1b")
357 self.send_response(200)
358 self.send_header("Content-Type", "text/event-stream")
359 self.send_header("Cache-Control", "no-cache")
360 self.send_header("Access-Control-Allow-Origin", "*")
361 self.end_headers()
362
363 def _sse(data: dict) -> None:
364 line = f"data: {json.dumps(data)}\n\n"
365 self.wfile.write(line.encode())
366 self.wfile.flush()
367
368 try:
369 from aquin.compute.model_runtime import ModelPhase, load_weights, snapshot
370 from aquin.compute.model_loader import resolve_model_id
371
372 model_id = resolve_model_id(model_id)
373 snap = snapshot()
374 if snap.phase == ModelPhase.LOADING and snap.target_id != model_id:
375 _sse(
376 {
377 "stage": "error",
378 "message": (
379 f"already loading {snap.target_id}; cancel first"
380 ),
381 }
382 )
383 return
384 _sse({"stage": "model", "message": f"Loading {model_id}..."})
385 slug = load_weights(model_id)
386 _sse({"stage": "done", "model_id": slug})
387 except Exception as e:
388 _sse({"stage": "error", "message": str(e)})
389
390 # ── POST handlers ─────────────────────────────────────────────────────────
391
392 def _handle_feature_logits(self):
393 body = _read_body(self)
394 feature_idx = body.get("feature_idx")
395 model_id = body.get("model_id", "llama-3.2-1b")
396 layer = body.get("layer")
397 top_k = int(body.get("top_k", 10))
398
399 if feature_idx is None:
400 _respond_error(self, "feature_idx required", 400)
401 return
402
403 try:
404 from aquin.compute.model_loader import get_loaded_model, load_model, resolve_model_id
405 from aquin.compute.feature_analysis import get_feature_logits
406
407 model_id = resolve_model_id(model_id)
408 model = get_loaded_model()
409 if model is None:
410 model = load_model(model_id)
411
412 from aquin.compute.feature_analysis import enrich_feature_tool_result, prompt_for_labeling
413 from aquin.compute.openai_client import get_openai_client
414
415 result = get_feature_logits(int(feature_idx), model, model_id=model_id, layer=layer, top_k=top_k)
416 enrich_feature_tool_result(
417 result,
418 prompt=prompt_for_labeling(),
419 model=model,
420 client=get_openai_client(),
421 model_id=model_id,
422 layer=layer,
423 )
424 _respond(self, result)
425 except Exception as e:
426 _respond_error(self, str(e))
427
429 body = _read_body(self)
430 feature_idx = body.get("feature_idx")
431 model_id = body.get("model_id", "llama-3.2-1b")
432 layer = body.get("layer")
433 top_k = int(body.get("top_k", 8))
434
435 if feature_idx is None:
436 _respond_error(self, "feature_idx required", 400)
437 return
438
439 try:
440 from aquin.compute.model_loader import get_loaded_model, load_model, resolve_model_id
442 enrich_feature_tool_result,
443 get_feature_neighbors,
444 prompt_for_labeling,
445 )
446 from aquin.compute.openai_client import get_openai_client
447
448 model_id = resolve_model_id(model_id)
449 model = get_loaded_model()
450 if model is None:
451 model = load_model(model_id)
452 result = get_feature_neighbors(int(feature_idx), model_id=model_id, layer=layer, top_k=top_k)
453 enrich_feature_tool_result(
454 result,
455 prompt=prompt_for_labeling(),
456 model=model,
457 client=get_openai_client(),
458 model_id=model_id,
459 layer=layer,
460 label_neighbors=True,
461 )
462 _respond(self, result)
463 except Exception as e:
464 _respond_error(self, str(e))
465
466 def _handle_umap(self):
467 body = _read_body(self)
468 model_id = body.get("model_id", "llama-3.2-1b")
469 layer = body.get("layer")
471 try:
472 from aquin.compute.bridge import _ensure_umap_loaded
473
474 result = _ensure_umap_loaded({"model_id": model_id, "layer": layer}, {})
475 if result.get("error"):
476 _respond_error(self, str(result["error"]), 500)
477 return
478 _respond(self, {"points": result.get("points", []), "cached": False})
479 except Exception as e:
480 _respond_error(self, str(e))
481
483 body = _read_body(self)
484 prompt = body.get("prompt", "")
485 response = body.get("response", "")
486 model_id = body.get("model_id", "llama-3.2-1b")
487 layer = body.get("layer")
488
489 try:
490 from aquin.compute.model_loader import get_loaded_model, load_model, resolve_model_id
491 from aquin.compute.feature_analysis import label_inspection_features, run_feature_analysis_unlabeled
492 from aquin.compute.openai_client import get_openai_client
493
494 model_id = resolve_model_id(model_id)
495 model = get_loaded_model()
496 if model is None:
497 model = load_model(model_id)
498
499 feat = run_feature_analysis_unlabeled(prompt, response, model, model_id=model_id, layer=layer)
500 label_inspection_features(
501 feat,
502 prompt=prompt,
503 model=model,
504 client=get_openai_client(),
505 model_id=model_id,
506 layer=layer,
507 )
508
509 self.send_response(200)
510 self.send_header("Content-Type", "text/event-stream")
511 self.send_header("Cache-Control", "no-cache")
512 self.send_header("Access-Control-Allow-Origin", "*")
513 self.end_headers()
514
515 def _sse(data: dict) -> None:
516 line = f"data: {json.dumps(data)}\n\n"
517 self.wfile.write(line.encode())
518 self.wfile.flush()
519
520 _sse({"type": "tokens", **feat})
521
522 seen: set[int] = set()
523 for f in feat.get("top_response_features", []):
524 fi = f["feature_idx"]
525 if fi not in seen:
526 seen.add(fi)
527 _sse({"type": "label", "feature_idx": fi, "label": f.get("label", f"feature_{fi}")})
528 for attr in feat.get("attribution", []):
529 for f in attr.get("driven_by_features", []):
530 fi = f["feature_idx"]
531 if fi not in seen:
532 seen.add(fi)
533 _sse({"type": "label", "feature_idx": fi, "label": f.get("label", f"feature_{fi}")})
534
535 _sse({"type": "done"})
536
537 except Exception as e:
538 self.send_response(500)
539 self.send_header("Content-Type", "application/json")
540 self.end_headers()
541 self.wfile.write(json.dumps({"error": str(e)}).encode())
542
543 def _handle_features(self):
544 body = _read_body(self)
545 prompt = body.get("prompt", "")
546 response = body.get("response", "")
547 model_id = body.get("model_id", "llama-3.2-1b")
548 layer = body.get("layer")
549
550 try:
551 from aquin.compute.model_loader import get_loaded_model, load_model, resolve_model_id
552 from aquin.compute.feature_analysis import run_feature_analysis_unlabeled
553
554 model_id = resolve_model_id(model_id)
555 model = get_loaded_model()
556 if model is None:
557 model = load_model(model_id)
558
559 result = run_feature_analysis_unlabeled(prompt, response, model, model_id=model_id, layer=layer)
560 _respond(self, result)
561 except Exception as e:
562 _respond_error(self, str(e))
563
564 def _handle_logit_lens(self):
565 body = _read_body(self)
566 prompt = body.get("prompt", "")
567 top_k = int(body.get("top_k", 5))
568 model_id = body.get("model_id", "llama-3.2-1b")
569
570 try:
571 from aquin.compute.model_loader import resolve_model_id
572 from aquin.compute.causal_trace import run_logit_lens
573
574 model_id = resolve_model_id(model_id)
575 results = run_logit_lens(prompt, model_id=model_id, top_k=top_k)
576 _respond(self, {"results": results})
577 except Exception as e:
578 _respond_error(self, str(e))
579
580 def _handle_trace(self):
581 body = _read_body(self)
582 prompt = body.get("prompt", "")
583 target = body.get("target", "")
584 model_id = body.get("model_id", "llama-3.2-1b")
585
586 try:
587 from aquin.compute.model_loader import resolve_model_id
588 from aquin.compute.causal_trace import run_trace
589
590 model_id = resolve_model_id(model_id)
591 results = run_trace(prompt, target, model_id=model_id)
592 _respond(self, {"results": results})
593 except Exception as e:
594 _respond_error(self, str(e))
595
596 def _handle_attribution(self):
597 body = _read_body(self)
598 prompt = body.get("prompt", "")
599 response = body.get("response", "")
600 model_id = body.get("model_id", "llama-3.2-1b")
601
602 prompt_tokens = prompt.split(" ")
603 response_tokens = response.split(" ")
604 sig_prompt_tis = list(range(len(prompt_tokens)))
605 sig_response_tis = list(range(len(response_tokens)))
606 noise_scale = float(body.get("noise_scale", 3.0))
607 n_noise_runs = int(body.get("n_noise_runs", 5))
608
609 if "prompt_tokens" in body:
610 prompt_tokens = body["prompt_tokens"]
611 response_tokens = body["response_tokens"]
612 sig_prompt_tis = body.get("sig_prompt_tis", sig_prompt_tis)
613 sig_response_tis = body.get("sig_response_tis", sig_response_tis)
614
615 try:
616 from aquin.compute.model_loader import resolve_model_id
617 from aquin.compute.causal_trace import run_prompt_attribution
618
619 model_id = resolve_model_id(model_id)
620 result = run_prompt_attribution(
621 prompt, response,
622 prompt_tokens, response_tokens,
623 sig_prompt_tis, sig_response_tis,
624 model_id,
625 noise_scale, n_noise_runs,
626 )
627 _respond(self, result)
628 except Exception as e:
629 _respond_error(self, str(e))
630
631 def _handle_interp_score(self):
632 body = _read_body(self)
633 feature_idx = body.get("feature_idx")
634 prompt = body.get("prompt", "")
635 n_samples = int(body.get("n_samples", 10))
636 layer = body.get("layer")
637 model_id = body.get("model_id", "llama-3.2-1b")
638
639 if feature_idx is None:
640 _respond_error(self, "feature_idx required", 400)
641 return
642
643 # interp_score requires OpenAI — not available on user machine
644 _respond_error(self, "interp-score requires OpenAI API key (server-side only)", 503)
645
646 def _handle_steer(self):
647 body = _read_body(self)
648 prompt = body.get("prompt", "")
649 feature_idx = body.get("feature_idx")
650 strength = float(body.get("strength", 20.0))
651 model_id = body.get("model_id", "llama-3.2-1b")
652 max_new_tokens = int(body.get("max_new_tokens", 200))
653 temperature = float(body.get("temperature", 0.7))
654
655 if feature_idx is None:
656 _respond_error(self, "feature_idx required", 400)
657 return
658
659 try:
660 from aquin.compute.model_loader import get_loaded_model, load_model, resolve_model_id
661 from aquin.compute.steer import run_steer_stream
662
663 model_id = resolve_model_id(model_id)
664 model = get_loaded_model()
665 if model is None:
666 model = load_model(model_id)
667
668 self.send_response(200)
669 self.send_header("Content-Type", "text/event-stream")
670 self.send_header("Cache-Control", "no-cache")
671 self.send_header("Access-Control-Allow-Origin", "*")
672 self.end_headers()
673
674 for event in run_steer_stream(
675 prompt=prompt,
676 feature_idx=int(feature_idx),
677 strength=strength,
678 model_id=model_id,
679 max_new_tokens=max_new_tokens,
680 temperature=temperature,
681 ):
682 line = f"data: {json.dumps(event)}\n\n"
683 self.wfile.write(line.encode())
684 self.wfile.flush()
685
686 except Exception as e:
687 try:
688 self.wfile.write(f"data: {json.dumps({'error': str(e)})}\n\n".encode())
689 self.wfile.flush()
690 except Exception:
691 pass
692
693 def _handle_steer_multi(self):
694 body = _read_body(self)
695 _respond_error(self, "steer-multi not yet implemented in local engine", 501)
696
698 body = _read_body(self)
699 query = body.get("query", "")
700 templates = body.get("templates")
701 model_id = body.get("model_id", "llama-3.2-1b")
702
703 if not query:
704 _respond_error(self, "query required", 400)
705 return
706
707 try:
708 from aquin.compute.model_loader import get_loaded_model, load_model, resolve_model_id
709 from aquin.compute.evals import consistency_eval
710
711 model_id = resolve_model_id(model_id)
712 model = get_loaded_model()
713 if model is None:
714 model = load_model(model_id)
715
716 result = consistency_eval(query, model, templates=templates or None)
717 _respond(self, result)
718 except Exception as e:
719 _respond_error(self, str(e))
720
722 body = _read_body(self)
723 topics = body.get("topics")
724 model_id = body.get("model_id", "llama-3.2-1b")
726 try:
727 from aquin.compute.model_loader import get_loaded_model, load_model, resolve_model_id
728 from aquin.compute.evals import suppression_eval
729
730 model_id = resolve_model_id(model_id)
731 model = get_loaded_model()
732 if model is None:
733 model = load_model(model_id)
734
735 result = suppression_eval(model, topics=topics or None)
736 _respond(self, result)
737 except Exception as e:
738 _respond_error(self, str(e))
739
740 def _handle_evals_boundary(self):
741 body = _read_body(self)
742 prompts = body.get("prompts", [])
743 model_id = body.get("model_id", "llama-3.2-1b")
745 if not prompts:
746 _respond_error(self, "prompts list cannot be empty", 400)
747 return
748 if len(prompts) > 20:
749 _respond_error(self, "max 20 prompts per boundary eval run", 400)
750 return
751
752 try:
753 from aquin.compute.model_loader import get_loaded_model, load_model, resolve_model_id
754 from aquin.compute.evals import boundary_eval
755
756 model_id = resolve_model_id(model_id)
757 model = get_loaded_model()
758 if model is None:
759 model = load_model(model_id)
760
761 result = boundary_eval(prompts, model)
762 _respond(self, result)
763 except Exception as e:
764 _respond_error(self, str(e))
765
766 def _handle_chat_inspect(self):
767 body = _read_body(self)
768 messages = body.get("messages", [])
769 model_id = body.get("model_id", "llama-3.2-1b")
770 max_new_tokens = int(body.get("max_new_tokens", 200))
771 temperature = float(body.get("temperature", 0.7))
772
773 user_content = ""
774 for m in reversed(messages):
775 if m.get("role") == "user":
776 user_content = m.get("content", "")
777 break
778
779 try:
780 from aquin.compute.model_loader import resolve_model_id
781 from aquin.compute.causal_trace import run_chat
782
783 model_id = resolve_model_id(model_id)
784 response = run_chat(user_content, model_id=model_id, max_new_tokens=max_new_tokens, temperature=temperature)
785 _respond(self, {"response": response})
786 except Exception as e:
787 _respond_error(self, str(e))
788
789
790def start() -> None:
791 global _server, _thread, PORT
792 if _server is not None:
793 return
795 PORT = resolve_engine_port()
796 try:
797 _server = HTTPServer(("127.0.0.1", PORT), _Handler)
798 except OSError:
799 # Port already in use — another aquin chat instance running
800 return
801
802 _thread = threading.Thread(target=_server.serve_forever, daemon=True)
803 _thread.start()
804 print("[syncing with web]", flush=True)
805
806
807def stop() -> None:
808 global _server
809 if _server:
810 _server.shutdown()
811 _server = None
812
813
814def run_forever(port: int = PORT) -> None:
815 """Run the server in the foreground (used by the standalone model daemon)."""
816 global _server, _is_daemon
817 _is_daemon = True
818 try:
819 _server = HTTPServer(("127.0.0.1", port), _Handler)
820 except OSError as exc:
821 print(f"[daemon] cannot bind 127.0.0.1:{port}: {exc}", flush=True)
822 return
823 print(f"[daemon] listening on 127.0.0.1:{port}", flush=True)
824 try:
825 _server.serve_forever()
826 except KeyboardInterrupt:
827 pass
828 finally:
829 _server = None
_handle_model_load(self, dict params)
_handle_sae_layers(self, dict params)
None _respond(BaseHTTPRequestHandler handler, Any data, int status=200)
None run_forever(int port=PORT)
str|None _current_loaded_model_id()
None _respond_error(BaseHTTPRequestHandler handler, str msg, int status=500)
dict _read_body(BaseHTTPRequestHandler handler)
None _load_model_in_background(str requested_model_id)