feat(retrieval): second meta-belief consumer — adaptive bm25f_anchor_weight (#757) - #787
Conversation
…ta-belief env flag First slice of #757 consumer wiring. Pure additions — no caller changes yet, so retrieval is byte-identical until the read-path commits land. Adds: - META_BM25F_ANCHOR_WEIGHT_KEY, BM25F_ANCHOR_WEIGHT_FLOOR (1), BM25F_ANCHOR_WEIGHT_CEIL (10), META_BM25F_ANCHOR_WEIGHT_STATIC_DEFAULT (0.5), META_BM25F_ANCHOR_WEIGHT_POSTERIOR_DECAY_SECONDS (30d). - ENV_META_BELIEF_BM25F_ANCHOR_WEIGHT = "AELFRICE_META_BELIEF_BM25F_ANCHOR_WEIGHT", ships default-OFF behind the #437 A/B bench-gate clause. - decode_meta_bm25f_anchor_weight(v) — log-linear interpolation [0, 1] -> [1, 10], rounded to int (BM25Index.build replicates the anchor token stream by an int count, see bm25.py:243). v=0.5 decodes to 3, matching DEFAULT_ANCHOR_WEIGHT exactly so the cold-start install preserves byte-identical retrieval order. - is_meta_belief_bm25f_anchor_weight_enabled() — truthy-token resolver mirroring is_meta_belief_half_life_enabled(), including the '=enabled' spelling from the umbrella #480 issue body. Encoding lives in the consumer per the 2026-05-13 #756 ratification. The substrate stays pattern-uniform across #480 sub-tasks B–F; each consumer picks its own bounds at integration time. Scope-down note: #757's body asked for per-field weights, but aelfrice's BM25F has one tunable (anchor_weight). Scope was ratified down to anchor_weight only before any code was written.
…a-aware resolver Second slice of #757. Still no caller change in retrieve() or _l1_hits — the new resolve_bm25f_anchor_weight_with_meta() function exists in parallel, ready for the third commit to wire in at the BM25IndexCache construction site. Adds: - install_bm25f_anchor_weight_meta_belief(store, *, now_ts) — idempotent install of the #757 row with the v3.x ratified defaults: bm25_l0_ratio signal only (relevance deferred to #779 per D4, same split as #756), 30d posterior decay, static_default=0.5 (decodes to 3, matching bm25.DEFAULT_ANCHOR_WEIGHT exactly). - resolve_bm25f_anchor_weight_with_meta(store, *, now_ts, explicit=None) — three-tier precedence: explicit kwarg > meta-belief (gated on the env flag) > bm25.DEFAULT_ANCHOR_WEIGHT. Unlike the #756 resolver this one has no env-var or TOML override layer because anchor_weight has never had a user-facing config knob — it has been a code constant since v1.5/#148 R3. Keeping the surface narrow avoids introducing a public config that we'd have to keep stable. The bm25.DEFAULT_ANCHOR_WEIGHT import is local (inside both helpers) to avoid a top-of-module aelfrice.retrieval ↔ aelfrice.bm25 cycle on the forward-ref direction — bm25 already imports nothing from retrieval, but a top-level reverse import would force ordering constraints under test collection. Smoke-checked the precedence ladder against an in-memory store: no store → 3, store + flag-off → 3, store + flag-on + cold meta-belief → 3 (byte-identical to baseline at cold start), explicit=7 always wins, re-install returns False.
…ratio signal update Closes the #757 loop in _l1_hits. The BM25F branch's BM25IndexCache construction now drives anchor_weight through resolve_bm25f_anchor_weight_with_meta when the caller hasn't supplied a cache, and on each query (when the meta feature flag is on) records a bm25_l0_ratio sub-posterior update: evidence is the fraction of store-locked beliefs that appeared in BM25F's top-K for the query. Wiring: - Caller-supplied bm25f_cache stays the unconditional override. The bench harness and unit tests still pin specific anchor_weights via the cache, so determinism contracts there are untouched. - The signal update fires inside _l1_hits because both the BM25F raw scored_pairs (top-K bid list) and the store handle are local there; threading them to retrieve_v2's tail would have been the same logic at one more level of indirection. - update_meta_belief is wrapped in try/except — store errors are printed to stderr and swallowed, mirroring the #756 latency-signal posture in retrieve_v2 (commit 521f668). Retrieval must never raise on a meta-belief write failure. - No locked beliefs in the store means no signal update (skip; evidence would be 0/0 division otherwise). That's the right fail-soft: empty L0 is uninformative for an L0-coverage metric. Default-OFF byte-identical contract: with the env flag unset, the read path collapses to BM25IndexCache(store, anchor_weight=3) via the resolver's fallback to bm25.DEFAULT_ANCHOR_WEIGHT, identical to the pre-#757 BM25IndexCache(store) call. Signal updates are fully gated on the same env flag. All 27 BM25-touching tests still pass under -k bm25.
…nsumer Mirrors tests/test_temporal_half_life_meta.py from PR #784 (#756), scoped to the smaller surface #757 exposes: - Log-linear encoding: floor / ceil / mid=DEFAULT_ANCHOR_WEIGHT parity / out-of-range clamping / int-valued return / monotonic non-decreasing over the unit interval. - Env feature flag: default-off / truthy + 'enabled' / falsy. - Constants sanity: pinned to the #757 ratified defaults so a careless edit surfaces in this file, not in a bench surprise. - Install helper: first-call True / second-call no-op (idempotency contract). - Resolver precedence: no-store fallback / flag-off ignores installed meta-belief / flag-on cold-start matches static default / explicit kwarg wins / flag-on-no-install falls through without materialising a phantom row. - _l1_hits integration: default-OFF byte-identical posture (no signal recorded with flag-off even when row is installed) / flag-on records a bm25_l0_ratio sub-posterior / flag-on no-install no-crash / caller-supplied bm25f_cache bypasses the resolver (bench harness pin contract). 30 tests, all green under uv run pytest.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR-size soft capThis PR is over the advisory size threshold:
Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the |
There was a problem hiding this comment.
Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
Closes #757. Sub-task C of umbrella #480 — pattern reuse of #756 (just shipped via #784), scoped to the actual single-knob BM25F that aelfrice has rather than the multi-field BM25F the issue body assumed.
Scope-down call (ratified before any code)
#757's issue body assumed a per-field BM25F with weights for title/body/tags. The actual code in
src/aelfrice/bm25.pyhas one tunable:anchor_weight(DEFAULT_ANCHOR_WEIGHT = 3), the weight on the incoming-anchor token stream from #148 R3. There are no title/body/tags fields. Scope was ratified down toanchor_weightonly before any code was written; this PR honours that.Commit layout (5 atomic, all SSH-signed)
feat(retrieval): log-linear bounded bm25f_anchor_weight encoding + meta-belief env flag— constants, decoder, env-flag predicate. Pure additions, no caller change.feat(retrieval): install_bm25f_anchor_weight_meta_belief helper + meta-aware resolver— installer + 3-tier resolver. Still no caller change.feat(retrieval): wire meta-belief anchor_weight into BM25F + bm25_l0_ratio signal update—_l1_hitsBM25F branch reads through the resolver when no cache is supplied; emitsbm25_l0_ratioevidence per query when the flag is on.test(retrieval): coverage for #757 bm25f_anchor_weight meta-belief consumer— 30 new tests mirroringtests/test_temporal_half_life_meta.py.docs(changelog): note adaptive bm25f_anchor_weight consumer (#757)— Unreleased entry.Default-OFF byte-identical contract
Without
AELFRICE_META_BELIEF_BM25F_ANCHOR_WEIGHT, the read path collapses toBM25IndexCache(store, anchor_weight=3)via the resolver's fallback tobm25.DEFAULT_ANCHOR_WEIGHT— identical to the pre-#757BM25IndexCache(store)call. Signal updates are gated on the same flag. Caller-suppliedbm25f_cacheis honoured unconditionally so the bench harness pin contract is preserved.Cold-start parity
static_default = 0.5decodes throughdecode_meta_bm25f_anchor_weighttoround(sqrt(1*10)) = round(3.162) = 3 = DEFAULT_ANCHOR_WEIGHT. Installing the meta-belief preserves byte-identical retrieval order until evidence actually moves the posterior.Signal
bm25_l0_ratio= fraction of store-locked beliefs that BM25F's top-K also surfaced. Updated per query when both the flag is on and the store has at least one locked belief. Empty L0 → skip (undefined metric). Per the same D4 split #756 followed, therelevancesignal stays deferred to #779.Test plan
uv run pytest tests/test_bm25f_anchor_weight_meta.py)tests/test_bm25_index.pystill passesbenchmarks/results/v2.0.0.jsonis canonical,uv sync && aelf bench all#437 A/B corpus) — gates flipping the env flag on by default; outside this PR