Ausweich auch für Schwelle-Aliasse und bei 429/5xx
auto/mass:fast war als Endstation ohne Rückfall: ling-3.0-flash fiel am 2026-09-07 nachts aus (5x 502 nach ~270 s hängendem Upstream, 117x 429), und der bisherige Ausweich griff nur bei fetch-Fehlern und nur für fest-Ziele. Jetzt darf auch ein Schwelle-Alias ausweichen, und der Ausweich löst bei 429/5xx aus — bevor das erste Byte an den Client geht, also gefahrlos wiederholbar, weiterhin nur einmal. auto/mass:fast weicht auf auto/coding:junior aus (ling erfüllt deren Schwellen strukturell nie, der Rückfall landet also garantiert auf einem tauglichen Modell). Die Container-Personas bekommen die Kettenziele in die ACL — ohne sie schlägt der Fallback in zielBestimmen still fehl und der ursprüngliche Fehler geht durch. Verifiziert im lokalen End-to-End-Test gegen Schein-Upstreams: Ausweich bei 429 (200 vom Ersatzmodell, Log-Zeile alias->ausweich), Durchreichung von 429 ohne ACL-Freigabe, fetch-catch-Pfad unverändert funktionierend. Refs 42i/intern#1252
This commit is contained in:
+7
-6
@@ -163,12 +163,13 @@
|
||||
"ausweich": "auto/mass:fast",
|
||||
"_doku": "Massenarbeit ohne Latenzanspruch: Mail-Ingest, Triage, Zusammenfassungen, opencodes small_model. Laeuft auf der eigenen Karte und kostet nichts. Steht der Knoten oder ist die Karte belegt, weicht der Router einmal auf auto/mass:fast aus -- ohne das haengt sonst jede Titelzeile jedes Agenten (42i/intern#1252)."
|
||||
},
|
||||
"auto/mass:fast": {
|
||||
"art": "schwelle",
|
||||
"index": 25,
|
||||
"minContext": 256000,
|
||||
"_doku": "Wie auto/mass, aber wenn es schnell gehen muss: billigstes Cloud-Modell ueber der Mindestschwelle. Andreas 2026-09-06: 'bei masse ist halt: brauchst du es schnell, nimm ling, ist geschwindigkeit nicht wichtig, nimm core'."
|
||||
},
|
||||
"auto/mass:fast": {
|
||||
"art": "schwelle",
|
||||
"index": 25,
|
||||
"minContext": 256000,
|
||||
"ausweich": "auto/coding:junior",
|
||||
"_doku": "Wie auto/mass, aber wenn es schnell gehen muss: billigstes Cloud-Modell ueber der Mindestschwelle. Andreas 2026-09-06: 'bei masse ist halt: brauchst du es schnell, nimm ling, ist geschwindigkeit nicht wichtig, nimm core'. Ausweich auf auto/coding:junior seit 2026-09-08 (Andreas): ling hat nachts Kapazitaets- und Ratengrenzprobleme (2026-09-07 23:20-23:52: 5x 502 nach ~270 s haengendem Upstream, 117x 429), und auto/mass:fast war als Endstation ohne Rueckfall. coding:junior erfuellt ling strukturell nie (Coding 50,6 / Agentic 21,1 gegen Coding>=68 / Agentic>=45) -- der Ausweich landet also garantiert auf einem tauglichen Modell, nicht auf ling selbst."
|
||||
},
|
||||
"auto/allround:junior": {
|
||||
"art": "schwelle",
|
||||
"index": 40,
|
||||
|
||||
+124
-118
@@ -1,120 +1,126 @@
|
||||
{
|
||||
"_note": "Rechte je Client-Key: Name -> erlaubte Aliasse (neue Namen; Altnamen werden vor der Pruefung uebersetzt). [] = alles. Die Token selbst liegen NICHT hier, sondern in KEYS_FILE auf dem Host (token -> name). Abgeleitet aus llmlite/key-acls.json am 2026-09-05: role/* und -auto gestrichen (Andreas), -auto -> :medium. Stand 2026-09-05 abends: Stufen low/medium/high/max; none->low, extra->high, ultra->max, mass->low. Namen ab 2026-09-05: 42i/marvin-2609:*; die Rechte gelten nur fuer Router-Aliasse, unbekannte Namen (2608) reicht der Router an LiteLLM durch, dort gelten dessen ACLs. Muster mit * erlaubt (openrouter/*, alt/*, role/*). Rollen aus der LiteLLM-Zeit je Key wieder eingetragen; Elton und Andreas duerfen openrouter/* und alt/*. 2026-09-07: core:none fuer alle Container-Personas, weil opencodes small_model (Titel, Zusammenfassungen) dorthin zeigt -- nach 42i/intern#1252 wird daraus auto/mass. 2026-09-07 aufgeraeumt: 'nightjob' (kein Token, kein Konsument, keine Nutzung) und 'persona-inka-mx' entfernt -- beide Inka-Tokens (Shim und Telefon-Front-End) laufen im Router unter 'persona-inka', der mx-Name existierte nur noch als LiteLLM-key_alias.",
|
||||
"keys": {
|
||||
"persona-spark": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-tts",
|
||||
"role/engineer",
|
||||
"role/worker"
|
||||
],
|
||||
"persona-buzz": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-tts",
|
||||
"role/ops",
|
||||
"role/worker"
|
||||
],
|
||||
"persona-jessie": [
|
||||
"42i/marvin-2609-core:medium",
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-tts",
|
||||
"role/assistant",
|
||||
"role/marketing"
|
||||
],
|
||||
"persona-klara": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-tts",
|
||||
"role/service"
|
||||
],
|
||||
"persona-elton": [
|
||||
"42i/marvin-2609:low",
|
||||
"42i/marvin-2609:medium",
|
||||
"42i/marvin-2609:high",
|
||||
"42i/marvin-2609:max",
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609-core:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-stt-turbo",
|
||||
"42i/marvin-tts",
|
||||
"42i/marvin-tts-turbo",
|
||||
"role/*",
|
||||
"openrouter/*",
|
||||
"alt/*",
|
||||
"auto/*"
|
||||
],
|
||||
"persona-festus": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609-core:medium",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"role/privacy"
|
||||
],
|
||||
"persona-aria": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609-core:medium",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"role/architect"
|
||||
],
|
||||
"persona-inka": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609-core:medium",
|
||||
"42i/marvin-2609:low",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-stt-turbo",
|
||||
"42i/marvin-tts",
|
||||
"42i/marvin-tts-turbo",
|
||||
"openrouter/openai/gpt-5.6-luna",
|
||||
"role/inka"
|
||||
],
|
||||
"persona-andreas": [],
|
||||
"lab-xo": [
|
||||
"42i/marvin-2609:low",
|
||||
"42i/marvin-2609:high",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"role/xo",
|
||||
"role/*",
|
||||
"auto/*"
|
||||
],
|
||||
"team-copilot": [
|
||||
"42i/marvin-2609:low",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts"
|
||||
]
|
||||
}
|
||||
"_note": "Rechte je Client-Key: Name -> erlaubte Aliasse (neue Namen; Altnamen werden vor der Pruefung uebersetzt). [] = alles. Die Token selbst liegen NICHT hier, sondern in KEYS_FILE auf dem Host (token -> name). Abgeleitet aus llmlite/key-acls.json am 2026-09-05: role/* und -auto gestrichen (Andreas), -auto -> :medium. Stand 2026-09-05 abends: Stufen low/medium/high/max; none->low, extra->high, ultra->max, mass->low. Namen ab 2026-09-05: 42i/marvin-2609:*; die Rechte gelten nur fuer Router-Aliasse, unbekannte Namen (2608) reicht der Router an LiteLLM durch, dort gelten dessen ACLs. Muster mit * erlaubt (openrouter/*, alt/*, role/*). Rollen aus der LiteLLM-Zeit je Key wieder eingetragen; Elton und Andreas duerfen openrouter/* und alt/*. 2026-09-07: core:none fuer alle Container-Personas, weil opencodes small_model (Titel, Zusammenfassungen) dorthin zeigt -- nach 42i/intern#1252 wird daraus auto/mass. 2026-09-07 aufgeraeumt: 'nightjob' (kein Token, kein Konsument, keine Nutzung) und 'persona-inka-mx' entfernt -- beide Inka-Tokens (Shim und Telefon-Front-End) laufen im Router unter 'persona-inka', der mx-Name existierte nur noch als LiteLLM-key_alias. 2026-09-08: auto/mass:fast und auto/coding:junior fuer die Container-Personas -- die Ausweich-Kette auto/mass -> auto/mass:fast -> auto/coding:junior laeuft durch die ACL-Pruefung des aufrufenden Keys; ohne die Ziele dort schlaegt der Fallback still fehl (403 in zielBestimmen) und der urspruengliche Fehler geht durch.",
|
||||
"keys": {
|
||||
"persona-spark": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609:medium",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-tts",
|
||||
"auto/coding:junior",
|
||||
"auto/mass:fast",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"role/engineer",
|
||||
"role/worker"
|
||||
],
|
||||
"persona-buzz": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609:medium",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-tts",
|
||||
"auto/coding:junior",
|
||||
"auto/mass:fast",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"role/ops",
|
||||
"role/worker"
|
||||
],
|
||||
"persona-jessie": [
|
||||
"42i/marvin-2609-core:medium",
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609:medium",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-tts",
|
||||
"auto/mass:fast",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"role/assistant",
|
||||
"role/marketing"
|
||||
],
|
||||
"persona-klara": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609:medium",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-tts",
|
||||
"auto/mass:fast",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"role/service"
|
||||
],
|
||||
"persona-elton": [
|
||||
"42i/marvin-2609:low",
|
||||
"42i/marvin-2609:medium",
|
||||
"42i/marvin-2609:high",
|
||||
"42i/marvin-2609:max",
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609-core:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-stt-turbo",
|
||||
"42i/marvin-tts",
|
||||
"42i/marvin-tts-turbo",
|
||||
"role/*",
|
||||
"openrouter/*",
|
||||
"alt/*",
|
||||
"auto/*"
|
||||
],
|
||||
"persona-festus": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609-core:medium",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"role/privacy"
|
||||
],
|
||||
"persona-aria": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609-core:medium",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"role/architect"
|
||||
],
|
||||
"persona-inka": [
|
||||
"42i/marvin-2609-core:none",
|
||||
"42i/marvin-2609-core:medium",
|
||||
"42i/marvin-2609:low",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"42i/marvin-stt",
|
||||
"42i/marvin-stt-turbo",
|
||||
"42i/marvin-tts",
|
||||
"42i/marvin-tts-turbo",
|
||||
"openrouter/openai/gpt-5.6-luna",
|
||||
"role/inka"
|
||||
],
|
||||
"persona-andreas": [],
|
||||
"lab-xo": [
|
||||
"42i/marvin-2609:low",
|
||||
"42i/marvin-2609:high",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts",
|
||||
"role/xo",
|
||||
"role/*",
|
||||
"auto/*"
|
||||
],
|
||||
"team-copilot": [
|
||||
"42i/marvin-2609:low",
|
||||
"42i/marvin-2609:medium",
|
||||
"deizo/kira-reasoning",
|
||||
"deizo/kira-stt",
|
||||
"deizo/kira-tts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -15,10 +15,11 @@ export type Alias =
|
||||
| { art: "alternative"; referenz: string }
|
||||
// Schwelle auf bis zu drei Achsen (42i/intern#1252): index = Intelligence,
|
||||
// coding und agentic je eigene Untergrenze. Fehlt eine, gilt sie nicht.
|
||||
| { art: "schwelle"; index?: number; coding?: number; agentic?: number; minContext: number }
|
||||
// ausweich: Alias, auf den bei Upstream-Ausfall gewechselt wird. Nur fuer
|
||||
// "fest" sinnvoll -- ein festes Ziel hat sonst keinen Rueckfall, und genau
|
||||
// das trifft das lokale Qwen: steht der Knoten, haengt jede Massenarbeit.
|
||||
| { art: "schwelle"; index?: number; coding?: number; agentic?: number; minContext: number; ausweich?: string }
|
||||
// ausweich: Alias, auf den bei Upstream-Ausfall gewechselt wird. Fuer "fest"
|
||||
// und "schwelle" -- ein Ziel ohne Rueckfall haengt sonst jede Arbeit, die
|
||||
// darauf zeigt: festes Ziel = lokales Qwen, schwelle = Billigmodell mit
|
||||
// Ratengrenzen und haengendem Upstream (429-Burst 2026-09-07).
|
||||
| { art: "fest"; upstream: string; model: string; body?: Record<string, unknown>; ausweich?: string };
|
||||
export type Config = {
|
||||
port: number;
|
||||
|
||||
+31
-3
@@ -118,7 +118,7 @@ class SlotFrist extends Error {
|
||||
}
|
||||
|
||||
// --- Aufloesung Name -> Ziel ---------------------------------------------------
|
||||
type Ziel = { alias: string; upstreamName: string; upstream: Upstream; model: string; effort: Effort | null; body?: Record<string, unknown>; effortSetzen: boolean; /** Alias, auf den bei Upstream-Ausfall einmal ausgewichen wird (nur "fest", s. config.ts). */ ausweich?: string };
|
||||
type Ziel = { alias: string; upstreamName: string; upstream: Upstream; model: string; effort: Effort | null; body?: Record<string, unknown>; effortSetzen: boolean; /** Alias, auf den bei Upstream-Ausfall einmal ausgewichen wird (fest oder schwelle, s. config.ts). */ ausweich?: string };
|
||||
type Fehler = { fehler: string; status: number };
|
||||
const OR = (): Upstream => config.upstreams.openrouter;
|
||||
|
||||
@@ -172,7 +172,7 @@ async function zielBestimmen(angefragtRoh: string, keyName: string, clientEffort
|
||||
if (def.art === "fest") return { alias: name, upstreamName: def.upstream, upstream: config.upstreams[def.upstream], model: def.model, effort: null, body: def.body, effortSetzen: false, ausweich: def.ausweich };
|
||||
const z = state.zuordnung[name];
|
||||
if (z === undefined) return { fehler: `${name} ist noch nicht aufgeloest (Zuordnung fehlt) -- spaeter erneut`, status: 503 };
|
||||
return { alias: name, upstreamName: "openrouter", upstream: OR(), model: z.model, effort: z.effort, effortSetzen: true };
|
||||
return { alias: name, upstreamName: "openrouter", upstream: OR(), model: z.model, effort: z.effort, effortSetzen: true, ausweich: def.ausweich };
|
||||
}
|
||||
if (name.startsWith("openrouter/")) {
|
||||
const { id, effort } = referenzParsen(name.slice(11));
|
||||
@@ -373,7 +373,9 @@ Bun.serve({
|
||||
} catch (e) {
|
||||
freigeben();
|
||||
const grund = (e as Error).message;
|
||||
// Ausweich (42i/intern#1252): ein "festes" Ziel hat keinen Rueckfall --
|
||||
// Ausweich (42i/intern#1252): ein Ziel ohne eigenen Rueckfall (festes
|
||||
// Ziel oder Schwelle auf einem Ausfall-Modell) wuerde den Aufrufer leer
|
||||
// ausgehen lassen --
|
||||
// steht der lokale LLM-Knoten oder haelt eine fremde Arbeit die Karte,
|
||||
// bekaeme der Aufrufer gar keine Antwort statt einer langsameren. Genau
|
||||
// das trifft die Massenarbeit (auto/mass -> lokales Qwen), die ueberall
|
||||
@@ -407,6 +409,32 @@ Bun.serve({
|
||||
return Response.json({ error: { message: `Upstream ${ziel.upstreamName} nicht erreichbar: ${eintrag.fehler}` } }, { status: 502 });
|
||||
}
|
||||
}
|
||||
// Ausweich auch bei Upstream-Antwortfehlern: 429 und 5xx kommen als
|
||||
// gueltige HTTP-Antwort durch und erreichen den fetch-catch nicht -- genau
|
||||
// der Modus, in dem Billigmodelle nachts ausfallen (429-Burst am
|
||||
// 2026-09-07, 42i/intern#1252). Der Wechsel passiert, bevor das erste
|
||||
// Byte an den Client geht, und nur EINMAL; scheitert auch der Ausweich,
|
||||
// geht die urspruengliche Antwort durch.
|
||||
if (!antwort.ok && (antwort.status === 429 || antwort.status >= 500) && ziel.ausweich !== undefined && origJson !== null) {
|
||||
const z2 = await zielBestimmen(ziel.ausweich, keyName);
|
||||
if (!("fehler" in z2)) {
|
||||
sagen(`Ausweich ${ziel.alias} -> ${ziel.ausweich} (Upstream ${ziel.upstreamName} antwortete ${antwort.status})`);
|
||||
const h2 = new Headers(headers);
|
||||
const k2 = upstreamKey(z2.upstream);
|
||||
if (k2 !== undefined) h2.set("authorization", `Bearer ${k2}`);
|
||||
if (z2.upstreamName === "openrouter") { h2.set("http-referer", "https://llm.lan"); h2.set("x-title", `llmrouter/${keyName}`); }
|
||||
freigeben();
|
||||
freigeben = await belegen(z2.upstreamName, z2.upstream);
|
||||
try {
|
||||
antwort = await fetch(`${z2.upstream.base}${pfad}`, { method: "POST", headers: h2, body: JSON.stringify(jsonBody(origJson, z2, pfad)), signal: AbortSignal.timeout(600_000) });
|
||||
eintrag.alias = `${ziel.alias}->${ziel.ausweich}`; eintrag.model = z2.model; eintrag.upstream = z2.upstreamName; eintrag.effort = z2.effort;
|
||||
ziel = z2;
|
||||
} catch (e2) {
|
||||
freigeben();
|
||||
eintrag.fehler = `Ausweich: ${(e2 as Error).message}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
eintrag.status = antwort.status;
|
||||
const ct = antwort.headers.get("content-type") ?? "";
|
||||
const weiter = new Headers(antwort.headers);
|
||||
|
||||
Reference in New Issue
Block a user