fix(s31): close 1 review BLOCKER — per-call retriever + run_portfolio forwards semantic_retrieval
This commit is contained in:
parent
969d450b31
commit
fc69285f2c
3 changed files with 231 additions and 57 deletions
|
|
@ -264,20 +264,30 @@ class VerdictStore:
|
|||
that want a fresh store straight from a folder."""
|
||||
return cls(verdicts=load_verdicts_from_dir(directory))
|
||||
|
||||
def retrieve(self, query: ProposalFeatures, k: int) -> list[Verdict]:
|
||||
def retrieve(
|
||||
self,
|
||||
query: ProposalFeatures,
|
||||
k: int,
|
||||
*,
|
||||
retriever: semretrieval.Retriever | None = None,
|
||||
) -> list[Verdict]:
|
||||
"""Return the top-``k`` verdicts. Deterministic: ties break by verdict id, so ordering
|
||||
is stable across runs.
|
||||
|
||||
Ranking is delegated to ``self.retriever``, defaulting to ``StructuralRetriever`` — the
|
||||
same weighted structural score and ``(-similarity, id)`` key as before the seam existed.
|
||||
A caller that installs a ``HybridRanker`` opts into an additional semantic term."""
|
||||
Ranking resolves in three steps: the explicit ``retriever`` argument, then
|
||||
``self.retriever``, then ``StructuralRetriever`` — the same weighted structural score and
|
||||
``(-similarity, id)`` key as before the seam existed.
|
||||
|
||||
``retriever`` is PER CALL and keyword-only. It exists because the alternative — assigning
|
||||
``store.retriever`` — mutates an object the caller owns, so an opt-in made for one
|
||||
retrieval silently governed every later use of that store (including a subsequent run with
|
||||
the flag OFF). ``self.retriever`` survives as the store-level default for callers that
|
||||
genuinely want a store to rank one way for its whole lifetime; see ``docs/extending.md``."""
|
||||
if k <= 0:
|
||||
raise ValueError(f"k must be positive, got {k}")
|
||||
ranker = (
|
||||
self.retriever
|
||||
if self.retriever is not None
|
||||
else semretrieval.StructuralRetriever(similarity)
|
||||
)
|
||||
ranker = retriever if retriever is not None else self.retriever
|
||||
if ranker is None:
|
||||
ranker = semretrieval.StructuralRetriever(similarity)
|
||||
return ranker.rank(query, self.verdicts, k)
|
||||
|
||||
def add(self, verdict: Verdict) -> None:
|
||||
|
|
@ -298,14 +308,24 @@ class ExpeLContextProvider(ContextProvider):
|
|||
"""Wraps ``VerdictStore.retrieve`` for ExpeL few-shot injection via the GA
|
||||
``ContextProvider`` hook."""
|
||||
|
||||
def __init__(self, store: VerdictStore, query: ProposalFeatures, *, k: int = 3) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
store: VerdictStore,
|
||||
query: ProposalFeatures,
|
||||
*,
|
||||
k: int = 3,
|
||||
retriever: semretrieval.Retriever | None = None,
|
||||
) -> None:
|
||||
super().__init__(source_id="expel-verdictstore")
|
||||
self._store = store
|
||||
self._query = query
|
||||
self._k = k
|
||||
# Per-call ranker, threaded straight through to ``retrieve`` — see its docstring for why
|
||||
# this is a parameter rather than an assignment on the store.
|
||||
self._retriever = retriever
|
||||
|
||||
def format_fewshot(self) -> str:
|
||||
hits = self._store.retrieve(self._query, self._k)
|
||||
hits = self._store.retrieve(self._query, self._k, retriever=self._retriever)
|
||||
body = "\n".join(f"- [{v.id}] {v.decision}: {v.rationale}" for v in hits)
|
||||
return f"Relevant prior verdicts (ExpeL few-shot):\n{body}"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue