From 3b9ad858257620c7937b1129be60539d4bd2ca3e Mon Sep 17 00:00:00 2001 From: Elton Turing Date: Sat, 5 Sep 2026 07:27:34 +0200 Subject: [PATCH] llmrouter: Alias-Art schwelle (Index + Mindestkontext), Stufen 25/40/47/50 billigsterUeberSchwelle in openrouter-auswahl, gemeinsamer klasseFiltern mit dem Referenzmodus. Stufen jetzt als Schwelle: low 25/256k, medium 40/256k, high 47/500k, max 50/500k (Andreas 2026-09-05). Co-Authored-By: Claude Fable 5.1 --- README.md | 4 ++++ config.json | 23 ++++++++++++++--------- src/config.ts | 1 + src/resolver.ts | 26 +++++++++++++++++--------- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 99a81ac..7fd202b 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ Zwei Arten: Die Effort-Stufe des Ziels wird als `reasoning.effort` gesetzt; ein vom Client mitgeschicktes `reasoning_effort` wird verworfen, die Klasse gehört zum Alias. +- **`schwelle`** — Index-Schwelle (0–100) plus Mindestkontext, ohne Referenzmodell: + das günstigste Modell, das beides erfüllt, mit der niedrigsten ausreichenden + Effort-Stufe. Stand 2026-09-05: low 25/256k, medium 40/256k, high 47/500k, + max 50/500k. Stabiler als eine Referenz, sagt aber weniger über die Bedeutung. - **`fest`** — Upstream und Modell stehen fest (lokales Qwen, TTS/STT, Embeddings, kira auf deizo). Optional ein `body`-Patch, etwa `chat_template_kwargs.enable_thinking` für die Qwen-Stufen. diff --git a/config.json b/config.json index dcffb9b..9c71199 100644 --- a/config.json +++ b/config.json @@ -35,20 +35,24 @@ }, "aliases": { "marvin:low": { - "art": "alternative", - "referenz": "anthropic/claude-haiku-4.5" + "art": "schwelle", + "index": 25, + "minContext": 256000 }, "marvin:medium": { - "art": "alternative", - "referenz": "anthropic/claude-sonnet-5" + "art": "schwelle", + "index": 40, + "minContext": 256000 }, "marvin:high": { - "art": "alternative", - "referenz": "anthropic/claude-opus-5:medium" + "art": "schwelle", + "index": 47, + "minContext": 500000 }, "marvin:max": { - "art": "alternative", - "referenz": "anthropic/claude-fable-5.1:high" + "art": "schwelle", + "index": 50, + "minContext": 500000 }, "marvin-free": { "art": "fest", @@ -133,5 +137,6 @@ "42i/marvin-2608-core:medium": "marvin-core:medium", "marvin-reasoning": "marvin-core:medium", "marvin-core": "marvin-core:none" - } + }, + "_stufen_doku": "Stufen als Index-Schwelle (Artificial-Analysis-Intelligence-Index, 0-100) mit Mindestkontext (Andreas 2026-09-05): low 25/256k, medium 40/256k, high 47/500k, max 50/500k. Die Art \"alternative\" mit Referenzmodell (z.B. anthropic/claude-opus-5:medium) bleibt möglich." } diff --git a/src/config.ts b/src/config.ts index 7416fa1..1b63a09 100644 --- a/src/config.ts +++ b/src/config.ts @@ -13,6 +13,7 @@ import { dirname, resolve } from "path"; export type Upstream = { base: string; keyEnv?: string; maxParallel?: number }; export type Alias = | { art: "alternative"; referenz: string } + | { art: "schwelle"; index: number; minContext: number } | { art: "fest"; upstream: string; model: string; body?: Record }; export type Config = { port: number; diff --git a/src/resolver.ts b/src/resolver.ts index 9cd2822..e95578d 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -12,7 +12,7 @@ */ import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, statSync } from "fs"; import { - apiKeyHolen, aaKeyHolen, listenHolen, kandidatenBauen, sperrliste, guenstigereAlternative, + apiKeyHolen, aaKeyHolen, listenHolen, kandidatenBauen, sperrliste, guenstigereAlternative, billigsterUeberSchwelle, variantenHolen, referenzParsen, referenzFormat, type Variante, type Effort, } from "../../llmlite/openrouter-auswahl.ts"; import { configLaden, type Config } from "./config.ts"; @@ -74,20 +74,28 @@ export async function aufloesen(c: Config, bisher: State, opts?: { dryRun?: bool const neu: State = { aktualisiert: new Date().toISOString(), zuordnung: { ...bisher.zuordnung } }; for (const [alias, def] of Object.entries(c.aliases)) { - if (def.art !== "alternative") continue; + if (def.art === "fest") continue; const alt = bisher.zuordnung[alias]; try { const abgelehnt: string[] = []; - const { referenz, auswahl } = await guenstigereAlternative(def.referenz, alle, key, - (id, grund) => abgelehnt.push(`${id}: ${grund}`), gesperrt, - { varianten, lasttest: c.lasttest, bestand: alt?.model }); - const ziel: Zuordnung = auswahl === null - ? { model: referenz.id, effort: referenz.effort ?? null, index: referenz.index, weighted: referenz.weighted, referenz: def.referenz, seit: alt?.seit ?? neu.aktualisiert, istReferenz: true } - : { model: auswahl.candidate.id, effort: auswahl.candidate.effort ?? null, index: auswahl.candidate.index, weighted: auswahl.candidate.weighted, referenz: def.referenz, seit: alt?.seit ?? neu.aktualisiert, istReferenz: false }; + const melden = (id: string, grund: string) => abgelehnt.push(`${id}: ${grund}`); + const o = { varianten, lasttest: c.lasttest, bestand: alt?.model }; + let ziel: Zuordnung; + if (def.art === "alternative") { + const { referenz, auswahl } = await guenstigereAlternative(def.referenz, alle, key, melden, gesperrt, o); + ziel = auswahl === null + ? { model: referenz.id, effort: referenz.effort ?? null, index: referenz.index, weighted: referenz.weighted, referenz: def.referenz, seit: alt?.seit ?? neu.aktualisiert, istReferenz: true } + : { model: auswahl.candidate.id, effort: auswahl.candidate.effort ?? null, index: auswahl.candidate.index, weighted: auswahl.candidate.weighted, referenz: def.referenz, seit: alt?.seit ?? neu.aktualisiert, istReferenz: false }; + } else { + const beschreibung = `Index>=${def.index}, Kontext>=${def.minContext}`; + const { auswahl } = await billigsterUeberSchwelle(def.index, def.minContext, alle, key, melden, gesperrt, o); + if (auswahl === null) throw new Error(`kein Modell erfuellt ${beschreibung}`); + ziel = { model: auswahl.candidate.id, effort: auswahl.candidate.effort ?? null, index: auswahl.candidate.index, weighted: auswahl.candidate.weighted, referenz: beschreibung, seit: alt?.seit ?? neu.aktualisiert, istReferenz: false }; + } const gleich = alt !== undefined && alt.model === ziel.model && alt.effort === ziel.effort; if (!gleich) { ziel.seit = neu.aktualisiert; - const zeile = `${neu.aktualisiert} ${alias}: ${alt ? referenzFormat(alt.model, alt.effort) : "(neu)"} -> ${referenzFormat(ziel.model, ziel.effort)} (Index ${ziel.index.toFixed(1)}, ${ziel.weighted.toFixed(2)} $/1M gewichtet, Referenz ${def.referenz}${ziel.istReferenz ? ", = Referenz" : ""})`; + const zeile = `${neu.aktualisiert} ${alias}: ${alt ? referenzFormat(alt.model, alt.effort) : "(neu)"} -> ${referenzFormat(ziel.model, ziel.effort)} (Index ${ziel.index.toFixed(1)}, ${ziel.weighted.toFixed(2)} $/1M gewichtet, ${ziel.referenz}${ziel.istReferenz ? ", = Referenz" : ""})`; log(zeile); if (!opts?.dryRun) { mkdirSync(c.logDir, { recursive: true }); appendFileSync(`${c.logDir}/changes.log`, zeile + "\n"); } }