MLX
Joblib
Safetensors
English
reasoning
chain-of-thought
context-compression
soft-prompt
apple-silicon
Instructions to use baya1116/hypernet-sp-distill with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use baya1116/hypernet-sp-distill with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] huggingface-cli download --local-dir hypernet-sp-distill baya1116/hypernet-sp-distill
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
| """Torch port of the FULL app turn pipeline (tiered_rag_mlx.ChatSession.turn) so the | |
| composite app-usage battery can run off-Mac. Mirrors the MLX runtime decision-for-decision: | |
| intent routing (intent_route + real intent_clf) -> specificity pinning (real probe) | |
| -> tiered retrieval (memory_core.TieredMemory + BGE; web backend injected) | |
| -> context-injection prompts (verbatim aug templates from tiered_rag_mlx) | |
| -> bounded SP-evict generation (pooler + fft_hf) with DecodePolicy | |
| -> recall via isolated _clean_quote -> groundedness gate + rolled-back retries | |
| -> memory writes (persist / session / fact-only) | |
| Differences from the MLX runtime are confined to: torch tensors, smaller default budgets | |
| (CPU), and the injected `web` object (tests use a canned corpus; production uses | |
| DuckDuckGo/Wikipedia exactly as before). | |
| """ | |
| import os, re, sys, time | |
| import torch | |
| import torch.nn.functional as F | |
| from transformers.cache_utils import DynamicCache | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| import memory_core as mc | |
| from intent_route import route_intent, looks_mathy | |
| from anaphora import expand_web_query | |
| from web_guard import guard_chunks | |
| from decode_policy import DecodePolicy | |
| from calculator import repair_answer as calc_repair | |
| ANSCAP = 600 | |
| # cheap candidate enumerator for the specificity probe (verbatim from tiered_rag_mlx) | |
| _CAND = re.compile( | |
| r"\$\d[\d,]*(?:\.\d+)?" | |
| r"|\b\d{1,2}(?::\d{2})?\s?(?:am|pm)\b" | |
| r"|\b\d+(?:\.\d+)?\s?(?:cm|mm|km|kg|%|percent|days?|years?|hours?|min)\b" | |
| r"|\b[A-Za-z]*\d[A-Za-z0-9]*(?:-[A-Za-z0-9]+)*\b" | |
| r"|\b[A-Z][a-z]{2,}(?:\s+[A-Z][a-z]{2,}){0,2}\b", 0) | |
| class AppSession: | |
| def __init__(self, llm, tok, pooler, bge, intent_clf, spec_clf, mem, web=None, | |
| rw=512, C=64, cap=600, temp=0.6, maxD=4096, seed=0, profile=True, | |
| archive=None, recall_k=2): | |
| self.llm, self.tok, self.pooler, self.bge = llm, tok, pooler, bge | |
| self.intent_clf, self.spec_clf = intent_clf, spec_clf | |
| self.mem, self.web = mem, web | |
| # SPCHAT_DEVICE=cuda moves the LLM+pooler at construction time, so the composite | |
| # batteries (which all build their model on CPU then hand it to AppSession) run | |
| # on GPU without edits. Default: leave devices untouched. | |
| if os.environ.get("SPCHAT_DEVICE"): | |
| llm.to(os.environ["SPCHAT_DEVICE"]) | |
| pooler.to(os.environ["SPCHAT_DEVICE"]) | |
| self.dev = next(llm.parameters()).device | |
| # Phase A block recall (BLOCKRECALL_V1): verbatim re-injection of evicted blocks. | |
| # None = exact pre-existing behaviour. SPCHAT_BLOCK_RECALL=bge|qk turns it on | |
| # globally (lets the composite batteries run with recall without edits). | |
| if archive is None and os.environ.get("SPCHAT_BLOCK_RECALL") in ("bge", "qk"): | |
| from block_recall import BlockArchive | |
| archive = BlockArchive(llm, tok, mode=os.environ["SPCHAT_BLOCK_RECALL"], | |
| bge=bge) | |
| self.archive, self.recall_k = archive, recall_k | |
| self.rw, self.C, self.cap, self.temp, self.maxD = rw, C, cap, temp, maxD | |
| self.embT = llm.get_input_embeddings() | |
| self.eos = tok.eos_token_id | |
| self.THINK_OPEN = tok.encode("<think>\n", add_special_tokens=False) | |
| self.gen, self.kept, self.absorbed = [], [], 0 | |
| self.evictions = 0 | |
| self.profile = profile # ambient identity in the MQ region | |
| torch.manual_seed(seed) | |
| # ---- conversational continuity (chitchat carries memory) ------------------------ | |
| def _profile_ids(self): | |
| """Compact user profile placed in the NEVER-EVICTED MQ prefix, so every turn β | |
| chitchat included β knows who it is talking to. Declarative one-liner built from | |
| persisted facts (recency-capped); never question-shaped (defect #3d).""" | |
| if not self.profile or not self.mem.persistent: | |
| return self.tok.encode("") or [self.tok.bos_token_id] | |
| facts = [f for f in self.mem.persistent[-8:] | |
| if not mc._is_question(f) and len(f.split()) <= 30] | |
| text = "(About the user: " + " ".join(facts) + ")\n" | |
| return self.tok.encode(text) | |
| def save_state(self, path): | |
| """Persist the conversation stream so the NEXT session starts with the previous | |
| one already compressed into the soft prompt ('SP warm start').""" | |
| import json as _json | |
| hist = (self.kept + self.gen[self.absorbed:])[-self.maxD:] | |
| with open(path, "w") as f: | |
| _json.dump({"kept": hist}, f) | |
| def load_state(self, path): | |
| """Resume continuity: the previous session's stream becomes this session's kept | |
| buffer β the first rebuild compresses it into the 32 SP vectors, so casual turns | |
| pick up tone/topics without any retrieval.""" | |
| import json as _json, os as _os | |
| if not _os.path.exists(path): | |
| return False | |
| with open(path) as f: | |
| self.kept = _json.load(f)["kept"][-self.maxD:] | |
| self.gen, self.absorbed = [], 0 | |
| return True | |
| def _stitch(self, user_msg, answer): | |
| """Record a NON-GENERATING turn (fact ack / honest miss / closest note / isolated | |
| recall quote) into the conversation stream. Without this, "I went to Kyoto today" | |
| -> instant ack leaves NO trace in SP/raw, and the next casual turn ("what do you | |
| think was the highlight?") has nothing to follow β the reported chitchat-continuity | |
| gap. Tokens only; no generation happens here.""" | |
| rec = answer | |
| if len((answer or "").split()) < 4: | |
| # 25 root: a BARE-TOKEN assistant turn in the stream ("8042") is a copy | |
| # attractor so strong that neither rejection-retries nor temperature 1.0 | |
| # escape it β the next creative turn echoes it verbatim. The user still SEES | |
| # the terse answer; only the stream RECORD is wrapped into prose. | |
| rec = f"The answer to your question is {answer}." | |
| text = ("<ο½endβofβsentenceο½>" if self.gen else "") + f"<ο½Userο½>{user_msg}<ο½Assistantο½>{rec}" | |
| self.gen.extend(self.tok.encode(text, add_special_tokens=False)) | |
| def _evict(self, kept): | |
| """Mass-based eviction (port of sp_mlx.evict / MAXD): when the distant buffer | |
| exceeds maxD, keep the maxD tokens the pooler itself attends to most, in | |
| chronological order. The production path RESULTS.md flags as barely exercised β | |
| the 6k/12k long-haul battery exists to finally hit it.""" | |
| if not self.maxD or len(kept) <= self.maxD: | |
| return kept | |
| _, mass = self.pooler.forward_with_mass(self._emb(kept).float()) | |
| idx = sorted(mass[0].topk(self.maxD).indices.tolist()) | |
| self.evictions += 1 | |
| return [kept[i] for i in idx] | |
| # ---- learned heads ------------------------------------------------------------- | |
| def intent_of(self, text): | |
| return route_intent(text, self.intent_clf, self.bge) | |
| def specific_spans(self, text, min_p=0.6, cap=6): | |
| if not self.spec_clf or not self.bge: | |
| return [] | |
| cands = [c for c in dict.fromkeys(m.group(0).strip() for m in _CAND.finditer(text)) | |
| if len(c) >= 2][:24] | |
| if not cands: | |
| return [] | |
| X = self.bge._encode(cands, is_query=False) | |
| si = list(self.spec_clf["clf"].classes_).index(1) | |
| p = self.spec_clf["clf"].predict_proba(X)[:, si] | |
| hits = sorted(((c, float(pp)) for c, pp in zip(cands, p) if pp >= min_p), | |
| key=lambda h: -h[1]) | |
| return [c for c, _ in hits[:cap]] | |
| # ---- generation core (port of sp_mlx/_gen_once bounded loop) -------------------- | |
| def _emb(self, ids): | |
| return self.embT(torch.tensor([ids], device=self.dev)) if ids else \ | |
| torch.zeros(1, 0, self.pooler.H, dtype=self.embT.weight.dtype, | |
| device=self.dev) | |
| def _gen_once(self, aug, policy=None, cap=None, salvage="Final answer: ", salvage_budget=48, | |
| force_think=True, temp_override=None): | |
| # force_think is a MATH device. The #16 isolation arms C/E proved the model answers | |
| # directly and well WITHOUT it; v7 proved that WITH it, creative tasks draft the | |
| # artifact inside <think> and then emit a self-review ("I think this fits the | |
| # user's request") as the visible answer. Compute paths keep the think; chat and | |
| # creative turns answer directly at base temperature. | |
| cap = cap or self.cap | |
| tok, llm = self.tok, self.llm | |
| gen, kept, absorbed = self.gen, self.kept, self.absorbed | |
| feed = list(tok.encode(("<ο½endβofβsentenceο½>" if gen else "") + | |
| f"<ο½Userο½>{aug}<ο½Assistantο½>", add_special_tokens=False)) \ | |
| + (list(self.THINK_OPEN) if force_think else | |
| list(tok.encode("<think>\n\n</think>\n\n", add_special_tokens=False))) | |
| # ^ non-compute turns get a PRE-CLOSED empty think: leaving it out entirely is not | |
| # enough β the FFT'd model re-opens its own <think>, drafts the artifact inside, | |
| # and emits only a self-review (v7 round-2 W2/B1). Pre-closing pins it to answer. | |
| start, fi, new = len(gen), 0, 0 | |
| rec_emb = None # Phase A verbatim recall, filled after absorb | |
| cache = DynamicCache() | |
| prime = llm(input_ids=torch.tensor([self._profile_ids()], device=self.dev), | |
| past_key_values=cache, use_cache=True) | |
| prime_last = prime.logits[:, -1, :].float() | |
| MQ = cache.get_seq_length() | |
| in_think, forced_final, done = force_think, False, False | |
| policy = policy or DecodePolicy() | |
| while not done: | |
| c0 = len(gen); R = min(c0, self.rw); nd_end = c0 - R | |
| if nd_end > absorbed: | |
| if self.archive is not None: | |
| self.archive.extend(gen[absorbed:nd_end]) | |
| kept.extend(gen[absorbed:nd_end]); absorbed = nd_end | |
| kept = self._evict(kept) | |
| # Phase A recall (BLOCKRECALL_V1): once per turn, after absorption, score the | |
| # evicted-block archive with the current message's own pre-RoPE query | |
| # projections and re-inject the winners verbatim between SP and the raw | |
| # window. Exact values survive SP compression this way (SP stays the gist). | |
| if rec_emb is None and self.archive is not None and \ | |
| (self.archive.blocks or len(self.archive.buf) >= 16): | |
| rid = self.archive.retrieve(tok.encode(aug, add_special_tokens=False), | |
| k=self.recall_k) | |
| if rid: | |
| rec_emb = self._emb(rid) | |
| # defect #16 (chitchat report): NO soft prompt when there is no past. The | |
| # pooler was trained on math-CoT contexts only; its EMPTY-input output is a | |
| # constant "there is a math problem" bias that made bare greetings invent | |
| # tasks (isolation: SP-pipeline 0/2 vs same weights full-KV 3/3). A summary | |
| # of nothing carries no information β don't inject one. | |
| parts = [] | |
| if kept: | |
| sp = self.pooler(self._emb(kept).float()).to(self.embT.weight.dtype) | |
| parts.append(sp) | |
| if rec_emb is not None: | |
| parts.append(rec_emb.to(self.embT.weight.dtype)) | |
| if R > 0: | |
| parts.append(self._emb(gen[c0 - R:c0])) | |
| cache.crop(MQ) | |
| if parts: | |
| block = torch.cat(parts, 1) | |
| last = llm(inputs_embeds=block, past_key_values=cache, | |
| use_cache=True).logits[:, -1, :].float() | |
| else: | |
| last = prime_last # first tokens of a first turn | |
| for _ in range(self.C): | |
| if fi < len(feed): | |
| t = feed[fi]; fi += 1 | |
| else: | |
| base_t = temp_override or self.temp | |
| T = policy.temp(in_think, base_t) if force_think else base_t | |
| t = int(torch.multinomial(F.softmax(last[0] / T, -1), 1)) | |
| if t == self.eos: | |
| done = True; break | |
| new += 1 | |
| if new >= cap: | |
| done = True; break | |
| gen.append(t) | |
| if in_think and "</think>" in tok.decode(gen[-8:]): | |
| in_think = False | |
| if in_think and fi >= len(feed) and policy.note_text(tok.decode(gen[start:])): | |
| feed += list(tok.encode("\n</think>\n\nFinal answer: ", | |
| add_special_tokens=False)) | |
| in_think = False | |
| last = llm(inputs_embeds=self._emb([t]), past_key_values=cache, | |
| use_cache=True).logits[:, -1, :].float() | |
| if done: | |
| break | |
| body = tok.decode(gen[start:]).split("<ο½Assistantο½>", 1)[-1] | |
| if "</think>" not in body or mc._extract_answer(body) in mc._EMPTY: | |
| # pass-1 salvage (port of the MLX two-pass that this port was missing): the | |
| # think meandered to the cap without converging or looping β close it and force | |
| # a short greedy answer instead of returning the timeout token (v3 C7). | |
| def step(t): | |
| return llm(inputs_embeds=self._emb([t]), past_key_values=cache, | |
| use_cache=True).logits[:, -1, :].float() | |
| # salvage continuation is INTENT-AWARE: "Final answer: " primes a bare number, | |
| # which turned a binary-search explanation into the stub "100" (v6 C3 β the | |
| # explanation never left <think> within the cap). Non-compute turns just close | |
| # the think and answer naturally, on a larger budget. | |
| for t in tok.encode(f"\n</think>\n\n{salvage}", add_special_tokens=False): | |
| gen.append(t); last = step(t) | |
| for _ in range(salvage_budget): | |
| t = int(last[0].argmax()) | |
| if t == self.eos: | |
| break | |
| gen.append(t); last = step(t) | |
| body = tok.decode(gen[start:]).split("<ο½Assistantο½>", 1)[-1] | |
| self.gen, self.kept, self.absorbed = gen, kept, absorbed | |
| self._last_body = body # for post-hoc arithmetic verification | |
| ans = mc._extract_answer(body) | |
| if "Final answer:" in ans: | |
| ans = ans.split("Final answer:")[-1].strip() | |
| return ans[:ANSCAP] | |
| def _clean_quote(self, aug, temp=0.2, think_budget=180, ans_budget=60): | |
| """Isolated recall: fresh cache, NO soft prompt, NO history (port of MLX version).""" | |
| tok, llm = self.tok, self.llm | |
| cache = DynamicCache() | |
| def feed(ids): | |
| return llm(inputs_embeds=self._emb(ids), past_key_values=cache, | |
| use_cache=True).logits[:, -1, :].float() | |
| def sample(last, budget): | |
| out, prev, rep = [], None, 0 | |
| for _ in range(budget): | |
| t = int(torch.multinomial(F.softmax(last[0] / temp, -1), 1)) | |
| if t == self.eos: | |
| break | |
| rep = rep + 1 if t == prev else 0 | |
| if rep >= 5: | |
| break | |
| prev = t; out.append(t) | |
| last = llm(inputs_embeds=self._emb([t]), past_key_values=cache, | |
| use_cache=True).logits[:, -1, :].float() | |
| return out | |
| last = feed(tok.encode(f"<ο½Userο½>{aug}<ο½Assistantο½>", add_special_tokens=True) | |
| + list(self.THINK_OPEN)) | |
| think = sample(last, think_budget) | |
| last = feed(tok.encode("\n</think>\n\n", add_special_tokens=False)) | |
| ans = tok.decode(sample(last, ans_budget)).strip() | |
| if mc._looks_degenerate(ans) or ans in mc._EMPTY: | |
| box = re.findall(r"\\boxed\{([^}]*)\}", tok.decode(think)) | |
| ans = f"\\boxed{{{box[-1].strip()}}}" if box and box[-1].strip() else ans | |
| return ans[:ANSCAP] | |
| def _web_retrieve(self, query): | |
| if self.web is None: | |
| return None, [] | |
| raw = self.web.search(query) | |
| ch = guard_chunks(raw[:2]) if raw else [] | |
| return ("L3Β·web", ch) if ch else (None, []) | |
| # ---- the app turn (decision-for-decision port of ChatSession.turn) -------------- | |
| def turn(self, user_msg, store="session", ack_only=False, retries=2): | |
| if ack_only: | |
| (self.mem.persist if store == "persist" else self.mem.remember_session)(user_msg) | |
| self._stitch(user_msg, "Got it β saved.") | |
| return "Got it β saved.", None, [] | |
| intent = self.intent_of(user_msg) | |
| compute_like = intent in ("math", "command") | |
| # store-request phrased as a QUESTION ("Can you remember that my locker code is | |
| # 8042?"): interrogative shape routes it to recall, whose empty-retrieval honest | |
| # miss would answer "you haven't told me yet" to the very message telling us. | |
| # A persist verb + an assertable value = a save, whatever the punctuation. | |
| head = " ".join(user_msg.split()[:8]) | |
| if mc.wants_persist(head) and len(user_msg.split()) <= 30 \ | |
| and mc._FACTLIKE.search(user_msg) and self.specific_spans(user_msg): | |
| self.mem.persist(user_msg) | |
| self.mem.pin(user_msg) | |
| self._stitch(user_msg, "Got it β saved.") | |
| return "Got it β saved.", None, [] | |
| if intent not in ("recall", "lookup") and self.specific_spans(user_msg): | |
| self.mem.pin(user_msg) | |
| if intent == "fact": | |
| # facts bypass generation (OPERATING.md): log + instant ack. Generating here | |
| # wastes a full turn AND pollutes the conversation stream β the composite | |
| # battery showed the previous fact's ramble bleeding into the next chitchat. | |
| if store == "persist" or mc.wants_persist(user_msg): | |
| self.mem.persist(user_msg) | |
| else: | |
| self.mem.remember_session(user_msg) | |
| self._stitch(user_msg, "Got it β saved.") | |
| return "Got it β saved.", None, [] | |
| if intent == "recall": | |
| src, chunks = self.mem.retrieve_personal(user_msg) | |
| if not chunks: | |
| # honest miss. Free generation here CONFABULATES (composite v2 B3: asked for | |
| # a never-stated wifi password, the model invented "password123"). A recall | |
| # is a lookup into the user's saved facts; an empty lookup has exactly one | |
| # truthful answer, and it costs zero tokens. | |
| ans = "I don't have that saved β you haven't told me yet." | |
| self._stitch(user_msg, ans) | |
| return (ans, None, []) | |
| elif intent == "lookup": | |
| # known-fact first (strict): a personal question that surface-classifies as a | |
| # world lookup ('Where does my sister live?', 'Where does Daniel live?') must | |
| # quote what the user told us, not hit the web (composite v3 B3/B4). | |
| src, chunks = self.mem.retrieve_known(user_msg) | |
| if not chunks: | |
| wm_only = [p for p in self.mem.pins if p not in self.mem.session] | |
| mp = mc._sem_matches(user_msg, wm_only, self.bge, min_sim=0.5) if wm_only else None | |
| if mp: | |
| src, chunks = "WMΒ·pins", mp | |
| else: | |
| q = expand_web_query(user_msg, self.mem.pins, self.mem.session) | |
| src, chunks = self._web_retrieve(q) | |
| # rank/conf with the EXPANDED query: it carries the anchor value, so | |
| # the chunk sharing it wins the closest-note pick and can clear the | |
| # confidence bar (live oil test: unexpanded ranking chose the Brent | |
| # chunk over the queried \$86.78 WTI one) | |
| self._rank_query = q if q != user_msg else None | |
| if not chunks: | |
| # 26: offline/failed lookup must NOT fall through to free | |
| # generation β the 1.5B invents figures ("Mount Fuji is 2,0900 | |
| # meters", full demo). A lookup means CHECKED knowledge; if we | |
| # cannot check, say so. | |
| ans = ("I can't look that up right now (no web access). " | |
| "Ask me again when I'm back online.") | |
| self._stitch(user_msg, ans) | |
| return (ans, None, []) | |
| elif intent == "command" and (self.mem.session or self.mem.pins): | |
| log = self.mem.session[-self.mem.LOGCAP:] | |
| # a pin is redundant when a log line EXTENDS it ('<question> β result: 24' | |
| # startswith '<question>'): re-injecting the bare question next to its answered | |
| # form is what tangled the follow-up math turn in the composite battery. | |
| chunks = log + [c for c in self.mem.pins if not any(l.startswith(c) for l in log)] | |
| src = "L1Β·same-session" + ("+WMΒ·pins" if self.mem.pins else "") | |
| elif intent == "math" and any(p != user_msg for p in self.mem.pins): | |
| prev = [p for p in self.mem.pins if p != user_msg and not mc._is_question(p)] | |
| log = [l for l in self.mem.session[-self.mem.LOGCAP:] if not mc._is_question(l)] | |
| chunks = log + [c for c in prev if not any(l.startswith(c) for l in log)] | |
| src = "WMΒ·pins" + ("+L1" if log else "") | |
| else: | |
| src, chunks = None, [] | |
| if chunks and compute_like: | |
| # keep compute injections SHORT and question-first. The verbatim MLX template | |
| # (context first + a meta-instruction about she/it/corrections) made the 1.5B | |
| # distill spend its entire think parsing the INSTRUCTION instead of computing | |
| # (composite battery transcript: it re-quoted the instruction 3x, never reached | |
| # 50-24, answered 27.5). Also relevance-filter the facts so unrelated pins | |
| # (hotel room number) don't ride into an arithmetic turn. | |
| rel = mc._sem_matches(user_msg, chunks, self.bge, cap=3, min_sim=0.4) or chunks[-2:] | |
| rel = mc._with_amendments(rel, chunks) # corrections ride along... | |
| rel = mc.mark_superseded(rel) # ...and are RESOLVED before injection: | |
| # the 'most recent value wins' instruction does not work on a 1.5B (v3 C7 twice); | |
| # explicit (outdated)/(current) tags are mechanical to follow. | |
| multi = bool(re.search(r"\([a-c]\)", user_msg)) | |
| tail = ("Answer EVERY lettered part; end with one line listing each part's result." | |
| if multi else "End with the final number.") | |
| # '(v5: "End with the final number." (singular) made the model stop after ONE | |
| # sub-part of (a)/(b)/(c) questions β P1 concluded at part (b), P5 at part (a)) | |
| aug = (f"{user_msg}\n\n(Earlier in this conversation: {' ; '.join(rel)})\n" | |
| f"Use those earlier values if the question refers to them. Ignore lines " | |
| f"marked (outdated). {tail}") | |
| elif chunks: | |
| # retrieval confidence picks the template. Measured sims OVERLAP across the | |
| # boundary (true paraphrase match 0.592 vs blood-type/badge false hit 0.543), | |
| # so a threshold can't reject false hits without killing paraphrase recall β | |
| # below 0.65 the template carries an ESCAPE HATCH instead. Without it, the | |
| # strict 'the answer IS in the Context' premise forced 'your blood type is | |
| # VB-7731' out of a badge-code chunk (v4 t35). | |
| rq = getattr(self, "_rank_query", None) or user_msg | |
| self._rank_query = None | |
| qv = self.bge._encode([rq], is_query=True)[0] | |
| sims = self.bge._encode(list(chunks), is_query=False) @ qv | |
| if float(max(sims)) < 0.62: # true-match floor measured at 0.653 (hotel) | |
| # uncertain band: don't ASSERT an answer at all. Nothing separates a true | |
| # paraphrase match from a false hit here β full-question sims overlap | |
| # (0.592 vs 0.543), topic sims overlap (0.498 vs 0.487), the in-prompt | |
| # escape hatch got steamrolled, and the yes/no micro-judge said yes and | |
| # invented blood type 'A' for a badge code. So show the closest saved note | |
| # verbatim instead: honest for a false hit, and for a genuine paraphrase | |
| # the note IS the answer. Mechanical, zero extra latency. | |
| note = chunks[int(sims.argmax())] | |
| lead = ("Here's the closest source I found: " if (src or "").startswith("L3") | |
| else "I don't have that saved exactly β the closest note I have: ") | |
| ans = f"{lead}\"{note}\"" | |
| self._stitch(user_msg, ans) | |
| return (ans, src, chunks) | |
| aug = (f"Context (retrieved from {src}): {' ; '.join(chunks)}\n\n" | |
| f"Question: {user_msg}\nThe answer is stated EXPLICITLY in the Context above. Do NOT " | |
| f"calculate, reason about, or transform it, and ignore anything earlier in the " | |
| f"conversation β just read the matching value from the Context and reply with ONLY that " | |
| f"value, verbatim (keep letter prefixes/punctuation, e.g. 'EMP-1234' not '1234'; use the " | |
| f"most recent value if it was corrected).") | |
| else: | |
| aug = user_msg | |
| quote_recall = bool(chunks) and not compute_like | |
| check_chunks = [] if compute_like else chunks | |
| if quote_recall: | |
| answer = self._clean_quote(aug) | |
| for _ in range(retries): | |
| if mc._answer_ok(answer, check_chunks, user_msg): | |
| break | |
| answer = self._clean_quote(aug) | |
| else: | |
| snap = (list(self.gen), list(self.kept), self.absorbed) | |
| # numeric-convergence forcing is a MATH device: on an explanation turn the | |
| # example number recurs ("an array of 100... halve 100...") and k=3 fires, | |
| # forcing "Final answer: 100" out of a binary-search explanation (v6 C3). | |
| # The verbatim-loop trigger stays armed on every turn. | |
| def _pol(): | |
| return DecodePolicy(k=3 if compute_like else 10 ** 9) | |
| sv = ("Final answer: ", 48) if compute_like else ("", 200) | |
| def _ok(a): | |
| # 25: a non-compute answer with NO alphabetic word is a copy artifact β | |
| # the bare "8042" recall answer one turn earlier primed direct-answer mode | |
| # into echoing it as the "haiku". Numbers-only replies are only legitimate | |
| # on compute turns. | |
| if not compute_like and not re.search(r"[A-Za-z]{2,}", a or ""): | |
| return False | |
| return mc._answer_ok(a, check_chunks, user_msg) | |
| answer = self._gen_once(aug, policy=_pol(), salvage=sv[0], salvage_budget=sv[1], | |
| force_think=compute_like) | |
| for attempt in range(retries): | |
| if _ok(answer): | |
| break | |
| self.gen, self.kept, self.absorbed = list(snap[0]), list(snap[1]), snap[2] | |
| # escalate temperature on retry: a copy attractor (the bare "8042" one | |
| # turn back) survives same-temp resampling β all three 0.6 draws echoed it | |
| answer = self._gen_once(aug, policy=_pol(), salvage=sv[0], salvage_budget=sv[1], | |
| force_think=compute_like, | |
| temp_override=None if compute_like else | |
| (0.85 if attempt == 0 else 1.0)) | |
| if compute_like and answer: | |
| # post-hoc calculator (the 1.5B mis-EVALUATES its own correct expressions: | |
| # 2000x1.05^3 -> 121550.625, 650-200 -> 210). Claims in the full turn body are | |
| # re-computed mechanically; a wrong value that reached the answer is replaced. | |
| fixed, corrections = calc_repair(answer, full_body=getattr(self, "_last_body", None)) | |
| if corrections: | |
| answer = fixed | |
| if quote_recall: | |
| self._stitch(user_msg, answer) # isolated quote leaves a trace too | |
| if intent == "lookup" and src and src.startswith("L3") \ | |
| and mc._answer_ok(answer, chunks, user_msg): | |
| # 23: web results were remembered NOWHERE (compute results self-log via | |
| # #3b, lookups didn't β asymmetry). "Can you verify the number?" right | |
| # after a successful oil-price lookup hit the honest-miss wall. Log the | |
| # grounded answer declaratively so follow-ups can reference it. | |
| self.mem.remember_session(f"Earlier looked up: {answer[:160]}") | |
| if store == "persist": | |
| self.mem.persist(user_msg) | |
| elif store == "session" and intent == "fact": | |
| self.mem.remember_session(user_msg) | |
| if compute_like and mc._answer_ok(answer, [], user_msg): | |
| # self-log the RESULT of a compute turn. The follow-up battery showed why: the | |
| # log carries the previous QUESTION but not its answer, so "I pay with $50, | |
| # how much change?" forced a full re-derivation of the $24 β and the model | |
| # tangled the two questions. With "... β result: 24" in the log, the follow-up | |
| # reads the prior result instead of re-deriving it. | |
| box = re.findall(r"\\boxed\{([^}]*)\}", answer) | |
| nums = re.findall(r"\$?\d[\d,]*(?:\.\d+)?", answer) | |
| val = box[-1].strip() if box else (nums[-1] if nums else None) | |
| if val: | |
| # DECLARATIVE form only. Logging "<question> β result: 24" baited the model | |
| # into re-answering the embedded QUESTION instead of the new one (composite | |
| # transcript: it ignored the $50-change question entirely and re-derived the | |
| # muffin total). Interrogative text must never be re-injected as context. | |
| # And the result line is WORKING STATE, not history: keeping old results | |
| # alongside made the model hedge between them ('if 24 then 26; if 32 then | |
| # 18' β composite v2 C3). A recompute supersedes the previous result. | |
| self.mem.session = [l for l in self.mem.session | |
| if not l.startswith("Earlier computed result:")] | |
| self.mem.remember_session(f"Earlier computed result: {val}.") | |
| return answer, src, chunks | |