session: readline + π* preflight (33/66 → 1/2) + Qwen default
Three fixes from interactive feedback:
1. **readline in the REPL** — input() now has up-arrow line history
and emacs/vi editing. Persists to ~/.arborist/sessions/.history
across runs (2000-entry cap). Without this, every typo required
retyping the whole line.
2. **π* canonical projection preflight in providence_query** —
bare arithmetic ("33/66", "0.1+0.2") was tokenized by FTS5 as
["33", "66"] and matched the year-article titles "33" and "66"
(low-DF integer tokens dominate bm25); the LLM then "interpreted"
the bare fraction as years 33 AD / 66 AD. The legacy query()
pipeline has the same preflight at the top of its pipeline;
providence_query was missing it (drift from the #000015/#000030
π* registry work). Now firing the arithmetic@v1 / algebra@v1 /
logic-kernel@v1 kernel short-circuits retrieval + LLM entirely
and returns audit_mode=CANONICAL_PROJECTION with the exact
rational answer.
Live: `make query Q="33/66"` → "1/2" (was HYBRID citing year
articles).
3. **Qwen as default LLM for `make session`** — interactive multi-
turn use rewards a smarter model over Hermes-3-8B's fast-sweep
strengths. LLM=hermes still toggles back to 8B; SESSION_LLM_*
env vars override.
66 verify+session+providence tests pass.
This commit is contained in:
parent
508fec6975
commit
2c629d5a11
3 changed files with 96 additions and 3 deletions
15
Makefile
15
Makefile
|
|
@ -211,9 +211,18 @@ query: bootstrap ## ask the corpus a question [JSON=1 BURN=1 LLM=qwen|hermes REP
|
|||
# tree node with a Bates id + a session-wide subtree_hash. Forking is
|
||||
# implicit: /cd to a parent node and ask again → sibling under that
|
||||
# parent. See arborist/qa/session.py + docs/sessions.md.
|
||||
session: bootstrap ## interactive multi-turn Q&A REPL [SID=... LLM=qwen|hermes ANSWER_MODE=... BURN=1]
|
||||
$(if $(LLM_ENDPOINT),@echo "# llm: $(LLM_ENDPOINT) / $(LLM_MODEL)" >&2,)
|
||||
$(ARBORIST) --shards-dir $(SHARDS_DIR) session $(if $(SID),--sid $(SID),) --top-k $(QUERY_TOP_K) $(if $(LLM_ENDPOINT),--endpoint $(LLM_ENDPOINT) --model $(LLM_MODEL),) $(if $(ANSWER_MODE),--answer-mode $(ANSWER_MODE),) $(if $(BURN),--burn,)
|
||||
#
|
||||
# Session defaults to Qwen3.6-27B (better answers for interactive
|
||||
# multi-turn use). Override with LLM=hermes for the 8B fast path.
|
||||
SESSION_LLM_ENDPOINT ?= https://qwen.ai.unturf.com/v1
|
||||
SESSION_LLM_MODEL ?= Qwen3.6-27B-UD-Q4_K_XL.gguf
|
||||
ifeq ($(LLM),hermes)
|
||||
SESSION_LLM_ENDPOINT := https://hermes.ai.unturf.com/v1
|
||||
SESSION_LLM_MODEL := adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic
|
||||
endif
|
||||
session: bootstrap ## interactive multi-turn Q&A REPL [SID=... LLM=qwen|hermes ANSWER_MODE=... BURN=1]; defaults LLM=qwen
|
||||
@echo "# llm: $(SESSION_LLM_ENDPOINT) / $(SESSION_LLM_MODEL)" >&2
|
||||
$(ARBORIST) --shards-dir $(SHARDS_DIR) session $(if $(SID),--sid $(SID),) --top-k $(QUERY_TOP_K) --endpoint $(SESSION_LLM_ENDPOINT) --model $(SESSION_LLM_MODEL) $(if $(ANSWER_MODE),--answer-mode $(ANSWER_MODE),) $(if $(BURN),--burn,)
|
||||
|
||||
session-list: bootstrap ## list all session dbs in ~/.arborist/sessions/
|
||||
$(ARBORIST) session --list $(if $(JSON),--json,)
|
||||
|
|
|
|||
|
|
@ -7502,6 +7502,15 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
return p
|
||||
|
||||
|
||||
def _ignore(fn, *a, **kw):
|
||||
"""Run fn, swallow any exception. Used for atexit hooks where
|
||||
failure (e.g. write to a closed file) shouldn't crash shutdown."""
|
||||
try:
|
||||
fn(*a, **kw)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _cmd_session(args: argparse.Namespace) -> int:
|
||||
"""Merkle-rooted multi-turn Q&A REPL with forks.
|
||||
|
||||
|
|
@ -7661,6 +7670,28 @@ def _session_repl(args: argparse.Namespace, sess) -> int:
|
|||
)
|
||||
from arborist.store import connect as _connect
|
||||
|
||||
# readline gives up-arrow line history + emacs/vi line editing
|
||||
# in the input() call below. The module is in the stdlib but
|
||||
# importing it is what wires it into input(). Persist history to
|
||||
# ~/.arborist/sessions/.history so commands survive across runs.
|
||||
try:
|
||||
import readline
|
||||
_hist = (
|
||||
Path.home() / ".arborist" / "sessions" / ".history"
|
||||
)
|
||||
_hist.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
readline.read_history_file(str(_hist))
|
||||
except (FileNotFoundError, OSError):
|
||||
pass
|
||||
readline.set_history_length(2000)
|
||||
import atexit
|
||||
atexit.register(
|
||||
lambda: _ignore(readline.write_history_file, str(_hist))
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
shards_dir = getattr(args, "global_shards_dir", None) or getattr(
|
||||
args, "shards_dir", None
|
||||
)
|
||||
|
|
|
|||
|
|
@ -308,6 +308,59 @@ def providence_query(
|
|||
def emit(self, *a, **kw): pass
|
||||
progress = _NoProgress()
|
||||
|
||||
# 0. π* canonical projection preflight (math / algebra / logic).
|
||||
# For questions that are pure arithmetic ("33/66", "0.1+0.2"),
|
||||
# pure algebra ("x^2 - 4 = 0"), or pure propositional logic
|
||||
# ("A AND NOT B"), short-circuit retrieval + LLM entirely and
|
||||
# return the deterministic canonical bytes. Without this,
|
||||
# "33/66" was tokenized by FTS5 as ["33", "66"] which matched
|
||||
# the year-article titles "33" and "66" — LLM then "interpreted"
|
||||
# the bare fraction as years 33 AD / 66 AD. The legacy query()
|
||||
# pipeline has the same preflight; providence_query was missing
|
||||
# it (#000015 / #000030).
|
||||
# Off-by-default for tests + bench paths that need deterministic
|
||||
# retrieval shapes; ON for interactive (set by callers).
|
||||
if policy.get("canonical_projection_preflight", True):
|
||||
try:
|
||||
from arborist.qa.query import _canonical_projection_preflight
|
||||
preflight = _canonical_projection_preflight(question)
|
||||
except Exception:
|
||||
preflight = None
|
||||
if preflight is not None:
|
||||
pi_star_ref, canonical_bytes = preflight
|
||||
answer_text = canonical_bytes.decode("utf-8", errors="replace")
|
||||
progress.emit("canonical_projection.hit", pi_star_ref=pi_star_ref)
|
||||
return {
|
||||
"status": "canonical_projection",
|
||||
"answer_text": answer_text,
|
||||
"raw_answer": answer_text,
|
||||
"audit_mode": "CANONICAL_PROJECTION",
|
||||
"verifier_method": "canonical_projection",
|
||||
"n_quotes": 0, "n_verified": 0, "violations": [],
|
||||
"sources": [],
|
||||
"pi_star_ref": pi_star_ref,
|
||||
"capacity": {},
|
||||
"timings": {"total": _time.time() - t_total},
|
||||
"model": model_id,
|
||||
"cache_key": "",
|
||||
"lookup_path": "preflight_canonical",
|
||||
"burned_existing": 0,
|
||||
"audit_event_hash": None,
|
||||
"run_dag_root": None,
|
||||
"context_root": "",
|
||||
"prompt_chars": {
|
||||
"messages_total": 0, "system_prompt": 0,
|
||||
"evidence_or_context": 0, "user_question": len(question),
|
||||
"grounding_reminder": 0,
|
||||
},
|
||||
"answer_chars": len(answer_text),
|
||||
"elapsed_s": round(_time.time() - t_total, 3),
|
||||
"unverified_quotes": [],
|
||||
"partially_verified_quotes": [],
|
||||
"warrant_proven_claim_idxs": [],
|
||||
"format_collapsed": None,
|
||||
}
|
||||
|
||||
# 1. RETRIEVAL ONLY — fast phase. Body + title FTS5 in parallel,
|
||||
# merge by best bm25 per doc_root. Body alone misses canonical
|
||||
# primary articles when high-DF tokens dilute the match (e.g.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue