Skip to content

Commit d4bfb62

Browse files
test(recall): make the arm-candidate-k ceiling test non-vacuous
The previous test_arm_candidate_k_cap_clamps_ceiling_when_first_page_insufficient only checked max(index.requested) <= 8. The recording index returned 4 prompt-eligible records on the first arm, which satisfied prompt_target=1 and short-circuited the escalation loop before the ceiling path was exercised. With the cap removed, the test still passed -- the cap was unverified. Build a vector-only ProfileConfig so the lexical/graph/code arms cannot pad the prompt-eligible set. Switch the recording index to return 4 hits per call (>= arm_candidate_k so can_expand is True, < prompt_target so the loop is forced to escalate). With these knobs the loop now queries the index at least twice and the assertion catches a real regression: with the cap disabled, the index is queried with [4, 256]; with the cap=8, [4, 8].
1 parent 960b130 commit d4bfb62

1 file changed

Lines changed: 65 additions & 15 deletions

File tree

tests/test_recall_arm_candidate_k_cap.py

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,29 @@ def _add(store, emb, wid, rid, text, **kw):
4040
class _RecordingIndex:
4141
"""Vector-index double that records every arm size it was queried with."""
4242

43-
def __init__(self):
43+
def __init__(self, hit_count: int | None = None, real_ids: list[str] | None = None):
44+
"""If ``hit_count`` is set, the index always returns exactly that many
45+
hits per call (up to ``k``). ``None`` (default) returns the synthetic
46+
up-to-4 series; ``0`` returns nothing; any positive int returns that
47+
many. ``real_ids`` (if given) replaces the synthetic id prefix so the
48+
returned ids resolve to real ``MemoryRecord`` rows in the store --
49+
otherwise the prompt-eligibility filter discards them and the
50+
escalation loop is short-circuited on empty recs.
51+
"""
4452
self.requested: list[int] = []
4553
self.records: list[tuple[str, float]] = []
54+
self._hit_count = hit_count
55+
self._real_ids = list(real_ids) if real_ids else None
4656

4757
def search(self, query, k, *, filter=None):
4858
self.requested.append(int(k))
49-
# Return synthetic (id, score) pairs so the prompt-eligible path has
50-
# something to score. Use distinct ids so the loop tests candidate count.
51-
return [(f"mem_{i}", float(k - i)) for i in range(min(k, 4))]
59+
if self._hit_count == 0:
60+
return []
61+
cap = min(k, 4) if self._hit_count is None else min(k, self._hit_count)
62+
if self._real_ids is not None:
63+
return [(self._real_ids[i % len(self._real_ids)], float(k - i))
64+
for i in range(cap)]
65+
return [(f"mem_{i}", float(k - i)) for i in range(cap)]
5266

5367

5468
def test_arm_candidate_k_cap_default_is_none(monkeypatch):
@@ -106,22 +120,58 @@ def test_arm_candidate_k_cap_clamps_ceiling_when_first_page_insufficient(monkeyp
106120
"""The second page must also be clamped so the escalation loop does not
107121
silently undo the savings by jumping to PROMPT_ONLY_MIN_CANDIDATES."""
108122
monkeypatch.setenv("ENGRAPHIS_RECALL_ARM_CANDIDATE_K", "8")
109-
index = _RecordingIndex()
123+
# Build an engine whose only enabled arm is the vector arm, so the
124+
# lexical/graph/code arms cannot pad the prompt-eligible record set
125+
# and short-circuit the ceiling path. The recording index returns
126+
# exactly 1 hit per call (less than the prompt_target of 2 below and
127+
# less than arm_candidate_k=4 so can_expand is True), so the
128+
# escalation loop is forced into the arm_candidate_k =
129+
# candidate_ceiling branch.
130+
from engraphis.core.retrieval_policy import ProfileConfig
131+
vector_only = ProfileConfig(
132+
name="vector-only-test", vector=True, lexical=False, graph=False, code=False
133+
)
110134
eng = RecallEngine(Store(":memory:"), _SemanticTestEmbedder(256),
111-
index, IdentityReranker())
135+
_RecordingIndex(), IdentityReranker())
112136
store = eng.store
113137
wid = store.get_or_create_workspace("w")
138+
real_ids = []
114139
for i in range(20):
115-
_add(store, eng.embedder, wid, None, f"fact {i}")
116-
117-
eng.recall("fact 0", SearchFilter(workspace_id=wid), k=1,
118-
candidate_k=1, prompt_only=True)
119-
120-
# First arm is 1 + min(250, 1*3) = 4, ceiling would normally escalate to
121-
# PROMPT_ONLY_MIN_CANDIDATES=256; with cap=8 the ceiling must also be 8.
140+
real_ids.append(_add(store, eng.embedder, wid, None, f"fact {i}"))
141+
142+
index = _RecordingIndex(hit_count=4, real_ids=real_ids)
143+
eng.index = index
144+
145+
eng.recall("zzz-unmatched-query-zzz", SearchFilter(workspace_id=wid), k=8,
146+
candidate_k=1, prompt_only=True, arm_config=vector_only)
147+
148+
# First arm: 1 + min(250, 1*3) = 4 (clamped at min(8, 4) = 4, then
149+
# floored at candidate_k=1, so 4). With the cap, the second-page
150+
# ceiling is min(256, 8) = 8. Without the cap the index would have
151+
# been queried with [4, 256]. The recording index returns 4 hits per
152+
# call (>= arm_candidate_k=4 so can_expand=True; < prompt_target=8
153+
# so the loop is forced to escalate to the ceiling).
154+
assert index.requested, "index was never queried -- test setup is broken"
155+
assert index.requested[0] == 4, (
156+
f"first arm must be 4 (cap=8, candidate_k=1, prompt_only), "
157+
f"got {index.requested[0]}"
158+
)
159+
assert all(requested <= 8 for requested in index.requested), (
160+
f"every index query must respect the cap=8 ceiling, "
161+
f"got {index.requested}"
162+
)
122163
assert max(index.requested) <= 8
123-
assert all(requested <= 8 for requested in index.requested)
124-
# Without the cap the index would have been queried with [4, 256].
164+
# Sanity: the loop must have actually escalated, i.e. it must have
165+
# queried the index at least twice. If it queried only once, the
166+
# first arm satisfied prompt_target and the ceiling-clamp code path
167+
# was not exercised -- which is the vacuous case this test guards
168+
# against.
169+
assert len(index.requested) >= 2, (
170+
f"ceiling clamp must be exercised via the escalation loop; "
171+
f"only {len(index.requested)} index queries were recorded -- "
172+
f"the first arm already satisfied prompt_target and the cap "
173+
f"code path is not being run"
174+
)
125175

126176

127177
def test_arm_candidate_k_cap_floor_protects_one_fact_corpus(monkeypatch):

0 commit comments

Comments
 (0)