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:
parent
26e3af9033
commit
2d6a86b991
3 changed files with 50 additions and 6 deletions
6
Makefile
6
Makefile
|
|
@ -166,11 +166,11 @@ QUERY_TOP_K ?= 8
|
||||||
# ANSWER_MODE=quote for the legacy substring verifier. ANSWER_MODE=
|
# ANSWER_MODE=quote for the legacy substring verifier. ANSWER_MODE=
|
||||||
# (empty) defers to DEFAULT_QUERY_POLICY.
|
# (empty) defers to DEFAULT_QUERY_POLICY.
|
||||||
ANSWER_MODE ?= claim_lattice
|
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 \
|
@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
|
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=...]
|
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 \
|
@if [ -z "$$Q" ] && [ -z "$(Q)" ]; then \
|
||||||
|
|
|
||||||
|
|
@ -412,6 +412,7 @@ def _cmd_query(args: argparse.Namespace) -> int:
|
||||||
policy=call_policy,
|
policy=call_policy,
|
||||||
fidelity=getattr(args, "fidelity", None),
|
fidelity=getattr(args, "fidelity", None),
|
||||||
burn_existing=bool(getattr(args, "burn", False)),
|
burn_existing=bool(getattr(args, "burn", False)),
|
||||||
|
retrieval_keywords=getattr(args, "retrieval_keywords", None),
|
||||||
)
|
)
|
||||||
|
|
||||||
if args.json:
|
if args.json:
|
||||||
|
|
@ -3058,6 +3059,21 @@ def build_parser() -> argparse.ArgumentParser:
|
||||||
"constrained inference like Qwen 3.6 reasoner / Claude / GPT-4)."
|
"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)
|
query_cmd.set_defaults(func=_cmd_query)
|
||||||
|
|
||||||
inspect_cmd = sub.add_parser(
|
inspect_cmd = sub.add_parser(
|
||||||
|
|
|
||||||
|
|
@ -1115,6 +1115,7 @@ def query(
|
||||||
chain: str = "private",
|
chain: str = "private",
|
||||||
fidelity: str | None = None,
|
fidelity: str | None = None,
|
||||||
burn_existing: bool = False,
|
burn_existing: bool = False,
|
||||||
|
retrieval_keywords: str | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Answer `question` using the corpus. Cache to qa_db. Returns a result 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-
|
rows were deleted (0 or 1 for the primary key; the equivalence-
|
||||||
class fallback key is left alone so prior alt-mode records stay
|
class fallback key is left alone so prior alt-mode records stay
|
||||||
historic).
|
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
|
policy = policy or DEFAULT_QUERY_POLICY
|
||||||
if fidelity is None:
|
if fidelity is None:
|
||||||
|
|
@ -1151,8 +1168,19 @@ def query(
|
||||||
t_start = time.monotonic()
|
t_start = time.monotonic()
|
||||||
|
|
||||||
# 1. Search.
|
# 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()
|
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:
|
if not hits:
|
||||||
return {
|
return {
|
||||||
"status": "no_sources",
|
"status": "no_sources",
|
||||||
|
|
@ -1164,7 +1192,7 @@ def query(
|
||||||
}
|
}
|
||||||
core_match_roots = getattr(hits, "_core_match_roots", set())
|
core_match_roots = getattr(hits, "_core_match_roots", set())
|
||||||
root_to_shard = getattr(hits, "_root_to_shard", {})
|
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:
|
def _body_density_check(h) -> bool:
|
||||||
# Lazy per-hit check: open the shard, count token mentions in this doc.
|
# Lazy per-hit check: open the shard, count token mentions in this doc.
|
||||||
|
|
@ -1179,7 +1207,7 @@ def query(
|
||||||
|
|
||||||
hits = _rerank(
|
hits = _rerank(
|
||||||
hits,
|
hits,
|
||||||
question,
|
retrieval_query,
|
||||||
core_match_roots=core_match_roots,
|
core_match_roots=core_match_roots,
|
||||||
body_density_check=_body_density_check,
|
body_density_check=_body_density_check,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue