File size: 30,788 Bytes
b5a142b 999c9f2 b5a142b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 | r"""TAV'IEL-DECIDE -- single-pass typed decisions for YahBible's AI ("Jev mode").
The current pipeline (taviel_reason.py) makes every decision INSIDE prose
generation: which groundings to pull, what kind of question this is, which
gauntlet round matches, whether a cited verse is real -- all decided while the
model is already writing, across two 360-token autoregressive generations.
This module moves those decisions in front of generation, using the
parallel-decision fork of llama.cpp (D:\llama.cpp-decision, branch
parallel-decision): every field of a finite schema is scored as token paths
forking from ONE shared KV cache, so all questions are answered in a single
llama_decode with a calibrated probability each, and the JSON is assembled by
code -- it cannot be malformed. Same mechanism harshatheg benchmarked at
5.6-7.0x over autoregressive JSON on this exact base model
(Qwen2.5-1.5B-Instruct).
Server: the fork's llama-server with --decision-seqs, port 8110 (root tier,
Qwen2.5-1.5B-Instruct-Q4_K_M -- Tav'iel's existing mobile-tier GGUF; one
resident model serves BOTH /v1/chat/completions and /v1/decision).
Machine-first: nothing here loads a model into this process; it is an HTTP
client. Start/stop the server with serve_up()/serve_down().
"""
from __future__ import annotations
import json
import os
import subprocess
import time
import urllib.request
PORT = int(os.environ.get("TAV_DECIDE_PORT", "8110"))
URL = f"http://127.0.0.1:{PORT}/v1/decision"
SERVER = r"D:\llama.cpp-decision\build-cuda-mmq\bin\llama-server.exe"
GGUF = os.environ.get("TAV_DECIDE_GGUF",
r"D:\0000_Raw_LLM Models\Qwen2.5-1.5B-Instruct-Q4_K_M.gguf")
NGL = int(os.environ.get("TAV_DECIDE_NGL", "99"))
EMB_PORT = int(os.environ.get("TAV_EMB_PORT", "8111"))
EMB_SERVER = r"D:\0000_Raw_LLM Models\llama.cpp-tools\llama.cpp\llama-server.exe"
# a REAL contrastive embedder: causal-LM mean-pooling (Qwen 0.6B) proved
# recall-blind -- 4/20 paraphrases, the true round absent from top-8. bge-small
# is 33M params, CPU-instant, trained for exactly this.
# bge-BASE (110M) over bge-small (33M): measured on 630 held-out askings,
# R@8 86% -> 88%, spatial 53->59, R@3 83->84. Still CPU-only, still zero
# VRAM taken from the decision model.
EMB_GGUF = os.environ.get(
"TAV_EMB_GGUF", r"D:\0000_Raw_LLM Models\bge-base-en-v1.5-q8_0.gguf")
_proc = None
_emb_proc = None
# ---------------------------------------------------------------- server ----
def serve_up(ngl=NGL, ctx=4096, seqs=16, wait_s=180):
"""Start the decision server (one resident model, 1.5B fits the 1650 whole)."""
global _proc
if _up():
return True
# the fork asserts (ggml_can_mul_mat) when --embeddings meets the unified
# decision KV, so recall gets its own tiny embedder: the third_eye tier
# (0.6B) on CPU ONLY -- zero VRAM taken from the decision model.
global _emb_proc
_emb_proc = subprocess.Popen(
[EMB_SERVER, "-m", EMB_GGUF, "-ngl", "0", "-c", "512",
"--embeddings", "--port", str(EMB_PORT)],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# ctx 8192 x 24 unified decision sequences proved unstable on the 4 GB
# card across long runs; 4096 x 16 comfortably covers the router's 8
# candidates plus instructions and has not died since.
_proc = subprocess.Popen(
[SERVER, "-m", GGUF, "-ngl", str(ngl), "-fa", "on", "-c", str(ctx),
"--decision-seqs", str(seqs), "--port", str(PORT)],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
t0 = time.time()
while time.time() - t0 < wait_s:
if _up() and _emb_up():
return True
time.sleep(1.0)
raise RuntimeError("server(s) did not come up in %ss (decision=%s embed=%s)"
% (wait_s, _up(), _emb_up()))
_AVAIL = {"at": 0.0, "ok": False}
AVAIL_TTL = 20.0
def available(timeout=0.4, ttl=AVAIL_TTL):
"""Is Jev actually here? A CHEAP probe that never starts anything.
This exists because of what serve_up() costs when the answer is no. Its
retry path waits up to 180 seconds for a server to come up, and decide()
calls it three times -- so on a machine without the fork binary, or with
one that cannot allocate, a single user question could hang for minutes
before falling back. A speedup whose absence costs six minutes is not a
speedup.
So every caller that would LIKE to use the decision server asks here
first. The probe is a HEAD-weight /health with a short timeout, cached for
a few seconds so a chat turn does not pay for it repeatedly, and it never
spawns a process. A machine without Jev behaves exactly as it did before
Jev existed, at the cost of one refused connection every ttl seconds.
Both servers are required: the router needs the decision endpoint to judge
and the embedder to retrieve, and a half-present Jev routes worse than no
Jev at all.
"""
now = time.time()
if now - _AVAIL["at"] < ttl:
return _AVAIL["ok"]
ok = False
try:
urllib.request.urlopen(f"http://127.0.0.1:{PORT}/health", timeout=timeout)
urllib.request.urlopen(f"http://127.0.0.1:{EMB_PORT}/health",
timeout=timeout)
ok = True
except Exception:
ok = False
_AVAIL.update(at=now, ok=ok)
return ok
def unavailable():
"""Mark Jev as gone without waiting for the cache to lapse.
Called when a decision actually fails mid-turn, so the next question does
not repeat a doomed round-trip.
"""
_AVAIL.update(at=time.time(), ok=False)
def serve_down():
global _proc, _emb_proc
for p in (_proc, _emb_proc):
if p:
p.terminate()
_proc = _emb_proc = None
def _up():
try:
urllib.request.urlopen(f"http://127.0.0.1:{PORT}/health", timeout=2)
return True
except Exception:
return False
def _emb_up():
try:
urllib.request.urlopen(f"http://127.0.0.1:{EMB_PORT}/health", timeout=2)
return True
except Exception:
return False
# ---------------------------------------------------------------- client ----
def decide(schema, contexts, instructions="", timeout=120, _tries=3):
"""POST /v1/decision. contexts: str or list of 1-256 strings sharing one
schema + one cached instruction prefix. Returns the parsed response.
RESILIENT BY DESIGN: the fork's server has died mid-run three times
across long evaluations, and each time the numbers it corrupted looked
like a model failure rather than a dead socket (the always-answer law
once scored 11/40 purely from connection-refused). A refused connection
now restarts the server and retries, so a measurement reports the system
rather than the server's mood."""
if isinstance(contexts, str):
contexts = [contexts]
body = json.dumps({"instructions": instructions, "schema": schema,
"contexts": contexts}).encode()
last = None
for attempt in range(_tries):
try:
req = urllib.request.Request(
URL, data=body, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=timeout) as r:
return json.loads(r.read())
except Exception as ex:
last = ex
if attempt == _tries - 1:
break
import sys
import time as _t
print(f"[taviel_decide] decision server unreachable "
f"({type(ex).__name__}); restarting", file=sys.stderr)
try:
if _proc:
_proc.terminate()
except Exception:
pass
globals()["_proc"] = None
_t.sleep(2)
serve_up()
raise last
def decisions_of(resp):
"""[{field: value}] per context, in order."""
return [r["decision"] for r in resp["results"]]
def fields_of(resp, i=0):
"""{field: {value, probability, ...}} for context i."""
return resp["results"][i]["fields"]
# ---------------------------------------------------------------- schemas ---
# 1) THE ANSWER PLAN -- the full shape of the answer, decided before the first
# generated token. One decode answers all nine.
# There is deliberately NO "route" question. v1 asked the model to route with
# bare labels and "decline" won at p=0.96 on a core objection; v2's descriptive
# phrases still lost at p=0.61. Routing is a COMPOUND judgment a 1.5B fumbles,
# while the primitive judgments below measure strong (genre=objection p=0.97).
# So the model answers primitives, and plan() COMPOSES the route in code --
# "with code owning the action", as the aeon-pack doctrine has it.
ANSWER_PLAN = {
"wants_passage": {"type": "boolean",
"description": "Do they only want a Bible passage's text "
"quoted to them, not an explanation?"},
"genre": {"type": "enum",
"choices": ["objection", "word_study", "doctrine", "comfort",
"history", "practice"],
"description": "What kind of question is this?"},
"register": {"type": "enum", "choices": ["skeptic", "believer", "child"],
"description": "Who is asking, by their wording?"},
"depth": {"type": "enum", "choices": ["brief", "moderate", "full"],
"description": "How thorough must the answer be to satisfy the asker?"},
"need_scripture": {"type": "boolean",
"description": "Would verse quotations strengthen the answer?"},
"need_hebrew_greek": {"type": "boolean",
"description": "Does answering require the original "
"Hebrew or Greek behind a word?"},
"need_apologetics": {"type": "boolean",
"description": "Is this an objection or challenge to the "
"faith that a recorded argument answers?"},
"christ_first": {"type": "boolean",
"description": "Should the answer open with what Yeshua the "
"Christ Himself said or did on this?"},
"in_scope": {"type": "boolean",
"description": "Is this within Scripture study, doctrine, or the "
"life of faith at all?"},
}
PLAN_INSTRUCTIONS = ("You are Tav'iel, the study guide of YahBible, a Scripture "
"study app. Judge the user's question below and answer each "
"field from the question's own wording.")
def _true(v):
return v in (True, "true")
def plan(question):
"""One decode of primitive judgments; the route is COMPOSED in code."""
r = decide(ANSWER_PLAN, "Question: " + question, PLAN_INSTRUCTIONS)
dec, fl = decisions_of(r)[0], fields_of(r)
if not _true(dec.get("in_scope")) and fl["in_scope"]["probability"] >= 0.6:
dec["route"] = "decline"
elif _true(dec.get("wants_passage")):
dec["route"] = "verse_lookup"
elif dec.get("genre") == "objection" or _true(dec.get("need_apologetics")):
dec["route"] = "vetted" # the router confirms; its NONE -> reason
else:
dec["route"] = "reason"
return dec, fl, r.get("timings", {})
# 2) THE GAUNTLET ROUTER -- which of the 100 vetted rounds answers this
# question. v1 (a 100-way enum of round IDS) proved 9/20: id tokens carry no
# meaning. v2 (100 independent booleans) proved 1/20 at 4.7s: "covers" says
# yes to everything topical, and independent judgments cannot discriminate.
# v3: the existing LEXICAL net shortlists a handful, and the decision is one
# enum whose CHOICES ARE THE CANDIDATE QUESTIONS THEMSELVES (plus NONE) --
# the candidates compete inside one softmax, which is where a decision
# model's discrimination actually lives.
_KB = None
def _kb():
global _KB
if _KB is None:
p = os.path.join(os.path.dirname(os.path.abspath(__file__)), "gauntlet_kb.json")
_KB = json.load(open(p, encoding="utf-8"))
return _KB
_STOP = set("a an the is are was be been do does did what why how who when where "
"isn didn doesn can could would should i you it they of in on to for "
"with about that this if not no or and but there s t re".split())
def _shortlist(question, k=8):
qw = {w for w in "".join(c if c.isalnum() else " " for c in question.lower())
.split() if w not in _STOP}
scored = []
for e in _kb():
ew = set()
for field in ("words", "keys"):
v = e.get(field) or []
ew |= {str(x).lower() for x in (v if isinstance(v, list) else [v])}
ew |= {w for w in "".join(c if c.isalnum() else " " for c in
e["question"].lower()).split() if w not in _STOP}
s = len(qw & ew)
if s:
scored.append((s, e))
scored.sort(key=lambda x: -x[0])
return [e for _, e in scored[:k]]
def embed(texts):
"""CLS-pooled embeddings from the bge server (/v1/embeddings).
NOT mean-pooled -- this docstring said so and was wrong. The GGUF declares
`bert.pooling_type = 2`, which is CLS, and serve_up passes no --pooling
flag, so the model's own default stands. Every vector in the precomputed
index was therefore built with CLS.
This matters far beyond a docstring: transformers.js and most ONNX
examples default to MEAN pooling. A port that accepts that default puts
its queries in a different space from the keys and recall collapses with
no error and no exception -- just quietly worse answers. Any
reimplementation must pool CLS, must truncate at 512 tokens (`-c 512`),
and must NOT prepend bge's "Represent this sentence for searching
relevant passages: " instruction, because llama-server does not.
"""
if isinstance(texts, str):
texts = [texts]
body = json.dumps({"input": texts}).encode()
last = None
for attempt in range(3):
try:
req = urllib.request.Request(
f"http://127.0.0.1:{EMB_PORT}/v1/embeddings", data=body,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as r:
data = json.loads(r.read())["data"]
return [d["embedding"] for d in sorted(data, key=lambda d: d["index"])]
except Exception as ex:
# same lesson as decide(): building the key cache issues ~180
# batched calls, and one dropped connection used to abort a whole
# proof run with a bare URLError.
last = ex
if attempt == 2:
break
import sys
import time as _t
print(f"[taviel_decide] embedder unreachable ({type(ex).__name__}); "
"restarting", file=sys.stderr)
try:
if _emb_proc:
_emb_proc.terminate()
except Exception:
pass
globals()["_emb_proc"] = None
_t.sleep(2)
serve_up()
raise last
def _cos(a, b):
num = sum(x * y for x, y in zip(a, b))
da = sum(x * x for x in a) ** 0.5
db = sum(x * x for x in b) ** 0.5
return num / (da * db + 1e-9)
_ROUND_EMB = None
def _round_embs():
"""Views per round: the question, its key words, and -- when the variant
file exists -- every generated way of asking it (registers, synonyms,
antonym framings, symbolic/metaphorical, ballpark). A user question
routes by its NEAREST view, so nobody needs the exact wording. Variant
embeddings are cached to disk keyed on the variant file's mtime."""
global _ROUND_EMB
if _ROUND_EMB is None:
base = os.path.dirname(os.path.abspath(__file__))
views = {} # n -> [text, ...]
for e in _kb():
words = e.get("words") or []
views[e["n"]] = [e["question"].strip('"'),
" ".join(str(w) for w in words[:16]) or
e["question"].strip('"')]
vfile = os.path.join(base, "gauntlet_variants.json")
if os.path.exists(vfile):
var = json.load(open(vfile, encoding="utf-8"))
for k_, vs in var.items():
views.setdefault(int(k_), []).extend(vs)
# flat embed with a disk cache (numpy npz keyed by content signature)
import hashlib
import numpy as np
flat, owners, isvar = [], [], []
nbase = {n: 0 for n in views}
for n in sorted(views):
for i, t in enumerate(views[n]):
flat.append(t)
owners.append(n)
isvar.append(0 if i < 2 else 1) # first two are question+words
sig = hashlib.sha1("\x1f".join(flat).encode("utf-8")).hexdigest()[:16]
cache = os.path.join(base, f"round_embs_{sig}.npz")
if os.path.exists(cache):
z = np.load(cache)
arr = z["e"]
else:
out = []
for i in range(0, len(flat), 64):
out.extend(embed(flat[i:i + 64]))
arr = np.asarray(out, dtype="float32")
arr /= (np.linalg.norm(arr, axis=1, keepdims=True) + 1e-9)
np.savez_compressed(cache, e=arr)
_ROUND_EMB = (np.asarray(owners), np.asarray(isvar), arr)
return _ROUND_EMB
# MEASURED on the paraphrase set + off-scope traps (tune_recall.py):
# knn k10 p6 ranks best (19/20 R@1) but its scores do NOT separate scope
# (worst on-topic 0.202 BELOW best off-scope 0.417);
# base_max (question+words only) ranks worse (17/20) but is the only
# strategy with positive separation (0.616 on-topic vs 0.583 off-scope).
# So the two jobs take the two signals: BASE-MAX GATES, KNN RANKS. Variants
# then help recall without letting one noisy generated line admit an
# off-scope question.
KNN_K, KNN_POWER = 10, 6
def _semantic_shortlist(question, k=8):
"""[(knn_score, entry)] best-first. The gate score rides on the list as
.scope (see _scope_of) so callers can judge in/out of scope separately."""
import numpy as np
owners, isvar, arr = _round_embs()
q = np.asarray(embed(question)[0], dtype="float32")
q /= (np.linalg.norm(q) + 1e-9)
sims = arr @ q
base_mask = isvar == 0
scope = float(sims[base_mask].max()) if base_mask.any() else 0.0
kk = min(KNN_K, sims.shape[0])
idx = np.argpartition(-sims, kk - 1)[:kk]
acc = {}
for i in idx:
acc[int(owners[i])] = acc.get(int(owners[i]), 0.0) + \
float(sims[i]) ** KNN_POWER
byn = {e["n"]: e for e in _kb()}
top = sorted(acc.items(), key=lambda x: -x[1])[:k]
out = [(s, byn[n]) for n, s in top]
_semantic_shortlist.last_scope = scope
return out
def _scope_of(question=None):
"""The gate score of the most recent shortlist (base-view max cosine)."""
return getattr(_semantic_shortlist, "last_scope", 0.0)
def suggest(question, k=3, exclude=None):
"""Better questions near this one -- ALWAYS k of them, from k DISTINCT
rounds. 'Poor answering without better suggestions for questions is poor
answering.'
It cannot reuse the router's shortlist: that ranks by a knn vote over
individual vectors, so a near-verbatim question fills all ten neighbours
with ONE round's own variants. Excluding the answered round then left
nothing to suggest -- measured, seven of forty perfect vetted answers
arrived with no follow-up questions at all, the best matches being the
emptiest. Suggestions therefore rank whole rounds by their best view,
which always yields distinct alternatives."""
import numpy as np
try:
owners, isvar, arr = _round_embs()
q = np.asarray(embed(question)[0], dtype="float32")
q /= (np.linalg.norm(q) + 1e-9)
sims = arr @ q
best = {}
for n, s in zip(owners, sims):
n = int(n)
if s > best.get(n, -1.0):
best[n] = float(s)
except Exception:
return []
byn = {e["n"]: e for e in _kb()}
out = []
for n, _ in sorted(best.items(), key=lambda x: -x[1]):
if n == exclude or n not in byn:
continue
out.append((n, byn[n]["question"].strip('"')))
if len(out) >= k:
break
return out
def route(question, threshold=0.60, cos_gate=0.60, k=8, direct_floor=0.70):
"""(round_number|None, probability, timings). None = fall through to reason().
v6 -- two successive questions, each GATED where its signal lives:
recall asks "what is this ABOUT" (embeddings -- lexical overlap capped it
at 3/20 and causal-LM pooling was recall-blind); the judge asks "which one
EXACTLY" among the top-k candidates' own text. A NONE choice no longer
sits in the judge's softmax: as candidates grow plausible the mass SPLITS
among them and NONE wins by default (measured -- the refined suffering
question lost to NONE at p=0.43 with round 2 sitting at recall rank 1).
Out-of-scope is the RECALL's signal: an off-topic question embeds far from
every round, so the cosine gate rejects it before any judging. The judge's
threshold is a margin over uniform (1/k), not a majority bar."""
try:
scored = _semantic_shortlist(question, k)
except Exception as ex:
# fall back, but NEVER silently -- a masked recall failure cost two
# proof runs that were secretly re-measuring the lexical net.
import sys
print(f"[taviel_decide] semantic recall failed ({ex!r}); lexical fallback",
file=sys.stderr)
scored = [(1.0, e) for e in _shortlist(question, k)]
# the GATE is the base-view cosine (the only separating signal); the
# ORDER is the knn vote. Two jobs, two signals.
scope = _scope_of()
if not scored or scope < cos_gate:
return None, scope, {}
bynq = {e["question"].strip('"'): e["n"] for _, e in scored}
schema = {"same": {"type": "enum", "choices": list(bynq),
"description": "Which recorded objection is asking the "
"same thing as the user, in other words?"}}
r = decide(schema, "User's question: " + question,
"The user's question is a REWORDING of one recorded objection. "
"Pick the objection that means the same thing even when the "
"words differ.")
f = fields_of(r)["same"]
n = bynq.get(f["value"])
p = f["probability"]
# TWO WITNESSES. To commit is to hand the inquirer a RECORDED answer --
# and if it is the wrong one, an answer to a question they never asked.
# So retrieval and the judge must INDEPENDENTLY name the same round, and
# the judge must be sure. Measured on 225 held-out askings
# (sweep_thresholds.py): the old permissive rule (p>=0.30, pick anywhere
# in the top 8, with a dominance override) answered 70.2% correctly but
# was OFF TOPIC 17.8% of the time -- one asking in six got another
# question's answer. Requiring agreement at p>=0.60 gives 54.2% correct
# and 1.8% wrong: a tenfold cut in the only truly bad outcome, at 96.8%
# precision. Deferral is not silence -- the front door reasons from
# scripture grounding and offers better questions, which under his law
# is a good answer of a different kind. Bounding WRONG is the objective;
# maximising CORRECT is not.
if n is None or n != scored[0][1]["n"]:
return None, p, r.get("timings", {})
if p < threshold:
return None, p, r.get("timings", {})
# A THIRD WITNESS THAT NEVER SAW THE SHORTLIST.
#
# The two-witness rule assumed retrieval and the judge were independent.
# They are not: retrieval hands the judge its candidate list. Measured on
# 600 held-out askings, 10 of the 14 worst wrong commits had the true
# round ABSENT from the top-8 entirely -- so the judge chose confidently,
# several at p=1.00, among candidates that could not contain the right
# answer, and both witnesses agreed on the same wrong page. No threshold
# on the judge can reach that failure.
#
# So the last check compares the asking DIRECTLY against the chosen
# round's own text, which depends on nothing the shortlist did. Measured
# separation: correct commits median 0.761, wrong 0.663. A floor of 0.70
# keeps 77% of correct commits and blocks 73% of wrong ones, taking the
# off-topic rate from 3.5% to about 1%. The commits it blocks are not
# lost -- they become deferrals, which answer by reasoning with better
# questions offered, and under his law that is a good answer of a
# different kind.
if not _resembles(question, scored[0][1], floor=direct_floor):
return None, p, {**(r.get("timings") or {}), "blocked": "direct_floor"}
return n, p, r.get("timings", {})
def _resembles(question, entry, floor=0.70):
"""Does the CHOSEN round actually look like what was asked?"""
try:
qv, cv = embed([question, entry["question"].strip('"')])
except Exception:
return True # no embedder: do not block on a check we cannot run
num = sum(x * y for x, y in zip(qv, cv))
na = sum(x * x for x in qv) ** .5
nb = sum(x * x for x in cv) ** .5
return (num / (na * nb + 1e-9)) >= floor
def literalize(question, timeout=120):
"""Say plainly what is being asked underneath a figurative asking."""
prompt = ("Someone asked this in a figurative or roundabout way:\n"
f"\"{question}\"\n\n"
"Restate ONLY the plain question underneath it, in one short "
"line, using ordinary words about God, the Bible, or faith. "
"No metaphors, no preamble, no quotation marks.")
body = json.dumps({
"messages": [{"role": "system",
"content": "You restate questions plainly. One line."},
{"role": "user", "content": prompt}],
"max_tokens": 48, "temperature": 0.1}).encode()
req = urllib.request.Request(
f"http://127.0.0.1:{PORT}/v1/chat/completions", data=body,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=timeout) as r:
t = json.loads(r.read())["choices"][0]["message"]["content"]
return t.strip().strip('"').split("\n")[0][:200]
def route_best(question, k=8, anchor=3, lit_threshold=0.70):
"""route(), and when it defers, ASK A BETTER QUESTION OF THE QUESTION.
His principle turned inward: "asking better questions, or questions that
build on questions, is what leads to the best answer." Metaphorical
askings fail retrieval because their words sit far from every recorded
question -- musical 44%, spatial 58% recall@1 -- so the system restates
the asking plainly and routes on the restatement, the way a librarian
says "so what you're really asking is whether God is just."
Three attempts to close this gap by moving the KEYS toward the question
all failed (intelligence-field variants, the KB's own prose as views, a
bigger embedder). Moving the QUESTION toward the keys works.
THE THIRD WITNESS: a restatement may only land on a round the ORIGINAL
asking already had in view. Restating is interpretation, and
interpretation can invent -- unanchored, it turned 7 deferrals into
confident wrong answers. Anchored, it keeps 22 of 24 rescues and breaks
2. Measured on 160 held-out askings from the four weakest fields:
correct 34.4% -> 48.1%, WRONG 2.5% -> 3.8%, with spatial 22% -> 40% at
zero wrong.
Returns (round|None, p, {source}). Fires only on deferral, so a question
both witnesses already agreed on is never second-guessed.
"""
n, p, t = route(question, k=k)
if n is not None:
return n, p, {"via": "direct", **(t or {})}
try:
# ANCHOR DEPTH 3, not 8. Swept offline over 300 held-out askings
# (collect_literalize + sweep_literalize): a top-8 anchor at p>=0.60
# scored correct 58.0% / WRONG 2.7%, while top-3 at p>=0.70 scores
# 57.7% / 2.3% -- and 2.3% is EXACTLY the baseline wrongness without
# literalization at all. So restating buys +7 points of correct
# answers at ZERO added off-topic risk, which is the only version of
# this feature his standard permits. Giving up 0.3 points of correct
# to add no risk is the right trade when the rule is "never off
# topic"; the 3% bar also sits near the sampling noise floor, so the
# rule that ties baseline is the robust one, not the nominal best.
near = [e["n"] for _, e in _semantic_shortlist(question, k)][:anchor]
plain = literalize(question)
n2, p2, t2 = route(plain, k=k)
if n2 is not None and n2 in near and p2 >= lit_threshold:
return n2, p2, {"via": "literalized", "plain": plain, **(t2 or {})}
except Exception:
pass
return None, p, {"via": "deferred"}
# 3) VERSE VERIFICATION -- every candidate ref of a draft, one batched round.
# Replaces the second 360-token "refine" generation on drafts whose only sin
# is a doubtful citation.
VERIFY_SCHEMA = {
"supports": {"type": "boolean",
"description": "Does the verse, as quoted, actually say what the "
"claim uses it to say?"},
"quoted_exactly": {"type": "boolean",
"description": "Is the quotation word-for-word from the "
"verse text given?"},
}
def verify_refs(claims_and_verses):
"""claims_and_verses: [(claim, ref, verse_text)] -> [{ref, supports, exact, p}].
All pairs are scored in ONE decision round (shared schema + cached prefix).
NOT SHIPPED as the refine replacement: prove_taviel_decide2 measured 3/6
discrimination at the 1.5B tier (embedding cosine also 3/6) -- the refine
generation stays until a bigger tier or a true NLI cross-encoder retries."""
contexts = [f"Claim: {c}\nVerse ({ref}): {v}" for c, ref, v in claims_and_verses]
r = decide(VERIFY_SCHEMA, contexts,
"Judge each claim against the verse quoted with it.")
out = []
for (c, ref, v), res in zip(claims_and_verses, r["results"]):
f = res["fields"]
out.append({"ref": ref,
"supports": f["supports"]["value"] in (True, "true"),
"exact": f["quoted_exactly"]["value"] in (True, "true"),
"p": min(f["supports"]["probability"],
f["quoted_exactly"]["probability"])})
return out
|