docs(s31): --semantic-retrieval CLI surface + Embedder/Retriever extension points

This commit is contained in:
Kjell Tore Guttormsen 2026-07-25 06:31:44 +02:00
commit 921a8daf71
3 changed files with 65 additions and 4 deletions

View file

@ -115,6 +115,43 @@ that is a **documented hook a deployer would reach for, not a shipped D7 connect
or MCP session is planned. A deployer who wants a network- or MCP-mediated source extends this
family behind the same explicit, per-run network grant; nothing here contacts a live endpoint.
## Bytt ut henteren (Embedder / Retriever, S3.1)
How prior verdicts are ranked is a seam, not a hard-coded sort. `semretrieval.py` declares two
protocols and the store delegates to them:
- **`Retriever`** — `rank(query, candidates, k) -> list[Verdict]`. `VerdictStore.retriever`
defaults to `None`, which means `StructuralRetriever`: the same weighted structural score and
`(-similarity, id)` ordering the store used before the seam existed. Assign your own object
with a `rank` method to replace ranking wholesale.
- **`Embedder`** — `__call__(features) -> np.ndarray`. `HybridRanker(embedder, similarity, weight)`
blends `weight * cosine + (1 - weight) * structural`; both terms live in `[0, 1]`, so `weight`
means what it reads as (`SEMANTIC_WEIGHT_DEFAULT = 0.5`).
```python
store.retriever = HybridRanker(MyEmbedder(), similarity, weight=0.3)
```
Note that `similarity` is **injected**, not imported by `semretrieval`. That is deliberate:
`verdicts.py` imports `agent_framework`, and injecting the score keeps the retrieval layer free
of it (guarded by `tests/test_semretrieval_loadbearing.py`, which ranks in a subprocess and then
asserts `verdicts` never entered `sys.modules`). Keep that property if you extend the module.
**A real embeddings client is a config-only extension point — it is not built here.** The shipped
`FakeEmbedder` is a deterministic sha256 projection with no semantics; it exists so the seam is
exercisable offline at zero cost, and the load-bearing proof is that removing the cosine term
flips the ranking, not that the projection is meaningful. A deployer supplying a real client owns
its network egress, cost, and the fact that it embeds proposal **text** — which is why the hybrid
is opt-in (`--semantic-retrieval`) rather than the default. If you wire one in, note that
`semretrieval` currently imports no network module at all, and a guard test asserts exactly that;
a real client belongs behind the `Embedder` protocol in *your* module, not inside this one.
Optional persistence: `save_vector_store(dir, verdicts, embedder)` / `load_vector_store(dir)`
write a byte-deterministic `vectors.npy` + `vectors.jsonl` pair (sorted by verdict id, atomic
replace). It is a **rebuildable cache, never authoritative** — a missing store loads as `None`
and the caller degrades to structural ranking, while a row/line mismatch raises rather than
silently mis-ranking. `*.npy` is gitignored.
## Bevisst ikke bygget (90 %-kuttlista)
Per the design philosophy (a ~90 % generic core with clear extension points — we do not chase