Router: Namensraum auto/ mit acht Profilen auf drei Achsen (42i/intern#1252)
Der Preisfinder waehlte bisher ueber EINE Zahl. Ein Modell kann generalistisch gut aussehen und agentisch durchfallen -- gemini-3.8-flash hat Coding 76,3 und Agentic 41,2 und traegt den Werkzeug-Rundlauf nicht. - openrouter-auswahl: billigsterUeberSchwelle reicht minCoding/minAgentic an klasseFiltern durch (nahm es bereits entgegen, wurde nur nie gefuettert) - config: art "schwelle" hat jetzt index, coding und agentic, jede optional; "fest" kennt ausweich - resolver: Beschreibung nennt die geforderten Achsen - server: ein festes Ziel weicht bei Upstream-Ausfall EINMAL auf den konfigurierten Alias aus. Ohne das haengt mit dem lokalen Knoten jede Titelzeile jedes Agenten, weil auto/mass ueberall als small_model haengt. - acht Profile, Rollen darauf umgehaengt, 42i/marvin-2609:* bleiben als Namen Aufgeloest (ohne Last-Probe, alle Ziele laufen bereits ueber bestehende Aliasse): coding:junior und leader:junior -> glm-5.3-flash, coding:senior -> grok-4.6:high, leader:senior -> glm-5.3:max (15,66 statt 26 gewichtet -- die Regel findet etwas Billigeres als die Schaetzung im Ticket), mass:fast -> ling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+39
-5
@@ -73,7 +73,7 @@ async function belegen(name: string, u: Upstream): Promise<() => void> {
|
||||
}
|
||||
|
||||
// --- Aufloesung Name -> Ziel ---------------------------------------------------
|
||||
type Ziel = { alias: string; upstreamName: string; upstream: Upstream; model: string; effort: Effort | null; body?: Record<string, unknown>; effortSetzen: boolean };
|
||||
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 Fehler = { fehler: string; status: number };
|
||||
const OR = (): Upstream => config.upstreams.openrouter;
|
||||
|
||||
@@ -116,7 +116,7 @@ async function zielBestimmen(angefragtRoh: string, keyName: string, clientEffort
|
||||
}
|
||||
const def = config.aliases[name];
|
||||
if (def !== undefined) {
|
||||
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 };
|
||||
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 };
|
||||
@@ -243,6 +243,7 @@ Bun.serve({
|
||||
let ziel: Ziel;
|
||||
const headers = new Headers();
|
||||
|
||||
let origJson: Record<string, unknown> | null = null; // fuer den Ausweich: Body muss mit neuem Modellnamen neu gebaut werden
|
||||
if (JSON_PFADE.has(pfad)) {
|
||||
let orig: Record<string, unknown>;
|
||||
try { orig = (await req.json()) as Record<string, unknown>; } catch { return Response.json({ error: { message: "Body ist kein JSON" } }, { status: 400 }); }
|
||||
@@ -254,6 +255,7 @@ Bun.serve({
|
||||
ziel = z;
|
||||
stream = orig.stream === true;
|
||||
wavGewuenscht = pfad === "/audio/speech" && orig.response_format === "wav";
|
||||
origJson = orig;
|
||||
upstreamBody = JSON.stringify(ziel.upstream.clientKey === true ? orig : jsonBody(orig, ziel, pfad));
|
||||
headers.set("content-type", "application/json");
|
||||
} else {
|
||||
@@ -287,14 +289,46 @@ Bun.serve({
|
||||
try { log.schreiben(eintrag); } catch (e) { sagen(`Log-Fehler: ${(e as Error).message}`); }
|
||||
};
|
||||
|
||||
const freigeben = await belegen(ziel.upstreamName, ziel.upstream);
|
||||
let freigeben = await belegen(ziel.upstreamName, ziel.upstream);
|
||||
let antwort: Response;
|
||||
try {
|
||||
antwort = await fetch(`${ziel.upstream.base}${pfad}`, { method: "POST", headers, body: upstreamBody, signal: AbortSignal.timeout(600_000) });
|
||||
} catch (e) {
|
||||
freigeben();
|
||||
eintrag.status = 502; eintrag.fehler = (e as Error).message; abschliessen();
|
||||
return Response.json({ error: { message: `Upstream ${ziel.upstreamName} nicht erreichbar: ${eintrag.fehler}` } }, { status: 502 });
|
||||
const grund = (e as Error).message;
|
||||
// Ausweich (42i/intern#1252): ein "festes" Ziel hat keinen Rueckfall --
|
||||
// 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
|
||||
// als small_model haengt. Deshalb EINMAL auf den konfigurierten Alias
|
||||
// ausweichen; scheitert auch der, bleibt es beim 502.
|
||||
const ausweichName = ziel.ausweich;
|
||||
let gerettet = false;
|
||||
if (ausweichName !== undefined && origJson !== null) {
|
||||
const z2 = await zielBestimmen(ausweichName, keyName);
|
||||
if (!("fehler" in z2)) {
|
||||
sagen(`Ausweich ${ziel.alias} -> ${ausweichName} (${ziel.upstreamName} nicht erreichbar: ${grund})`);
|
||||
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}`); }
|
||||
const b2 = JSON.stringify(jsonBody(origJson, z2, pfad));
|
||||
freigeben = await belegen(z2.upstreamName, z2.upstream);
|
||||
try {
|
||||
antwort = await fetch(`${z2.upstream.base}${pfad}`, { method: "POST", headers: h2, body: b2, signal: AbortSignal.timeout(600_000) });
|
||||
eintrag.alias = `${ziel.alias}->${ausweichName}`; eintrag.model = z2.model; eintrag.upstream = z2.upstreamName; eintrag.effort = z2.effort;
|
||||
ziel = z2;
|
||||
gerettet = true;
|
||||
} catch (e2) {
|
||||
freigeben();
|
||||
eintrag.fehler = `${grund} / Ausweich: ${(e2 as Error).message}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!gerettet) {
|
||||
eintrag.status = 502; eintrag.fehler = eintrag.fehler ?? grund; abschliessen();
|
||||
return Response.json({ error: { message: `Upstream ${ziel.upstreamName} nicht erreichbar: ${eintrag.fehler}` } }, { status: 502 });
|
||||
}
|
||||
}
|
||||
eintrag.status = antwort.status;
|
||||
const ct = antwort.headers.get("content-type") ?? "";
|
||||
|
||||
Reference in New Issue
Block a user