diff --git a/README.md b/README.md index 24a28b5655..72d30374fb 100644 --- a/README.md +++ b/README.md @@ -424,7 +424,7 @@ All **19** strategies — mix & match per combo step: 16 lkgp - Last-Known-Good Path — sticky to the last successful target + Last-Known-Good Path — pins to the last successful provider, then falls back to rules 17 diff --git a/changelog.d/maintenance/11555-lkgp-doc-provider-stickiness.md b/changelog.d/maintenance/11555-lkgp-doc-provider-stickiness.md new file mode 100644 index 0000000000..9c0c300554 --- /dev/null +++ b/changelog.d/maintenance/11555-lkgp-doc-provider-stickiness.md @@ -0,0 +1 @@ +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) diff --git a/docs/guides/USER_GUIDE.md b/docs/guides/USER_GUIDE.md index 9f5c79ee4a..516fd75dc2 100644 --- a/docs/guides/USER_GUIDE.md +++ b/docs/guides/USER_GUIDE.md @@ -827,7 +827,7 @@ Configure via **Dashboard → Settings → Routing**. The dashboard exposes the - `p2c` (Power of Two Choices) - `least-used` and `cost-optimized` - `auto` — score-driven across all candidates -- `lkgp` (Last Known Good Provider) — sticks to the last successful model per session +- `lkgp` (Last Known Good Provider) — pins to the last successful provider, then falls back to rules - `context-optimized` — picks the model with the largest free context window - `context-relay` — chains long-context models for follow-up turns @@ -1088,15 +1088,15 @@ Access via **Dashboard → Health**. Real-time system health overview with 6 car OmniRoute ships with a **score-driven auto-router** that picks the best model for each request across every connected provider — no combo to maintain. Just send the request with one of the `auto/*` prefixes and OmniRoute will assemble a virtual combo on the fly, scoring candidates on latency, cost, success rate, context fit, model fitness for the task, recent failures, quota, and circuit-breaker state. -| Prefix | Optimizes for | -| -------------- | ----------------------------------------------------------------------------- | -| `auto` | Balanced default (latency × cost × success rate) | -| `auto/coding` | Coding tasks: prefers Claude, GPT-5, GLM, Kimi, Qwen Coder, DeepSeek coders | -| `auto/cheap` | Lowest $/token, accepts higher latency | -| `auto/fast` | Lowest latency, ignores cost | -| `auto/offline` | Local-only providers (Ollama, vLLM, llama.cpp) — useful for air-gapped setups | -| `auto/smart` | Reasoning quality first (Opus, GPT-5 xhigh, R1, GLM 5.1 reasoning) | -| `auto/lkgp` | "Last Known Good Provider" — sticky to the most recently successful target | +| Prefix | Optimizes for | +| -------------- | ------------------------------------------------------------------------------------------- | +| `auto` | Balanced default (latency × cost × success rate) | +| `auto/coding` | Coding tasks: prefers Claude, GPT-5, GLM, Kimi, Qwen Coder, DeepSeek coders | +| `auto/cheap` | Lowest $/token, accepts higher latency | +| `auto/fast` | Lowest latency, ignores cost | +| `auto/offline` | Local-only providers (Ollama, vLLM, llama.cpp) — useful for air-gapped setups | +| `auto/smart` | Reasoning quality first (Opus, GPT-5 xhigh, R1, GLM 5.1 reasoning) | +| `auto/lkgp` | "Last Known Good Provider" — pins to the last successful provider, then falls back to rules | Example: diff --git a/docs/routing/AUTO-COMBO.md b/docs/routing/AUTO-COMBO.md index 6e3d598181..84d6a8e190 100644 --- a/docs/routing/AUTO-COMBO.md +++ b/docs/routing/AUTO-COMBO.md @@ -280,7 +280,7 @@ OmniRoute's combo engine supports **19 routing strategies** (declared in `src/sh | `headroom` | Pick the target with the most remaining quota headroom | | `strict-random` | Random without deduplication of repeats | | `auto` | Use Auto Combo scoring (15-factor) — **recommended** | -| `lkgp` | Last-Known-Good Path (sticky route to last successful target) | +| `lkgp` | Last-Known-Good Path (pins to the last successful provider, then falls back to rules) | | `context-optimized` | Pick target with best fit for current context size | | `cache-optimized` | Reorder targets by prompt-cache affinity — the connection likeliest to already hold this request's cached prefix is tried first (`open-sse/services/combo/promptCacheAffinity.ts`, #8008) | | `fusion` 🧬 | Fan out to a panel of models in parallel, then synthesize one answer via a judge (see below) | diff --git a/tests/unit/router-strategies.test.ts b/tests/unit/router-strategies.test.ts index 2a162c35ed..db88737ba6 100644 --- a/tests/unit/router-strategies.test.ts +++ b/tests/unit/router-strategies.test.ts @@ -262,6 +262,34 @@ test("lkgp — lkgpEnabled:false delegates to rules", () => { assert.equal(d.strategy, "rules"); }); +test("lkgp — falls back to rules when the pool lacks the last known good provider", () => { + const pool = [cand({ provider: "x" })]; + const context: RoutingContext = { + taskType: "default", + lastKnownGoodProvider: "y", + }; + const d = getStrategy("lkgp").select(pool, context); + const expected = getStrategy("rules").select(pool, context); + assert.equal(d.strategy, expected.strategy); + assert.equal(d.provider, expected.provider); + assert.equal(d.model, expected.model); +}); + +test("lkgp — keeps the first candidate among several targets of the pinned provider", () => { + const pool = [ + cand({ provider: "shared", model: "shared/m1", connectionId: "conn-1" }), + cand({ provider: "shared", model: "shared/m2", connectionId: "conn-2" }), + ]; + const d = getStrategy("lkgp").select(pool, { + taskType: "default", + lastKnownGoodProvider: "shared", + }); + assert.equal(d.provider, "shared"); + assert.equal(d.model, "shared/m1"); + assert.equal(d.connectionId, "conn-1"); + assert.equal(d.candidatesConsidered, 1); +}); + // ── selectWithStrategy + registry ───────────────────────────────────────────── test("selectWithStrategy — dispatches by name", () => { const pool = [