cli: flip 'arborist query' default to providence_query; --legacy hatch
#000072 Phase 2 step 3. The user-facing 'arborist query' command now routes through arborist.qa.providence_query (cache-aware run_query wrapper) instead of the legacy 2000-line arborist.qa.query.query(). Legacy function stays alive in the module — other importers (test_query.py, internal calls) keep working — but the CLI defaults to the new orchestrator. Escape hatch: arborist query "..." --legacy ARBORIST_LEGACY_QUERY=1 arborist query "..." Either re-routes through the legacy retrieval+gate pipeline. Useful when a themed bench subset regresses on the new path and operators need fleet-wide fallback while the gap gets ported. Why now: the legacy "dinosaur → Edwina" primary-source bug fixes itself on the default path — slim FTS5 cloud parity already proved the run_query orchestrator picks the canonical 'Dinosaur' article on Q5. Smoke confirms it: arborist query "why did the dinosaurs go extinct?" --dry-run → primary: Dinosaur (default — new) arborist query "..." --legacy --dry-run → primary: Edwina, the Dinosaur Who Didn't Know She Was Extinct Known gaps providence_query DOESN'T port today (legacy still has): - pre-retrieval: canonical_projection, crosslang sandwich, quantifier preflight, metacog, soft_preflight, frame_detection - post-retrieval: answerability, repair, witness, sandwich edge-out - merkle_proof column is "[]" placeholder; equivalence_class fallback lookup omitted - args legacy accepts that providence ignores: retrieval_keywords, extra_body, translator, fidelity, over_fetch Forcing-function-style rollout per fox 2026-05-31 — the bench regression-finding IS the next signal. Themed subsets in bench/qa_questions.txt that exercise the missing gates may regress; those are the targets for the next round of porting. CLI changes: - new --legacy flag (with ARBORIST_LEGACY_QUERY=1 env equivalent) - _cmd_query branches on use_legacy → legacy query() vs builds Corpus + calls providence_query - providence_query result.status mapped fresh_persisted/burned → cache_miss_then_written so the bottom-of-function exit-code check stays consistent providence_query.providence_query cache_hit branch now surfaces raw_answer, verifier_method, n_quotes, n_verified, unverified_quotes, violations so the render layer + journal emitter don't crash on None when serving from cache. 283 tests pass in the broader gate.
This commit is contained in:
parent
17b9622a08
commit
492d1a8e7b
2 changed files with 112 additions and 18 deletions
120
arborist/cli.py
120
arborist/cli.py
|
|
@ -644,25 +644,95 @@ def _cmd_query(args: argparse.Namespace) -> int:
|
|||
cli_override=getattr(args, "progress_override", None),
|
||||
)
|
||||
|
||||
result = query(
|
||||
question=args.question,
|
||||
qa_db=qa_db,
|
||||
chat_client=client,
|
||||
model_id=model,
|
||||
revision=revision,
|
||||
quantization=quantization,
|
||||
shards_dir=shards_dir,
|
||||
single_db=single_db,
|
||||
extra_shards=extra_shards,
|
||||
top_k=args.top_k,
|
||||
over_fetch=args.over_fetch,
|
||||
max_context_chars=args.max_context_chars,
|
||||
policy=call_policy,
|
||||
fidelity=getattr(args, "fidelity", None),
|
||||
burn_existing=bool(getattr(args, "burn", False)),
|
||||
retrieval_keywords=getattr(args, "retrieval_keywords", None),
|
||||
progress=progress,
|
||||
# #000072 Phase 2 step 3 — default to providence_query unless the
|
||||
# caller opts back into legacy via --legacy or ARBORIST_LEGACY_QUERY=1.
|
||||
# The legacy path keeps the full pre/post gate stack (canonical_
|
||||
# projection, crosslang sandwich, quantifier guard, metacog,
|
||||
# soft_preflight, answerability, repair, witness, sandwich edge-out);
|
||||
# providence_query skips those today but routes through the unified
|
||||
# run_query orchestrator that fixes the legacy "dinosaur → Edwina"
|
||||
# primary-source bug on the smoke fixture. Bench-gated rollback: if
|
||||
# themed subsets in bench/qa_questions.txt regress on providence,
|
||||
# set ARBORIST_LEGACY_QUERY=1 in env for fleet-wide fallback.
|
||||
use_legacy = (
|
||||
getattr(args, "legacy", False)
|
||||
or os.environ.get("ARBORIST_LEGACY_QUERY") == "1"
|
||||
)
|
||||
if use_legacy:
|
||||
result = query(
|
||||
question=args.question,
|
||||
qa_db=qa_db,
|
||||
chat_client=client,
|
||||
model_id=model,
|
||||
revision=revision,
|
||||
quantization=quantization,
|
||||
shards_dir=shards_dir,
|
||||
single_db=single_db,
|
||||
extra_shards=extra_shards,
|
||||
top_k=args.top_k,
|
||||
over_fetch=args.over_fetch,
|
||||
max_context_chars=args.max_context_chars,
|
||||
policy=call_policy,
|
||||
fidelity=getattr(args, "fidelity", None),
|
||||
burn_existing=bool(getattr(args, "burn", False)),
|
||||
retrieval_keywords=getattr(args, "retrieval_keywords", None),
|
||||
progress=progress,
|
||||
)
|
||||
else:
|
||||
from arborist.qa.providence_query import providence_query
|
||||
from arborist.qa.corpus import (
|
||||
MultiShardSqliteCorpus, SqliteShardCorpus,
|
||||
)
|
||||
from arborist.store import connect as _connect
|
||||
# Build Corpus from the same arg-shape legacy query() resolves:
|
||||
# shards_dir wins when set (multi-shard), else single_db.
|
||||
_all_shard_paths: list[Path] = []
|
||||
if shards_dir:
|
||||
candidates = sorted(Path(shards_dir).glob("*.db"))
|
||||
skip = {"qa.db", "snapshots.db", "selfmodel-chain.db"}
|
||||
_all_shard_paths = [p for p in candidates if p.name not in skip] or candidates
|
||||
if extra_shards:
|
||||
_all_shard_paths.extend(extra_shards)
|
||||
if _all_shard_paths:
|
||||
corpus_obj = MultiShardSqliteCorpus(_all_shard_paths)
|
||||
_closer = corpus_obj.close
|
||||
elif single_db:
|
||||
_conn = _connect(single_db)
|
||||
corpus_obj = SqliteShardCorpus(_conn)
|
||||
_closer = _conn.close
|
||||
else:
|
||||
print(
|
||||
"query needs --shards-dir or --db (or --extra-shards). "
|
||||
"Use --legacy to fall back to the legacy retrieval pipeline.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
# Resolve per-mode max_context_chars when caller didn't set one
|
||||
# — same shape legacy query() uses.
|
||||
mcc = args.max_context_chars
|
||||
if mcc is None:
|
||||
by_mode = call_policy.get("max_context_chars_by_mode") or {}
|
||||
answer_mode = call_policy.get("answer_mode", "quote")
|
||||
mcc = int(by_mode.get(answer_mode, by_mode.get("quote", 24_000)))
|
||||
try:
|
||||
result = providence_query(
|
||||
corpus_obj, args.question, client,
|
||||
qa_db=qa_db,
|
||||
policy=call_policy,
|
||||
model_id=model,
|
||||
burn_existing=bool(getattr(args, "burn", False)),
|
||||
top_k=args.top_k,
|
||||
max_context_chars=mcc,
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
_closer()
|
||||
except Exception:
|
||||
pass
|
||||
# Normalize providence_query status → legacy exit-code values
|
||||
# so the bottom-of-function status check stays consistent.
|
||||
if result.get("status") in ("fresh_persisted", "burned"):
|
||||
result["status"] = "cache_miss_then_written"
|
||||
|
||||
# Emit unfirehose-compatible session journal. One JSONL file per
|
||||
# `make query` invocation, written to ~/.arborist/unfirehose/{slug}/
|
||||
|
|
@ -5817,6 +5887,20 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
"tools that misbehave on stderr noise."
|
||||
),
|
||||
)
|
||||
query_cmd.add_argument(
|
||||
"--legacy", action="store_true",
|
||||
help=(
|
||||
"Use the legacy 2000-line query() retrieval pipeline instead "
|
||||
"of the unified providence_query orchestrator (default since "
|
||||
"#000072 Phase 2 step 3). Legacy preserves the full pre/post "
|
||||
"gate stack (canonical_projection, crosslang sandwich, "
|
||||
"quantifier guard, metacog, soft_preflight, answerability, "
|
||||
"repair, witness, sandwich edge-out) that providence_query "
|
||||
"hasn't ported yet. Use --legacy when a themed bench subset "
|
||||
"regresses on the new path. ARBORIST_LEGACY_QUERY=1 env var "
|
||||
"has the same effect."
|
||||
),
|
||||
)
|
||||
query_cmd.set_defaults(
|
||||
func=_cmd_query, progress_override=None, witness_override=None,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -369,15 +369,25 @@ def providence_query(
|
|||
cached = _lookup(qa_conn, ckey)
|
||||
if cached is not None and not burn_existing:
|
||||
_bump_hit(qa_conn, ckey)
|
||||
# Surface every field the legacy query() result carries
|
||||
# so render layers + journal emitters don't crash on None.
|
||||
return {
|
||||
"status": "cache_hit",
|
||||
"cache_key": ckey,
|
||||
"lookup_path": "primary",
|
||||
"audit_mode": cached["audit_mode"],
|
||||
"answer_text": cached["answer_text"],
|
||||
"raw_answer": cached["answer_text"],
|
||||
"sources": sources,
|
||||
"audit_event_hash": cached["audit_event_hash"],
|
||||
"run_dag_root": cached["run_dag_root"],
|
||||
"verifier_method": cached["verifier_method"] or "none",
|
||||
"n_quotes": int(cached["n_quotes"] or 0),
|
||||
"n_verified": int(cached["n_verified"] or 0),
|
||||
"unverified_quotes": _json.loads(
|
||||
cached["unverified_quotes"] or "[]"
|
||||
),
|
||||
"violations": [],
|
||||
"burned_existing": 0,
|
||||
"elapsed_s": round(_time.time() - t_total, 3),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue