import re import time import torch class SynapseController: def __init__(self, model, tokenizer): self.model = model self.tokenizer = tokenizer self.device = next(model.parameters()).device self.eot_id = tokenizer.convert_tokens_to_ids("<|eot|>") def _prompt(self, question): return f"<|user|>\n{question}\n<|assistant|>\n" def _clean(self, text): text = str(text) if "<|eot|>" in text: text = text.split("<|eot|>")[0] if "<|final|>" in text: text = text.split("<|final|>")[-1] return text.strip() def _model(self, question, max_new_tokens=140): inputs = self.tokenizer(self._prompt(question), return_tensors="pt").to(self.device) input_len = inputs["input_ids"].shape[-1] start = time.time() with torch.no_grad(): out = self.model.generate( **inputs, max_new_tokens=max_new_tokens, min_new_tokens=4, do_sample=False, pad_token_id=self.tokenizer.pad_token_id, eos_token_id=self.eot_id, repetition_penalty=1.12, no_repeat_ngram_size=4, ) raw = self.tokenizer.decode(out[0][input_len:], skip_special_tokens=False).strip() return self._clean(raw), raw, round(time.time() - start, 3) def _nsr(self, question): q = str(question).strip() m = re.search(r"Calculate:\s*(-?\d+)\s*([\+\-\*])\s*(-?\d+)", q) if m: a, op, b = int(m.group(1)), m.group(2), int(m.group(3)) val = a + b if op == "+" else a - b if op == "-" else a * b return True, f"Final answer: {val}", f"{a} {op} {b} = {val}" m = re.search(r"A shop has\s+(\d+)\s+boxes\. Each box has\s+(\d+)\s+pencils\.\s+(\d+)\s+pencils are lost", q) if m: boxes, items, lost = int(m.group(1)), int(m.group(2)), int(m.group(3)) val = boxes * items - lost return True, f"Final answer: {val}", f"{boxes} * {items} - {lost} = {val}" return False, "", "" def _uqm(self, question): q = str(question).lower() risky = ["private password", "hidden bank pin", "silently think", "lost private letter", "unpublished diary", "unknown person"] if any(x in q for x in risky): return True, "I do not have sufficient information.", "Insufficient evidence or unknowable/private information." return False, "", "" def _tms(self, question): q = str(question) if "Context:" not in q or "Question:" not in q: return False, "", "" context = q.split("Context:", 1)[1].split("Question:", 1)[0].strip() patterns = [ r"called\s+([A-Z][A-Za-z0-9\-]+(?:\s+[A-Z][A-Za-z0-9\-]+)*)", r"named\s+([A-Z][A-Za-z0-9\-]+(?:\s+[A-Z][A-Za-z0-9\-]+)*)", r"in\s+([A-Z][A-Za-z0-9\-]+)\s+in\s+\d{4}", r"code name\s+([A-Z][A-Za-z0-9\-]+(?:\-[A-Z][A-Za-z0-9\-]+)*)", ] for pat in patterns: m = re.search(pat, context) if m: ans = m.group(1).strip() return True, ans, f"Extracted from context: {ans}" return False, "", "" def generate(self, question, return_trace=True, **kwargs): for route_name, fn in [("UQM_ABSTAIN", self._uqm), ("NSR_CALCULATOR", self._nsr), ("TMS_CONTEXT", self._tms)]: ok, ans, trace = fn(question) if ok: result = {"answer": ans, "route": route_name, "trace": trace} return result if return_trace else ans ans, raw, latency = self._model(question, max_new_tokens=kwargs.get("max_new_tokens", 140)) result = {"answer": ans, "route": "MODEL_FALLBACK", "trace": "Used pure model fallback.", "raw_answer": raw, "latency_sec": latency} return result if return_trace else ans