docs(s31): --semantic-retrieval CLI surface + Embedder/Retriever extension points
This commit is contained in:
parent
63734f5bfa
commit
921a8daf71
3 changed files with 65 additions and 4 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -34,6 +34,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
`kost_mot_verdi` placeholder — that cost-vs-value integration is a separate, deferred step. The
|
||||
`costsim` seam note was reworded from the stale "fylles av S5.4 verdirapport" to a truthful
|
||||
forward reference so `costsim`'s own output no longer claims the wiring is done.
|
||||
- Semantic retrieval seam (S3.1): a new MAF-free `semretrieval.py` adds an `Embedder`/`Retriever`
|
||||
pair and a `HybridRanker` blending brute-force numpy cosine over embedded proposal features with
|
||||
the existing structural score, exposed as `--semantic-retrieval` (valid in both run modes).
|
||||
**Off by default and additive**: with no retriever installed the store delegates to
|
||||
`StructuralRetriever`, which reproduces the pre-seam ranking exactly, so the text-excluded
|
||||
default and every existing test are unchanged. Turning it on is a deliberate, gated reversal of
|
||||
that default, since an embedder does see proposal text. Determinism is pinned rather than hoped
|
||||
for: BLAS threads are fixed before numpy is imported, vectors are C-contiguous float64, and
|
||||
ranking uses the total order `(-round(score, 9), id)`. Ships an optional, rebuildable
|
||||
`vectors.npy` + `vectors.jsonl` store (byte-identical regardless of insertion order; fail-fast
|
||||
on a row/line mismatch; missing loads as `None`). numpy is confined to `semretrieval.py` and
|
||||
never enters `okf.py`, `retrieval.py` or `shared/`. The real embeddings client remains a
|
||||
config-only extension point — the shipped `FakeEmbedder` is a deterministic hash projection with
|
||||
no semantics, so this buys a scaling *seam*, not better retrieval quality.
|
||||
- Azure/Foundry offline preflight config gate (`preflight.py`, S4.1).
|
||||
- Offline live-dry-run drill (`--live-dry-run`, S4.2): walks the whole path up to the eager
|
||||
client build and stops before the first model call — zero chat calls.
|
||||
|
|
@ -44,7 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
`allow_egress` opt-in. Not auto-wired into `run.py`.
|
||||
- `docs/knowledge-base-recipe.md` (S5.3, D-H item 1): the documented team process (technical +
|
||||
domain expert) for building a knowledge base, with the honest 1–2 week expectation.
|
||||
- Test suite: 431 passing tests (4 skips are live-provider-only), every wired seam covered by a
|
||||
- Test suite: 488 passing tests (4 skips are live-provider-only), every wired seam covered by a
|
||||
load-bearing test that goes red when the seam is detached.
|
||||
|
||||
### Notes
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -102,9 +102,10 @@ when the seam is detached, so the loop cannot silently degrade into theater.
|
|||
cannot exercise every flag:
|
||||
- **Single-project** — `PROJECT_ID --docs-dir <dir>`, plus optional `--bundle-dir`,
|
||||
`--verdict-dir`, `--outbox-dir` (which requires `--run-id`), `--dimension-config`,
|
||||
`--decision`/`--rationale`, and `--live-dry-run`.
|
||||
- **Portfolio** — `--portfolio`, plus optional `--goals`, `--ledger`, `--dimension-config`;
|
||||
it stops early and prints a `goal reached: …` line when the accumulated ledger meets a goal.
|
||||
`--semantic-retrieval`, `--decision`/`--rationale`, and `--live-dry-run`.
|
||||
- **Portfolio** — `--portfolio`, plus optional `--goals`, `--ledger`, `--dimension-config`,
|
||||
`--semantic-retrieval`; it stops early and prints a `goal reached: …` line when the
|
||||
accumulated ledger meets a goal.
|
||||
- **Value report (S5.4, read-only)** — `--report --ledger <file>` rolls up the ledger's realized
|
||||
savings to stdout: per-project totals, the portfolio total, flagged cross-dimension overlaps
|
||||
(each counted once), and per-entry provenance. Add `--json` for deterministic JSON instead of
|
||||
|
|
@ -121,6 +122,15 @@ when the seam is detached, so the loop cannot silently degrade into theater.
|
|||
uv run python -m portfolio_optimiser.run --report --ledger ledger.json
|
||||
```
|
||||
|
||||
`--semantic-retrieval` (S3.1) is an **opt-in** ranking change, valid in both run modes and
|
||||
**off by default**. Off, prior verdicts are ranked exactly as before: a structural score over
|
||||
the affected cost-code set, measure type and magnitude bucket, with surface text deliberately
|
||||
excluded. On, that score is blended with brute-force cosine over embedded features, so a
|
||||
semantically related prior verdict carrying a *different* code set can also reach the
|
||||
hypothesis prompt. Since an embedder sees the proposal text, turning the flag on is a
|
||||
deliberate, gated reversal of the text-excluded default — which is why it is a flag and not a
|
||||
new default. Nothing about a flag-off run changes, and no savings claim depends on it.
|
||||
|
||||
The **prior-verdict fold — the learning step — happens only on the `--bundle-dir` path**; a
|
||||
plain `--docs-dir`-only run is single-shot (no fold). `--decision`/`--rationale` apply to the
|
||||
single-project path only and are inert in portfolio mode. **`--outbox-dir` must differ from
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue