qa: --retrieval-keywords flag for explicit retrieval augmentation

Empirically observed 2026-05-01: long discursive questions like
'what technology is currently or soon available which may enable
one person to reconstruct another person's thoughts...' under-
retrieve because their content tokens get diluted by template
phrasing. AND-mode FTS5 returns zero hits; OR-fallback ranks
unrelated articles by raw BM25.

Fox's discovery: appending domain keywords ('transcranial knowledge
acquisition') to the question lifts the verdict from HYBRID 6/10
to STRICT 1/1 by narrowing OR-mode retrieval to the topical
article (Neurotechnology). The keywords act as an operator hint
about what the question is really asking.

This commit exposes that pattern as a first-class flag without
polluting the question text:

  --retrieval-keywords TEXT   on `aborist query`
  K="..."                     on `make query`

Plumbing: the keywords are concatenated with the question for
FTS5 search and title-filter token construction only. The LLM
still sees the original question; the verifier still checks against
the original question; cache_key still computes from the original
question. Keywords are session-only — successive calls with
different keywords on the same question can cache-hit each other.
Pair with BURN=1 for fresh inference per call.

Live verification:

  make query Q="...thoughts...without speaking or sign language."
       K="transcranial knowledge acquisition" BURN=1
  -> STRICT 2/2 via claim_lattice, 37.5s, Neurotechnology article
     cited for fMRI and DBS claims.

Without K: HYBRID 6/10 with Videoconferencing/Telepathy as top
sources — model hallucinated structure across irrelevant chunks.
This commit is contained in:
russell@unturf.com 2026-05-01 10:30:13 -04:00
parent 26e3af9033
commit 2d6a86b991
No known key found for this signature in database
3 changed files with 50 additions and 6 deletions

View file

@ -166,11 +166,11 @@ QUERY_TOP_K ?= 8
# ANSWER_MODE=quote for the legacy substring verifier. ANSWER_MODE=
# (empty) defers to DEFAULT_QUERY_POLICY.
ANSWER_MODE ?= claim_lattice
query: bootstrap ## ask the corpus a question [JSON=1 BURN=1 REPAIR=1 REPROMPTS=N ANSWER_MODE=claim_lattice|claim_lattice_pointer|quote]; JSON by default
query: bootstrap ## ask the corpus a question [JSON=1 BURN=1 REPAIR=1 REPROMPTS=N K="extra retrieval keywords" ANSWER_MODE=claim_lattice|claim_lattice_pointer|quote]; JSON by default
@if [ -z "$$Q" ] && [ -z "$(Q)" ]; then \
echo "usage: make query Q=\"your question\" [JSON=1 BURN=1 REPAIR=1 REPROMPTS=N ANSWER_MODE=claim_lattice|claim_lattice_pointer|quote]"; exit 2; \
echo "usage: make query Q=\"your question\" [JSON=1 BURN=1 REPAIR=1 REPROMPTS=N K=\"extra retrieval keywords\" ANSWER_MODE=claim_lattice|claim_lattice_pointer|quote]"; exit 2; \
fi
$(ABORIST) --shards-dir $(SHARDS_DIR) query --top-k $(QUERY_TOP_K) $(if $(JSON),--json,) $(if $(BURN),--burn,) $(if $(REPAIR),--repair,) $(if $(REPROMPTS),--repair-reprompts $(REPROMPTS),) $(if $(ANSWER_MODE),--answer-mode $(ANSWER_MODE),) "$(Q)"
$(ABORIST) --shards-dir $(SHARDS_DIR) query --top-k $(QUERY_TOP_K) $(if $(JSON),--json,) $(if $(BURN),--burn,) $(if $(REPAIR),--repair,) $(if $(REPROMPTS),--repair-reprompts $(REPROMPTS),) $(if $(ANSWER_MODE),--answer-mode $(ANSWER_MODE),) $(if $(K),--retrieval-keywords "$(K)",) "$(Q)"
query-dry: bootstrap ## like 'make query' but skip the LLM call (dry-run) [JSON=1 BURN=1 ANSWER_MODE=...]
@if [ -z "$$Q" ] && [ -z "$(Q)" ]; then \

View file

@ -412,6 +412,7 @@ def _cmd_query(args: argparse.Namespace) -> int:
policy=call_policy,
fidelity=getattr(args, "fidelity", None),
burn_existing=bool(getattr(args, "burn", False)),
retrieval_keywords=getattr(args, "retrieval_keywords", None),
)
if args.json:
@ -3058,6 +3059,21 @@ def build_parser() -> argparse.ArgumentParser:
"constrained inference like Qwen 3.6 reasoner / Claude / GPT-4)."
),
)
query_cmd.add_argument(
"--retrieval-keywords", dest="retrieval_keywords", default=None,
help=(
"operator-supplied keywords appended to the question for "
"FTS5 retrieval ONLY — never sent to the LLM, never enters "
"cache_key, never reaches the verifier. Use to narrow "
"OR-mode retrieval on long discursive questions whose "
"content tokens get diluted by template phrasing. Example: "
"make query Q='what tech may enable one person to "
"reconstruct another person's thoughts...' "
"K='transcranial knowledge acquisition'. Pair with --burn "
"to force fresh inference (keywords are session-only and "
"cache-hits ignore them)."
),
)
query_cmd.set_defaults(func=_cmd_query)
inspect_cmd = sub.add_parser(

View file

@ -1115,6 +1115,7 @@ def query(
chain: str = "private",
fidelity: str | None = None,
burn_existing: bool = False,
retrieval_keywords: str | None = None,
) -> dict:
"""Answer `question` using the corpus. Cache to qa_db. Returns a result dict.
@ -1135,6 +1136,22 @@ def query(
rows were deleted (0 or 1 for the primary key; the equivalence-
class fallback key is left alone so prior alt-mode records stay
historic).
`retrieval_keywords` augments the FTS5 search and title-filter
token set with operator-supplied keywords WITHOUT changing what
the LLM sees, what the verifier checks, or what enters cache_key.
Empirically observed 2026-05-01: long discursive questions like
'what technology is currently or soon available which may enable
one person to reconstruct another person's thoughts...' under-
retrieve because their content tokens get diluted by template
phrasing. Appending domain keywords ('transcranial knowledge
acquisition') narrows OR-mode FTS5 to the topical article
(Neurotechnology) and lifts the verdict from HYBRID to STRICT.
This flag exposes that pattern explicitly. Cache implication:
keywords are session-only not in cache_key, so successive
calls with different keywords on the same question can cache-
hit each other. Pair with ``burn_existing=True`` for fresh
inference each call.
"""
policy = policy or DEFAULT_QUERY_POLICY
if fidelity is None:
@ -1151,8 +1168,19 @@ def query(
t_start = time.monotonic()
# 1. Search.
#
# Retrieval-only query string: question + operator-supplied
# ``retrieval_keywords`` (a hint, never part of the cache_key /
# LLM prompt / verifier surface). When the user passes
# `--retrieval-keywords "transcranial knowledge acquisition"`,
# only the FTS5 MATCH and title-filter token set see those
# tokens; the question text fed to the LLM and to question_hash
# stays untouched.
retrieval_query = question
if retrieval_keywords and retrieval_keywords.strip():
retrieval_query = f"{question} {retrieval_keywords.strip()}"
t_phase = time.monotonic()
hits = _search_corpus(shards_dir, single_db, question, over_fetch)
hits = _search_corpus(shards_dir, single_db, retrieval_query, over_fetch)
if not hits:
return {
"status": "no_sources",
@ -1164,7 +1192,7 @@ def query(
}
core_match_roots = getattr(hits, "_core_match_roots", set())
root_to_shard = getattr(hits, "_root_to_shard", {})
qtokens_lower = {t.lower() for t in _title_query_tokens(question)}
qtokens_lower = {t.lower() for t in _title_query_tokens(retrieval_query)}
def _body_density_check(h) -> bool:
# Lazy per-hit check: open the shard, count token mentions in this doc.
@ -1179,7 +1207,7 @@ def query(
hits = _rerank(
hits,
question,
retrieval_query,
core_match_roots=core_match_roots,
body_density_check=_body_density_check,
)