pi-smart-router 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -0
- package/config/benchmark-profiles.json +2 -2
- package/config/operator-config.json.example +7 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +3 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/domain/matching/embedding-provider.d.ts +63 -1
- package/dist/domain/matching/embedding-provider.d.ts.map +1 -1
- package/dist/domain/matching/embedding-provider.js +117 -0
- package/dist/domain/matching/embedding-provider.js.map +1 -1
- package/dist/domain/matching/encoder-gate.d.ts +73 -0
- package/dist/domain/matching/encoder-gate.d.ts.map +1 -0
- package/dist/domain/matching/encoder-gate.js +66 -0
- package/dist/domain/matching/encoder-gate.js.map +1 -0
- package/dist/domain/matching/hydra-matcher.d.ts +24 -3
- package/dist/domain/matching/hydra-matcher.d.ts.map +1 -1
- package/dist/domain/matching/hydra-matcher.js +21 -2
- package/dist/domain/matching/hydra-matcher.js.map +1 -1
- package/dist/domain/pipeline/router-pipeline.d.ts.map +1 -1
- package/dist/domain/pipeline/router-pipeline.js +12 -0
- package/dist/domain/pipeline/router-pipeline.js.map +1 -1
- package/dist/domain/types/entities.d.ts +15 -0
- package/dist/domain/types/entities.d.ts.map +1 -1
- package/dist/domain/types/schemas.d.ts +63 -7
- package/dist/domain/types/schemas.d.ts.map +1 -1
- package/dist/domain/types/schemas.js +46 -3
- package/dist/domain/types/schemas.js.map +1 -1
- package/package.json +1 -1
- package/src/config/defaults.ts +3 -0
- package/src/domain/matching/embedding-provider.ts +205 -1
- package/src/domain/matching/encoder-gate.ts +121 -0
- package/src/domain/matching/hydra-matcher.ts +53 -3
- package/src/domain/pipeline/router-pipeline.ts +13 -0
- package/src/domain/types/entities.ts +15 -0
- package/src/domain/types/schemas.ts +56 -7
package/README.md
CHANGED
|
@@ -991,6 +991,28 @@ Set the encoder in operator config:
|
|
|
991
991
|
|
|
992
992
|
MiniLM remains the default fallback when `encoder` is omitted. Both encoders produce 384-dim vectors compatible with the SP-115 learned projection head.
|
|
993
993
|
|
|
994
|
+
**Opt-in per-prompt encoder cascade ([#173](https://github.com/beettlle/pi-smart-router/issues/173), v1.2.0 — default OFF).** Instead of a process-wide single encoder, an opt-in gate routes prompts whose estimated token count reaches a threshold to the long-context encoder, avoiding MiniLM's 512-token truncation on long turns:
|
|
995
|
+
|
|
996
|
+
```json
|
|
997
|
+
{
|
|
998
|
+
"hydra": {
|
|
999
|
+
"artifact_cache_path": ".pi-smart-router/models/",
|
|
1000
|
+
"encoder": "minilm",
|
|
1001
|
+
"encoder_cascade": {
|
|
1002
|
+
"enabled": true,
|
|
1003
|
+
"long_context_encoder": "granite",
|
|
1004
|
+
"token_threshold": 512
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
- **Default off** — existing single-encoder installs are unaffected and pay zero added cost (the gate is pre-embedding arithmetic, ~sub-microsecond p50 in replay).
|
|
1011
|
+
- **Lazy memory** — the Granite ONNX session loads only on the first over-threshold prompt; short prompts keep using the same MiniLM session, so their routing decisions are identical to the MiniLM-only baseline.
|
|
1012
|
+
- **Degrade, never mix** — encoders embed into different vector spaces. If Granite is unavailable at request time, routing continues on MiniLM with `granite_fallback` telemetry; the cascade never compares cross-encoder vectors. Per-encoder artifacts: bootstrap Granite centroids with `npm run routing:bootstrap-centroids -- --encoder granite` (namespaced `config/routing-centroids.granite.json`); `npm run routing:verify-calibration` rejects mixed-encoder bundles, and Granite-side projection ships honest-untrained.
|
|
1013
|
+
- **Telemetry** — cascade-enabled decisions carry `encoder_selected`, `token_estimate`, `cascade_threshold`, and `cascade_fallback_reason` on the feature sidecar.
|
|
1014
|
+
- **Evidence, not a default flip** — see the cascade replay report [`docs/qa/encoder-cascade-replay-v1.2.0.md`](docs/qa/encoder-cascade-replay-v1.2.0.md). Promoting the cascade (or Granite) to a default is a future ticket gated on the #173 eval table.
|
|
1015
|
+
|
|
994
1016
|
**Latency budget:** the HyDRA embedding stage targets ~80–120 ms per turn. Compare MiniLM vs Granite on held-out agent turn samples:
|
|
995
1017
|
|
|
996
1018
|
```bash
|
|
@@ -1000,6 +1022,62 @@ npm run benchmark:encoder
|
|
|
1000
1022
|
|
|
1001
1023
|
The script reports p50/p95 latency for each encoder and asserts Granite p50/p95 stay within the 120 ms budget ceiling. Requires `@huggingface/transformers` and a one-time ONNX artifact download.
|
|
1002
1024
|
|
|
1025
|
+
#### Granite opt-in dogfood runbook ([#167](https://github.com/beettlle/pi-smart-router/issues/167))
|
|
1026
|
+
|
|
1027
|
+
Operator enablement for the Granite 97M long-context encoder on dogfood installs. **MiniLM remains the shipped default** — this runbook never flips `encoder` defaults; promotion is decided only through [#96](https://github.com/beettlle/pi-smart-router/issues/96) with dogfood evidence. The code path already shipped in [#80](https://github.com/beettlle/pi-smart-router/issues/80); do not reimplement it.
|
|
1028
|
+
|
|
1029
|
+
**1. Fetch the model.** No manual download step is required — the ONNX artifact fetches on demand:
|
|
1030
|
+
|
|
1031
|
+
| Item | Value |
|
|
1032
|
+
|------|-------|
|
|
1033
|
+
| Runtime HF ONNX id | `onnx-community/granite-embedding-97m-multilingual-r2-ONNX` (`GRANITE_ONNX_MODEL` in `src/domain/matching/embedding-provider.ts`) |
|
|
1034
|
+
| Upstream weights | `ibm-granite/granite-embedding-97m-multilingual-r2` (384-dim, long context) |
|
|
1035
|
+
| Cache dir | `hydra.artifact_cache_path` (default `.pi-smart-router/models/` — gitignored) |
|
|
1036
|
+
| Fetch trigger | First HyDRA embed with `encoder: granite`, **or** `npm run benchmark:encoder` (downloads on demand) |
|
|
1037
|
+
| Offline | One-time Hugging Face download; fully offline after cache warm (see Supply-chain below for pin modes) |
|
|
1038
|
+
|
|
1039
|
+
Expected cache layout after fetch:
|
|
1040
|
+
|
|
1041
|
+
```text
|
|
1042
|
+
.pi-smart-router/models/onnx-community/granite-embedding-97m-multilingual-r2-ONNX/
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
**2. Switch config (operator).** Edit `config/operator-config.json` on the dogfood host:
|
|
1046
|
+
|
|
1047
|
+
```json
|
|
1048
|
+
{
|
|
1049
|
+
"hydra": {
|
|
1050
|
+
"artifact_cache_path": ".pi-smart-router/models/",
|
|
1051
|
+
"encoder": "granite",
|
|
1052
|
+
"hydra_heads": "learned_projection"
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
Keep `hydra_heads: learned_projection` — Granite stays 384-dim compatible with the SP-115 projection (`config/hydra-projection-weights.json`). Do **not** enable `modernbert_k4` (still blocked on trained `config/modernbert-k4-heads.json`; tracked by #96).
|
|
1058
|
+
|
|
1059
|
+
**3. Verify resident.** After a routed request, run `/smart-router plan` or `/smart-router doctor` to confirm the Granite encoder model and cache path are loaded. Smoke-check that routing still selects tiers and that there is **no silent MiniLM fallback** — if the encoder errors or exceeds budget, the neural stage fails open by design (#119 / #148) and telemetry/`reason_code` surfaces it; a crash or silent downgrade is a bug, not expected behavior.
|
|
1060
|
+
|
|
1061
|
+
**4. Measure and archive.** Run the encoder benchmark and archive output for the issue trail:
|
|
1062
|
+
|
|
1063
|
+
```bash
|
|
1064
|
+
mkdir -p .pi-smart-router/measurements/
|
|
1065
|
+
npm run benchmark:encoder | tee .pi-smart-router/measurements/benchmark-encoder-$(date +%Y%m%d).txt
|
|
1066
|
+
# optional: --fixtures path --cache .pi-smart-router/models/
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
Expectation: Granite p50/p95 within the ≤120 ms HyDRA embedding-stage budget (SP-204 go/no-go measured ~17 ms p50 for both encoders; [artifact](spine-tasks/_authoring/release-v0.11.0/encoder-gonogo-artifact.md)). `.pi-smart-router/measurements/` is gitignored — post a short summary comment on #167 with the numbers.
|
|
1070
|
+
|
|
1071
|
+
**5. Post-switch follow-ups (feed #96 — evidence only, no default flip).** Track after Granite is live on the dogfood host:
|
|
1072
|
+
|
|
1073
|
+
- **Latency regression watch** — HyDRA stage stays within ~80–120 ms on dogfood hardware vs SP-204 baselines; re-run `npm run benchmark:encoder` after fleet/catalog changes.
|
|
1074
|
+
- **Centroid / cluster space** — centroids were bootstrapped with MiniLM (`npm run routing:bootstrap-centroids`). Same 384-dim space, but embedding geometry differs; dry-run compare cluster match quality under Granite and document whether `config/routing-centroids.json` / the calibration bundle need refresh **before** any default flip.
|
|
1075
|
+
- **Projection / calibration** — SP-115 weights were trained on MiniLM embeddings. Run `npm run routing:calibration-dry-run` under Granite; if holdout ECE worsens, retrain the projection **or** keep MiniLM as the shipped default.
|
|
1076
|
+
- **Truncation / quality signal** — confirm long agent turns no longer hit MiniLM's 512-token wall (the primary motivation); note any residual encoder truncation surfaced in explain/telemetry.
|
|
1077
|
+
- **Degraded / fail-open path** — with Granite ONNX missing or slow, confirm neural failover still fails open (#119 / #148) rather than crashing the host agent.
|
|
1078
|
+
- **Feed #96** — after dogfood evidence, comment on #96 with *promote Granite as default?* yes/no + evidence. Only then open a separate issue/PR to flip defaults — never from dogfood alone (see `docs/qa/shadow-dogfood-protocol.md`).
|
|
1079
|
+
- **Do not conflate with ModernBERT** — K=4 stays blocked on trained `config/modernbert-k4-heads.json` (SP-218/219); out of scope here.
|
|
1080
|
+
|
|
1003
1081
|
#### Supply-chain: artifact pins, offline cache, and audit posture
|
|
1004
1082
|
|
|
1005
1083
|
**Digest pinning (SP-259, [#147](https://github.com/beettlle/pi-smart-router/issues/147)).** The embedder verifies cached ONNX artifacts against SHA-256 pins before they are used. Pins live in [`config/onnx-artifact-pins.json`](config/onnx-artifact-pins.json) (`pins[modelId][cacheRelativePath] = sha256`; digests are the HuggingFace LFS oids for the default quantized artifacts). Pin mode is controlled by `SMART_ROUTER_ONNX_PIN_MODE`:
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"livecodebench": "https://livecodebench.github.io/leaderboard.html",
|
|
8
8
|
"bfcl": "https://gorilla.cs.berkeley.edu/leaderboard.html"
|
|
9
9
|
},
|
|
10
|
-
"scrape_date": "2026-09-
|
|
11
|
-
"catalog_freeze_date": "2026-09-
|
|
10
|
+
"scrape_date": "2026-09-13",
|
|
11
|
+
"catalog_freeze_date": "2026-09-13"
|
|
12
12
|
},
|
|
13
13
|
"aliases": {
|
|
14
14
|
"anthropic/claude-opus-4": "claude-opus-4-5",
|
|
@@ -21,6 +21,13 @@
|
|
|
21
21
|
"hydra": {
|
|
22
22
|
"artifact_cache_path": ".pi-smart-router/models/",
|
|
23
23
|
"encoder": "minilm",
|
|
24
|
+
"_encoder_documentation": "Encoder switch — minilm (shipped default) | granite (opt-in trial, #80/#167). Granite: ibm-granite/granite-embedding-97m-multilingual-r2 ONNX, 384-dim long-context drop-in; fetches on first use into artifact_cache_path. See README 'HyDRA model cache → Granite opt-in dogfood runbook (#167)' for fetch/switch/measure/follow-ups. Do NOT flip the shipped default here — promotion is decided only via #96.",
|
|
25
|
+
"encoder_cascade": {
|
|
26
|
+
"enabled": false,
|
|
27
|
+
"long_context_encoder": "granite",
|
|
28
|
+
"token_threshold": 512
|
|
29
|
+
},
|
|
30
|
+
"_encoder_cascade_documentation": "SP-291 (#173) — opt-in per-prompt encoder cascade. Default OFF: every prompt uses hydra.encoder (MiniLM) and existing installs are unaffected. When enabled, prompts whose estimated token count reaches token_threshold are embedded with long_context_encoder (Granite 97M, long-context) instead of MiniLM (512-token window), avoiding truncation on long prompts. The gate is pure pre-embedding arithmetic (~zero overhead) and reuses the turn-envelope token estimator (estimated_input_tokens ?? prompt_text.length). Hard constraint: encoders embed into different vector spaces — never mix per-encoder artifacts (routing centroids, learned projection); per-encoder artifact sets and the cascading embedder land with SP-292.",
|
|
24
31
|
"hydra_heads": "learned_projection",
|
|
25
32
|
"_onnx_artifact_pins_documentation": "SP-259 (#147) — ONNX artifact supply-chain pinning. Digests live in config/onnx-artifact-pins.json (SHA-256 per cached artifact). Enable with env SMART_ROUTER_ONNX_PIN_MODE=verify (verify configured pins; unpinned models still download) or =enforce (CI/prod: pins required for the loaded model — missing pin file, missing pins, missing cached artifact, or any digest mismatch fails closed). Default off preserves first-run unpinned dogfood downloads. Override pin file path with SMART_ROUTER_ONNX_PIN_FILE. See README 'HyDRA model cache → Supply-chain' for offline cache warm and audit posture.",
|
|
26
33
|
"_hydra_heads_documentation": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAYL,KAAK,cAAc,EACpB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EACL,iCAAiC,EACjC,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,kCAAkC,EAClC,4BAA4B,EAC5B,qCAAqC,EACrC,oCAAoC,EACpC,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AAEpC,4GAA4G;AAC5G,wBAAgB,4BAA4B,CAC1C,IAAI,GAAE,cAAwC,GAC7C,cAAc,CAOhB;AAED,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,cAAc,CAyCnD,CAAC"}
|
package/dist/config/defaults.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Operator configuration defaults (FR-021).
|
|
3
3
|
* Values sourced from specs/001-build-smart-router/data-model.md § Configuration (Operator).
|
|
4
4
|
*/
|
|
5
|
-
import { DEFAULT_ADAPTIVE_REASONING_CONFIG, DEFAULT_DEGRADED_ROUTE_CONFIG, DEFAULT_LOCAL_ZERO_CONFIG, DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, DEFAULT_SPECULATIVE_PREWARM_CONFIG, DEFAULT_WORKLOAD_HEAT_CONFIG, resolveAdaptiveReasoningConfigFromEnv, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv, } from '../domain/types/schemas.js';
|
|
5
|
+
import { DEFAULT_ADAPTIVE_REASONING_CONFIG, DEFAULT_DEGRADED_ROUTE_CONFIG, DEFAULT_ENCODER_CASCADE_CONFIG, DEFAULT_LOCAL_ZERO_CONFIG, DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, DEFAULT_SPECULATIVE_PREWARM_CONFIG, DEFAULT_WORKLOAD_HEAT_CONFIG, resolveAdaptiveReasoningConfigFromEnv, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv, } from '../domain/types/schemas.js';
|
|
6
6
|
import { DEFAULT_LOW_INTENSITY_WEIGHTS } from '../domain/routing/tier-features.js';
|
|
7
7
|
export { DEFAULT_ADAPTIVE_REASONING_CONFIG, DEFAULT_LOCAL_ZERO_CONFIG, DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, DEFAULT_SPECULATIVE_PREWARM_CONFIG, DEFAULT_WORKLOAD_HEAT_CONFIG, resolveAdaptiveReasoningConfigFromEnv, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv, } from '../domain/types/schemas.js';
|
|
8
8
|
/** Merge operator env overrides onto defaults (adaptive reasoning, SAAR and planning delegate sections). */
|
|
@@ -34,6 +34,8 @@ export const DEFAULT_OPERATOR_CONFIG = {
|
|
|
34
34
|
hydra: {
|
|
35
35
|
artifact_cache_path: '.pi-smart-router/models/',
|
|
36
36
|
encoder: 'minilm',
|
|
37
|
+
/** Opt-in encoder cascade (SP-291, #173): default off. */
|
|
38
|
+
encoder_cascade: DEFAULT_ENCODER_CASCADE_CONFIG,
|
|
37
39
|
hydra_heads: 'learned_projection',
|
|
38
40
|
},
|
|
39
41
|
low_intensity: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,iCAAiC,EACjC,6BAA6B,EAC7B,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,kCAAkC,EAClC,4BAA4B,EAC5B,qCAAqC,EACrC,oCAAoC,EACpC,wBAAwB,GAEzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAEnF,OAAO,EACL,iCAAiC,EACjC,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,kCAAkC,EAClC,4BAA4B,EAC5B,qCAAqC,EACrC,oCAAoC,EACpC,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AAEpC,4GAA4G;AAC5G,MAAM,UAAU,4BAA4B,CAC1C,OAAuB,uBAAuB;IAE9C,OAAO;QACL,GAAG,IAAI;QACP,kBAAkB,EAAE,qCAAqC,CAAC,IAAI,CAAC,kBAAkB,CAAC;QAClF,IAAI,EAAE,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC;QACzC,iBAAiB,EAAE,oCAAoC,CAAC,IAAI,CAAC,iBAAiB,CAAC;KAChF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAA6B;IAC/D,SAAS,EAAE;QACT,WAAW,EAAE,GAAG;QAChB,cAAc,EAAE,GAAG;QACnB,gBAAgB,EAAE,IAAI;KACvB;IACD,eAAe,EAAE;QACf,SAAS,EAAE,CAAC;KACb;IACD,OAAO,EAAE;QACP,cAAc,EAAE,EAAE;KACnB;IACD,KAAK,EAAE;QACL,kBAAkB,EAAE,EAAE;QACtB,4BAA4B,EAAE,CAAC;QAC/B,qBAAqB,EAAE,EAAE;KAC1B;IACD,KAAK,EAAE;QACL,mBAAmB,EAAE,0BAA0B;QAC/C,OAAO,EAAE,QAAQ;QACjB,WAAW,EAAE,oBAAoB;KAClC;IACD,aAAa,EAAE;QACb,OAAO,EAAE,6BAA6B;QACtC,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,eAAe,EAAE,GAAG;KACrB;IACD,IAAI,EAAE,mBAAmB;IACzB,iBAAiB,EAAE,gCAAgC;IACnD,UAAU,EAAE,yBAAyB;IACrC,cAAc,EAAE,6BAA6B;IAC7C,8EAA8E;IAC9E,aAAa,EAAE,4BAA4B;IAC3C,mFAAmF;IACnF,mBAAmB,EAAE,kCAAkC;IACvD,8EAA8E;IAC9E,kBAAkB,EAAE,iCAAiC;IACrD,iBAAiB,EAAE,KAAK;CAChB,CAAC"}
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,iCAAiC,EACjC,6BAA6B,EAC7B,8BAA8B,EAC9B,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,kCAAkC,EAClC,4BAA4B,EAC5B,qCAAqC,EACrC,oCAAoC,EACpC,wBAAwB,GAEzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAEnF,OAAO,EACL,iCAAiC,EACjC,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,kCAAkC,EAClC,4BAA4B,EAC5B,qCAAqC,EACrC,oCAAoC,EACpC,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AAEpC,4GAA4G;AAC5G,MAAM,UAAU,4BAA4B,CAC1C,OAAuB,uBAAuB;IAE9C,OAAO;QACL,GAAG,IAAI;QACP,kBAAkB,EAAE,qCAAqC,CAAC,IAAI,CAAC,kBAAkB,CAAC;QAClF,IAAI,EAAE,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC;QACzC,iBAAiB,EAAE,oCAAoC,CAAC,IAAI,CAAC,iBAAiB,CAAC;KAChF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAA6B;IAC/D,SAAS,EAAE;QACT,WAAW,EAAE,GAAG;QAChB,cAAc,EAAE,GAAG;QACnB,gBAAgB,EAAE,IAAI;KACvB;IACD,eAAe,EAAE;QACf,SAAS,EAAE,CAAC;KACb;IACD,OAAO,EAAE;QACP,cAAc,EAAE,EAAE;KACnB;IACD,KAAK,EAAE;QACL,kBAAkB,EAAE,EAAE;QACtB,4BAA4B,EAAE,CAAC;QAC/B,qBAAqB,EAAE,EAAE;KAC1B;IACD,KAAK,EAAE;QACL,mBAAmB,EAAE,0BAA0B;QAC/C,OAAO,EAAE,QAAQ;QACjB,0DAA0D;QAC1D,eAAe,EAAE,8BAA8B;QAC/C,WAAW,EAAE,oBAAoB;KAClC;IACD,aAAa,EAAE;QACb,OAAO,EAAE,6BAA6B;QACtC,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,eAAe,EAAE,GAAG;KACrB;IACD,IAAI,EAAE,mBAAmB;IACzB,iBAAiB,EAAE,gCAAgC;IACnD,UAAU,EAAE,yBAAyB;IACrC,cAAc,EAAE,6BAA6B;IAC7C,8EAA8E;IAC9E,aAAa,EAAE,4BAA4B;IAC3C,mFAAmF;IACnF,mBAAmB,EAAE,kCAAkC;IACvD,8EAA8E;IAC9E,kBAAkB,EAAE,iCAAiC;IACrD,iBAAiB,EAAE,KAAK;CAChB,CAAC"}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* matching. One ONNX session per instance; share across matchers via a single
|
|
6
6
|
* factory call and coordinated dispose().
|
|
7
7
|
*/
|
|
8
|
-
import type { Encoder } from '../types/schemas.js';
|
|
8
|
+
import type { Encoder, EncoderCascadeConfig } from '../types/schemas.js';
|
|
9
|
+
import { type EncoderGateReasonCode } from './encoder-gate.js';
|
|
9
10
|
export declare const EMBEDDING_DIM = 384;
|
|
10
11
|
/** MiniLM ONNX model (384-dim, 512-token context). */
|
|
11
12
|
export declare const MINILM_ONNX_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
@@ -70,4 +71,65 @@ export declare function createOnnxTextEmbedder(artifactCachePath: string, pinOpt
|
|
|
70
71
|
export declare function createGraniteOnnxTextEmbedder(artifactCachePath: string, pinOptions?: OnnxPinOptions): Promise<TextEmbedder>;
|
|
71
72
|
/** Select ONNX text embedder by operator encoder flag. */
|
|
72
73
|
export declare function createTextEmbedder(encoder: Encoder | undefined, artifactCachePath: string, pinOptions?: OnnxPinOptions): Promise<TextEmbedder>;
|
|
74
|
+
/**
|
|
75
|
+
* Decision telemetry for the most recent cascade embed. Mirrors the routing
|
|
76
|
+
* feature-sidecar fields from #173 so the pipeline can copy them onto live
|
|
77
|
+
* decisions without reshaping (SP-292).
|
|
78
|
+
*/
|
|
79
|
+
export interface CascadeEmbedderTelemetry {
|
|
80
|
+
/** Encoder that produced the embedding (never mixed across spaces). */
|
|
81
|
+
readonly encoder_selected: Encoder;
|
|
82
|
+
/** Gate token estimate for the embedded prompt. */
|
|
83
|
+
readonly token_estimate: number;
|
|
84
|
+
/** Configured cascade threshold in effect for the decision. */
|
|
85
|
+
readonly cascade_threshold: number;
|
|
86
|
+
/** SP-291 gate reason code (`under_threshold` / `over_threshold` / `granite_fallback`). */
|
|
87
|
+
readonly reason_code: EncoderGateReasonCode;
|
|
88
|
+
/**
|
|
89
|
+
* Present when an over-threshold prompt degraded to the primary encoder
|
|
90
|
+
* because the long-context session was unavailable (degrade, never mix).
|
|
91
|
+
*/
|
|
92
|
+
readonly cascade_fallback_reason: EncoderGateReasonCode | null;
|
|
93
|
+
}
|
|
94
|
+
export interface CascadingTextEmbedder extends TextEmbedder {
|
|
95
|
+
/**
|
|
96
|
+
* Telemetry from the most recently completed embed(); null before the
|
|
97
|
+
* first embed. Concurrent embeds race on this snapshot — callers on the
|
|
98
|
+
* routing hot path embed sequentially per request.
|
|
99
|
+
*/
|
|
100
|
+
lastTelemetry(): CascadeEmbedderTelemetry | null;
|
|
101
|
+
}
|
|
102
|
+
/** Type guard: does this embedder carry cascade decision telemetry? */
|
|
103
|
+
export declare function isCascadingTextEmbedder(embedder: TextEmbedder): embedder is CascadingTextEmbedder;
|
|
104
|
+
export interface CreateCascadingTextEmbedderOptions {
|
|
105
|
+
readonly pinOptions?: OnnxPinOptions;
|
|
106
|
+
/**
|
|
107
|
+
* Primary encoder for under-threshold / fallback embeds.
|
|
108
|
+
* Default: DEFAULT_ENCODER ('minilm'), matching `hydra.encoder` defaults.
|
|
109
|
+
*/
|
|
110
|
+
readonly primaryEncoder?: Encoder;
|
|
111
|
+
/**
|
|
112
|
+
* Session factory override for tests. Default: createTextEmbedder bound to
|
|
113
|
+
* the cascade artifact cache path and pin options.
|
|
114
|
+
*/
|
|
115
|
+
readonly sessionFactory?: (encoder: Encoder) => Promise<TextEmbedder>;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Lazy dual-session cascading embedder (SP-292, #173 part 2).
|
|
119
|
+
*
|
|
120
|
+
* Owns at most one ONNX session per encoder: the primary session loads on the
|
|
121
|
+
* first embed, the long-context (Granite) session loads only on the first
|
|
122
|
+
* over-threshold prompt — memory is paid only when the path fires. Selection
|
|
123
|
+
* is the SP-291 pure gate; token estimates default to prompt length (the
|
|
124
|
+
* turn-envelope-parity estimator).
|
|
125
|
+
*
|
|
126
|
+
* Failure behavior: degrade, never mix. If the long-context session fails to
|
|
127
|
+
* load or embed, the request is served by the primary encoder with reason
|
|
128
|
+
* code `granite_fallback` and the long-context encoder is marked unavailable
|
|
129
|
+
* for subsequent prompts. Never silently compares cross-encoder vectors.
|
|
130
|
+
*
|
|
131
|
+
* dispose() closes every session that was actually loaded (idempotent);
|
|
132
|
+
* embed() after dispose fails closed (SP-260 precedent).
|
|
133
|
+
*/
|
|
134
|
+
export declare function createCascadingTextEmbedder(config: EncoderCascadeConfig, artifactCachePath: string, options?: CreateCascadingTextEmbedderOptions): CascadingTextEmbedder;
|
|
73
135
|
//# sourceMappingURL=embedding-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedding-provider.d.ts","sourceRoot":"","sources":["../../../src/domain/matching/embedding-provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"embedding-provider.d.ts","sourceRoot":"","sources":["../../../src/domain/matching/embedding-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@ import { createHash } from 'node:crypto';
|
|
|
9
9
|
import { promises as fs } from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { DEFAULT_ENCODER } from '../types/schemas.js';
|
|
12
|
+
import { selectEncoderForPrompt, } from './encoder-gate.js';
|
|
12
13
|
export const EMBEDDING_DIM = 384;
|
|
13
14
|
/** MiniLM ONNX model (384-dim, 512-token context). */
|
|
14
15
|
export const MINILM_ONNX_MODEL = 'Xenova/all-MiniLM-L6-v2';
|
|
@@ -222,4 +223,120 @@ export async function createTextEmbedder(encoder = DEFAULT_ENCODER, artifactCach
|
|
|
222
223
|
}
|
|
223
224
|
}
|
|
224
225
|
}
|
|
226
|
+
/** Type guard: does this embedder carry cascade decision telemetry? */
|
|
227
|
+
export function isCascadingTextEmbedder(embedder) {
|
|
228
|
+
return (typeof embedder.lastTelemetry ===
|
|
229
|
+
'function');
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Lazy dual-session cascading embedder (SP-292, #173 part 2).
|
|
233
|
+
*
|
|
234
|
+
* Owns at most one ONNX session per encoder: the primary session loads on the
|
|
235
|
+
* first embed, the long-context (Granite) session loads only on the first
|
|
236
|
+
* over-threshold prompt — memory is paid only when the path fires. Selection
|
|
237
|
+
* is the SP-291 pure gate; token estimates default to prompt length (the
|
|
238
|
+
* turn-envelope-parity estimator).
|
|
239
|
+
*
|
|
240
|
+
* Failure behavior: degrade, never mix. If the long-context session fails to
|
|
241
|
+
* load or embed, the request is served by the primary encoder with reason
|
|
242
|
+
* code `granite_fallback` and the long-context encoder is marked unavailable
|
|
243
|
+
* for subsequent prompts. Never silently compares cross-encoder vectors.
|
|
244
|
+
*
|
|
245
|
+
* dispose() closes every session that was actually loaded (idempotent);
|
|
246
|
+
* embed() after dispose fails closed (SP-260 precedent).
|
|
247
|
+
*/
|
|
248
|
+
export function createCascadingTextEmbedder(config, artifactCachePath, options) {
|
|
249
|
+
const primaryEncoder = options?.primaryEncoder ?? DEFAULT_ENCODER;
|
|
250
|
+
const longContextEncoder = config.long_context_encoder;
|
|
251
|
+
const factory = options?.sessionFactory ??
|
|
252
|
+
((encoder) => createTextEmbedder(encoder, artifactCachePath, options?.pinOptions));
|
|
253
|
+
const sessions = new Map();
|
|
254
|
+
let longContextAvailable = true;
|
|
255
|
+
let disposed = false;
|
|
256
|
+
let last = null;
|
|
257
|
+
function sessionFor(encoder) {
|
|
258
|
+
let pending = sessions.get(encoder);
|
|
259
|
+
if (pending === undefined) {
|
|
260
|
+
pending = factory(encoder);
|
|
261
|
+
sessions.set(encoder, pending);
|
|
262
|
+
// A rejected session promise must not be cached forever — drop it so a
|
|
263
|
+
// later retry observes a fresh load attempt.
|
|
264
|
+
pending.catch(() => sessions.delete(encoder));
|
|
265
|
+
}
|
|
266
|
+
return pending;
|
|
267
|
+
}
|
|
268
|
+
function telemetry(decision, fallbackReason) {
|
|
269
|
+
return {
|
|
270
|
+
encoder_selected: decision.encoder,
|
|
271
|
+
token_estimate: decision.token_estimate,
|
|
272
|
+
cascade_threshold: config.token_threshold,
|
|
273
|
+
reason_code: fallbackReason ?? decision.reason_code,
|
|
274
|
+
cascade_fallback_reason: fallbackReason,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
async embed(text) {
|
|
279
|
+
if (disposed) {
|
|
280
|
+
throw new Error('CascadingTextEmbedder has been disposed; embed() fails closed. ' +
|
|
281
|
+
'Create a new embedder via createCascadingTextEmbedder to continue.');
|
|
282
|
+
}
|
|
283
|
+
const decision = selectEncoderForPrompt(text, config, {
|
|
284
|
+
primaryEncoder,
|
|
285
|
+
longContextAvailable,
|
|
286
|
+
});
|
|
287
|
+
if (decision.encoder === longContextEncoder && longContextAvailable) {
|
|
288
|
+
try {
|
|
289
|
+
const session = await sessionFor(longContextEncoder);
|
|
290
|
+
const vector = await session.embed(text);
|
|
291
|
+
last = telemetry(decision, null);
|
|
292
|
+
return vector;
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
// Degrade, never mix (#173): Granite session/artifacts unavailable →
|
|
296
|
+
// serve with the primary encoder and make the fallback explicit.
|
|
297
|
+
longContextAvailable = false;
|
|
298
|
+
sessions.delete(longContextEncoder);
|
|
299
|
+
console.warn('Encoder cascade: long-context encoder unavailable; degrading to primary encoder (granite_fallback)', {
|
|
300
|
+
long_context_encoder: longContextEncoder,
|
|
301
|
+
primary_encoder: primaryEncoder,
|
|
302
|
+
token_estimate: decision.token_estimate,
|
|
303
|
+
error: error instanceof Error ? error.message : String(error),
|
|
304
|
+
});
|
|
305
|
+
const session = await sessionFor(primaryEncoder);
|
|
306
|
+
const vector = await session.embed(text);
|
|
307
|
+
last = telemetry({ ...decision, encoder: primaryEncoder }, 'granite_fallback');
|
|
308
|
+
return vector;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const session = await sessionFor(primaryEncoder);
|
|
312
|
+
const vector = await session.embed(text);
|
|
313
|
+
last = telemetry({ ...decision, encoder: primaryEncoder }, decision.reason_code === 'granite_fallback' ? 'granite_fallback' : null);
|
|
314
|
+
return vector;
|
|
315
|
+
},
|
|
316
|
+
lastTelemetry() {
|
|
317
|
+
return last;
|
|
318
|
+
},
|
|
319
|
+
async dispose() {
|
|
320
|
+
if (disposed)
|
|
321
|
+
return; // idempotent: safe for shared-factory callers
|
|
322
|
+
disposed = true;
|
|
323
|
+
const pending = [...sessions.values()];
|
|
324
|
+
sessions.clear();
|
|
325
|
+
const failures = [];
|
|
326
|
+
for (const sessionPromise of pending) {
|
|
327
|
+
try {
|
|
328
|
+
const session = await sessionPromise;
|
|
329
|
+
await session.dispose();
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
failures.push(error instanceof Error ? error.message : String(error));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Fail loud: partially released cascade sessions must not pass silently.
|
|
336
|
+
if (failures.length > 0) {
|
|
337
|
+
throw new Error(`CascadingTextEmbedder dispose failed for ${failures.length} session(s): ${failures.join('; ')}`);
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
225
342
|
//# sourceMappingURL=embedding-provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedding-provider.js","sourceRoot":"","sources":["../../../src/domain/matching/embedding-provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"embedding-provider.js","sourceRoot":"","sources":["../../../src/domain/matching/embedding-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encoder cascade gate (SP-291, #173 part 1) — pure domain, unit-testable.
|
|
3
|
+
*
|
|
4
|
+
* Opt-in per-prompt encoder selection: when `hydra.encoder_cascade.enabled`
|
|
5
|
+
* is true, prompts whose estimated token count reaches `token_threshold` are
|
|
6
|
+
* routed to the long-context encoder (Granite 97M, SP-156) instead of the
|
|
7
|
+
* primary `hydra.encoder` (MiniLM, 512-token window). Default off: existing
|
|
8
|
+
* single-encoder installs are unaffected and pay zero added cost — the gate
|
|
9
|
+
* is pre-embedding arithmetic only.
|
|
10
|
+
*
|
|
11
|
+
* Token estimate parity: this module reuses the same estimator as the
|
|
12
|
+
* turn-envelope stage (`src/domain/pipeline/turn-envelope-stage.ts`):
|
|
13
|
+
* `estimated_input_tokens ?? prompt_text.length`. Callers pass
|
|
14
|
+
* `RoutingRequest.estimated_input_tokens` via options when present; the
|
|
15
|
+
* fallback is the prompt's character length.
|
|
16
|
+
*
|
|
17
|
+
* Hard constraint (#173): encoders embed into different vector spaces — the
|
|
18
|
+
* gate only SELECTS an encoder; per-encoder artifacts (centroids, learned
|
|
19
|
+
* projection) and the cascading embedder land in SP-292. Never mix spaces.
|
|
20
|
+
*/
|
|
21
|
+
import type { Encoder, EncoderCascadeConfig } from '../types/schemas.js';
|
|
22
|
+
/** Gate outcomes per #173; attached to decision telemetry. */
|
|
23
|
+
export type EncoderGateReasonCode =
|
|
24
|
+
/** Cascade disabled — every prompt uses the primary encoder. */
|
|
25
|
+
'cascade_disabled'
|
|
26
|
+
/** Estimate below the configured threshold — primary encoder. */
|
|
27
|
+
| 'under_threshold'
|
|
28
|
+
/** Estimate at/over the threshold — long-context encoder selected. */
|
|
29
|
+
| 'over_threshold'
|
|
30
|
+
/**
|
|
31
|
+
* Over threshold but the long-context encoder is unavailable — degrade to
|
|
32
|
+
* the primary encoder (continuity of routing wins; never mix vector spaces).
|
|
33
|
+
*/
|
|
34
|
+
| 'granite_fallback';
|
|
35
|
+
export interface EncoderGateDecision {
|
|
36
|
+
readonly encoder: Encoder;
|
|
37
|
+
readonly reason_code: EncoderGateReasonCode;
|
|
38
|
+
readonly token_estimate: number;
|
|
39
|
+
}
|
|
40
|
+
export interface EncoderGateOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Precomputed token estimate — pass `RoutingRequest.estimated_input_tokens`
|
|
43
|
+
* when set. Falls back to `prompt.length`, exactly mirroring the
|
|
44
|
+
* turn-envelope stage estimator (`estimated_input_tokens ?? prompt_text.length`).
|
|
45
|
+
*/
|
|
46
|
+
readonly estimatedTokens?: number | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Primary encoder used when the cascade is disabled or does not fire.
|
|
49
|
+
* Default: DEFAULT_ENCODER ('minilm'), matching `hydra.encoder` defaults.
|
|
50
|
+
*/
|
|
51
|
+
readonly primaryEncoder?: Encoder | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Long-context encoder availability signal (SP-292 embedder layer owns
|
|
54
|
+
* session/artifact health). When explicitly false, an over-threshold prompt
|
|
55
|
+
* degrades to the primary encoder with reason `granite_fallback`.
|
|
56
|
+
* Default: assumed available.
|
|
57
|
+
*/
|
|
58
|
+
readonly longContextAvailable?: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Turn-envelope-parity token estimator: a caller-supplied estimate wins;
|
|
62
|
+
* otherwise the prompt's character length is the proxy (same formula as
|
|
63
|
+
* `turn-envelope-stage.ts` step 2b breakeven arithmetic).
|
|
64
|
+
*/
|
|
65
|
+
export declare function estimatePromptTokens(prompt: string, estimatedTokens?: number): number;
|
|
66
|
+
/**
|
|
67
|
+
* Select the encoder for a prompt. Pure: no I/O, no ONNX sessions.
|
|
68
|
+
*
|
|
69
|
+
* Boundary semantics (per #173 eval table): `token_estimate >= token_threshold`
|
|
70
|
+
* routes to the long-context encoder; strictly below stays on the primary.
|
|
71
|
+
*/
|
|
72
|
+
export declare function selectEncoderForPrompt(prompt: string, config: EncoderCascadeConfig, options?: EncoderGateOptions): EncoderGateDecision;
|
|
73
|
+
//# sourceMappingURL=encoder-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encoder-gate.d.ts","sourceRoot":"","sources":["../../../src/domain/matching/encoder-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGzE,8DAA8D;AAC9D,MAAM,MAAM,qBAAqB;AAC/B,gEAAgE;AAC9D,kBAAkB;AACpB,iEAAiE;GAC/D,iBAAiB;AACnB,sEAAsE;GACpE,gBAAgB;AAClB;;;GAGG;GACD,kBAAkB,CAAC;AAEvB,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9C;;;;;OAKG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CACzC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,eAAe,CAAC,EAAE,MAAM,GACvB,MAAM,CAER;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,oBAAoB,EAC5B,OAAO,CAAC,EAAE,kBAAkB,GAC3B,mBAAmB,CAiCrB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encoder cascade gate (SP-291, #173 part 1) — pure domain, unit-testable.
|
|
3
|
+
*
|
|
4
|
+
* Opt-in per-prompt encoder selection: when `hydra.encoder_cascade.enabled`
|
|
5
|
+
* is true, prompts whose estimated token count reaches `token_threshold` are
|
|
6
|
+
* routed to the long-context encoder (Granite 97M, SP-156) instead of the
|
|
7
|
+
* primary `hydra.encoder` (MiniLM, 512-token window). Default off: existing
|
|
8
|
+
* single-encoder installs are unaffected and pay zero added cost — the gate
|
|
9
|
+
* is pre-embedding arithmetic only.
|
|
10
|
+
*
|
|
11
|
+
* Token estimate parity: this module reuses the same estimator as the
|
|
12
|
+
* turn-envelope stage (`src/domain/pipeline/turn-envelope-stage.ts`):
|
|
13
|
+
* `estimated_input_tokens ?? prompt_text.length`. Callers pass
|
|
14
|
+
* `RoutingRequest.estimated_input_tokens` via options when present; the
|
|
15
|
+
* fallback is the prompt's character length.
|
|
16
|
+
*
|
|
17
|
+
* Hard constraint (#173): encoders embed into different vector spaces — the
|
|
18
|
+
* gate only SELECTS an encoder; per-encoder artifacts (centroids, learned
|
|
19
|
+
* projection) and the cascading embedder land in SP-292. Never mix spaces.
|
|
20
|
+
*/
|
|
21
|
+
import { DEFAULT_ENCODER } from '../types/schemas.js';
|
|
22
|
+
/**
|
|
23
|
+
* Turn-envelope-parity token estimator: a caller-supplied estimate wins;
|
|
24
|
+
* otherwise the prompt's character length is the proxy (same formula as
|
|
25
|
+
* `turn-envelope-stage.ts` step 2b breakeven arithmetic).
|
|
26
|
+
*/
|
|
27
|
+
export function estimatePromptTokens(prompt, estimatedTokens) {
|
|
28
|
+
return estimatedTokens ?? prompt.length;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Select the encoder for a prompt. Pure: no I/O, no ONNX sessions.
|
|
32
|
+
*
|
|
33
|
+
* Boundary semantics (per #173 eval table): `token_estimate >= token_threshold`
|
|
34
|
+
* routes to the long-context encoder; strictly below stays on the primary.
|
|
35
|
+
*/
|
|
36
|
+
export function selectEncoderForPrompt(prompt, config, options) {
|
|
37
|
+
const primary = options?.primaryEncoder ?? DEFAULT_ENCODER;
|
|
38
|
+
const tokenEstimate = estimatePromptTokens(prompt, options?.estimatedTokens);
|
|
39
|
+
if (!config.enabled) {
|
|
40
|
+
return {
|
|
41
|
+
encoder: primary,
|
|
42
|
+
reason_code: 'cascade_disabled',
|
|
43
|
+
token_estimate: tokenEstimate,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (tokenEstimate < config.token_threshold) {
|
|
47
|
+
return {
|
|
48
|
+
encoder: primary,
|
|
49
|
+
reason_code: 'under_threshold',
|
|
50
|
+
token_estimate: tokenEstimate,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (options?.longContextAvailable === false) {
|
|
54
|
+
return {
|
|
55
|
+
encoder: primary,
|
|
56
|
+
reason_code: 'granite_fallback',
|
|
57
|
+
token_estimate: tokenEstimate,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
encoder: config.long_context_encoder,
|
|
62
|
+
reason_code: 'over_threshold',
|
|
63
|
+
token_estimate: tokenEstimate,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=encoder-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encoder-gate.js","sourceRoot":"","sources":["../../../src/domain/matching/encoder-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AA2CtD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAc,EACd,eAAwB;IAExB,OAAO,eAAe,IAAI,MAAM,CAAC,MAAM,CAAC;AAC1C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAc,EACd,MAA4B,EAC5B,OAA4B;IAE5B,MAAM,OAAO,GAAG,OAAO,EAAE,cAAc,IAAI,eAAe,CAAC;IAC3D,MAAM,aAAa,GAAG,oBAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC;IAE7E,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO;YACL,OAAO,EAAE,OAAO;YAChB,WAAW,EAAE,kBAAkB;YAC/B,cAAc,EAAE,aAAa;SAC9B,CAAC;IACJ,CAAC;IAED,IAAI,aAAa,GAAG,MAAM,CAAC,eAAe,EAAE,CAAC;QAC3C,OAAO;YACL,OAAO,EAAE,OAAO;YAChB,WAAW,EAAE,iBAAiB;YAC9B,cAAc,EAAE,aAAa;SAC9B,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,EAAE,oBAAoB,KAAK,KAAK,EAAE,CAAC;QAC5C,OAAO;YACL,OAAO,EAAE,OAAO;YAChB,WAAW,EAAE,kBAAkB;YAC/B,cAAc,EAAE,aAAa;SAC9B,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,oBAAoB;QACpC,WAAW,EAAE,gBAAgB;QAC7B,cAAc,EAAE,aAAa;KAC9B,CAAC;AACJ,CAAC"}
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* Budget: 80–120 ms (configurable, default 100 ms).
|
|
12
12
|
*/
|
|
13
13
|
import { z } from 'zod';
|
|
14
|
-
import { EMBEDDING_DIM, type TextEmbedder } from './embedding-provider.js';
|
|
14
|
+
import { EMBEDDING_DIM, type CascadeEmbedderTelemetry, type TextEmbedder } from './embedding-provider.js';
|
|
15
15
|
import { type K4CapabilityVector, type ModernBertHeadsPredictor } from './modernbert-heads.js';
|
|
16
|
-
import type { Encoder, HydraConfig, HydraHeads } from '../types/schemas.js';
|
|
16
|
+
import type { Encoder, EncoderCascadeConfig, HydraConfig, HydraHeads } from '../types/schemas.js';
|
|
17
17
|
import { type FrugalityWeights } from '../scoring/multi-objective.js';
|
|
18
18
|
import type { CandidateScore, ModelProfile, RoutingRequest } from '../types/index.js';
|
|
19
19
|
export interface RequirementVector {
|
|
@@ -39,6 +39,12 @@ export interface EmbeddingProvider {
|
|
|
39
39
|
* Absent/empty when learned weights are active.
|
|
40
40
|
*/
|
|
41
41
|
requirementReasonCodes?(): readonly string[];
|
|
42
|
+
/**
|
|
43
|
+
* Cascade decision telemetry from the most recent extraction (SP-292, #173).
|
|
44
|
+
* Present only on cascading-embedder-backed providers; null before the first
|
|
45
|
+
* embed. Absent on single-encoder and ModernBERT K=4 providers.
|
|
46
|
+
*/
|
|
47
|
+
cascadeTelemetry?(): CascadeEmbedderTelemetry | null;
|
|
42
48
|
dispose(): Promise<void>;
|
|
43
49
|
}
|
|
44
50
|
export interface MatchResult {
|
|
@@ -61,6 +67,13 @@ export interface MatchResult {
|
|
|
61
67
|
* always populates it.
|
|
62
68
|
*/
|
|
63
69
|
readonly requirement_reason_codes?: readonly string[];
|
|
70
|
+
/**
|
|
71
|
+
* Encoder cascade decision telemetry for this match (SP-292, #173):
|
|
72
|
+
* `encoder_selected`, `token_estimate`, `cascade_threshold`, and the
|
|
73
|
+
* fallback reason when an over-threshold prompt degraded to the primary
|
|
74
|
+
* encoder. Absent on legacy call paths and non-cascade providers.
|
|
75
|
+
*/
|
|
76
|
+
readonly cascade_telemetry?: CascadeEmbedderTelemetry | null;
|
|
64
77
|
readonly elapsedMs: number;
|
|
65
78
|
readonly budgetExceeded: boolean;
|
|
66
79
|
}
|
|
@@ -183,6 +196,14 @@ export declare class HydraMatcher {
|
|
|
183
196
|
export interface CreateOnnxEmbeddingProviderOptions {
|
|
184
197
|
readonly encoder?: Encoder;
|
|
185
198
|
readonly projectionWeightsPath?: string;
|
|
199
|
+
/**
|
|
200
|
+
* Opt-in encoder cascade (SP-292, #173). When enabled, the provider is
|
|
201
|
+
* backed by a lazy dual-session cascading embedder that routes
|
|
202
|
+
* over-threshold prompts to the long-context encoder and degrades to the
|
|
203
|
+
* primary encoder with an explicit fallback reason when unavailable.
|
|
204
|
+
* Default: disabled (single-encoder path unchanged).
|
|
205
|
+
*/
|
|
206
|
+
readonly encoderCascade?: EncoderCascadeConfig;
|
|
186
207
|
}
|
|
187
208
|
export interface CreateHydraEmbeddingProviderOptions extends CreateOnnxEmbeddingProviderOptions {
|
|
188
209
|
readonly hydraHeads?: HydraHeads;
|
|
@@ -203,5 +224,5 @@ export declare function createHydraEmbeddingProvider(artifactCachePath: string,
|
|
|
203
224
|
/**
|
|
204
225
|
* Bootstrap HyDRA matcher from operator hydra config (encoder, heads, artifact path).
|
|
205
226
|
*/
|
|
206
|
-
export declare function createHydraMatcherFromHydraConfig(hydraConfig: Pick<HydraConfig, 'artifact_cache_path'> & Partial<Pick<HydraConfig, 'encoder' | 'hydra_heads'>>, options?: Omit<HydraMatcherConfig, 'artifactCachePath' | 'encoder' | 'hydraHeads'>): Promise<HydraMatcher>;
|
|
227
|
+
export declare function createHydraMatcherFromHydraConfig(hydraConfig: Pick<HydraConfig, 'artifact_cache_path'> & Partial<Pick<HydraConfig, 'encoder' | 'hydra_heads' | 'encoder_cascade'>>, options?: Omit<HydraMatcherConfig, 'artifactCachePath' | 'encoder' | 'hydraHeads'>): Promise<HydraMatcher>;
|
|
207
228
|
//# sourceMappingURL=hydra-matcher.d.ts.map
|