llmrouter: role/inka -> openrouter/qwen/qwen3.8-27b (Test, Andreas 2026-09-06); Inka-Keys dürfen die Rolle
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+3
-2
@@ -143,7 +143,7 @@
|
|||||||
"upstream": "litellm",
|
"upstream": "litellm",
|
||||||
"_doku": "Unbekannte Modellnamen (z.B. 42i/marvin-2608:*) gehen unverändert mit dem Client-Key an LiteLLM. Umstellung je Agent = Modellname 2608 -> 2609. Fällt weg, wenn LiteLLM abgeschaltet wird; dann kommen die 2608-Namen als altnamen zurück."
|
"_doku": "Unbekannte Modellnamen (z.B. 42i/marvin-2608:*) gehen unverändert mit dem Client-Key an LiteLLM. Umstellung je Agent = Modellname 2608 -> 2609. Fällt weg, wenn LiteLLM abgeschaltet wird; dann kommen die 2608-Namen als altnamen zurück."
|
||||||
},
|
},
|
||||||
"_rollen_doku": "Rollen entkoppeln Agenten von Modellnamen: ein Agent ruft role/worker, welches Modell dahintersteht, stellt man HIER um -- aus der Ferne, ohne Container-Deploy. Ziele: 42i/*-Alias, openrouter/<id>[:effort] (1:1) oder alt/<id>[:effort] (guenstigste Alternative). Vorbelegung 2026-09-05 (Elton): Fuehrungsrollen high, Rest medium -- Andreas' Feinjustierung ausstehend.",
|
"_rollen_doku": "Rollen entkoppeln Agenten von Modellnamen: ein Agent ruft role/worker, welches Modell dahintersteht, stellt man HIER um -- aus der Ferne, ohne Container-Deploy. Ziele: 42i/*-Alias, openrouter/<id>[:effort] (1:1) oder alt/<id>[:effort] (guenstigste Alternative). Vorbelegung 2026-09-05 (Elton): Fuehrungsrollen high, Rest medium -- Andreas' Feinjustierung ausstehend. role/inka (2026-09-06): Inkas Telefon-Hirn; Test Qwen 3.8 27B als Kandidat fuer die A6000 bei Uwe, vorher openrouter/openai/gpt-5.6-luna.",
|
||||||
"rollen": {
|
"rollen": {
|
||||||
"xo": "42i/marvin-2609:high",
|
"xo": "42i/marvin-2609:high",
|
||||||
"architect": "42i/marvin-2609:high",
|
"architect": "42i/marvin-2609:high",
|
||||||
@@ -164,7 +164,8 @@
|
|||||||
"i18n": "42i/marvin-2609:medium",
|
"i18n": "42i/marvin-2609:medium",
|
||||||
"docs": "42i/marvin-2609:medium",
|
"docs": "42i/marvin-2609:medium",
|
||||||
"ar": "42i/marvin-2609:medium",
|
"ar": "42i/marvin-2609:medium",
|
||||||
"archivist": "42i/marvin-2609:medium"
|
"archivist": "42i/marvin-2609:medium",
|
||||||
|
"inka": "openrouter/qwen/qwen3.8-27b"
|
||||||
},
|
},
|
||||||
"_namen_doku": "Namensraeume: 42i/* = konfigurierte Aliasse (aliases); deizo/* = Modelle im Prod-RZ deizo (kira, fest); openrouter/<id>[:effort] = 1:1 an OpenRouter, Effort als reasoning.effort; alt/<id>[:effort] = guenstigste Alternative derselben Klasse (Intelligence, Coding, Agentic je >= 95 % der Referenz, Kontext >= 90 %, billiger), aufgeloest bei der ersten Anfrage und im 12h-Takt erneuert, OHNE Last-Probe; role/<name> = Rolle (rollen). Alles andere -> fallback (LiteLLM).",
|
"_namen_doku": "Namensraeume: 42i/* = konfigurierte Aliasse (aliases); deizo/* = Modelle im Prod-RZ deizo (kira, fest); openrouter/<id>[:effort] = 1:1 an OpenRouter, Effort als reasoning.effort; alt/<id>[:effort] = guenstigste Alternative derselben Klasse (Intelligence, Coding, Agentic je >= 95 % der Referenz, Kontext >= 90 %, billiger), aufgeloest bei der ersten Anfrage und im 12h-Takt erneuert, OHNE Last-Probe; role/<name> = Rolle (rollen). Alles andere -> fallback (LiteLLM).",
|
||||||
"anthropicModus": "alt",
|
"anthropicModus": "alt",
|
||||||
|
|||||||
+4
-2
@@ -88,7 +88,8 @@
|
|||||||
"42i/marvin-stt-turbo",
|
"42i/marvin-stt-turbo",
|
||||||
"42i/marvin-tts",
|
"42i/marvin-tts",
|
||||||
"42i/marvin-tts-turbo",
|
"42i/marvin-tts-turbo",
|
||||||
"openrouter/openai/gpt-5.6-luna"
|
"openrouter/openai/gpt-5.6-luna",
|
||||||
|
"role/inka"
|
||||||
],
|
],
|
||||||
"persona-inka-mx": [
|
"persona-inka-mx": [
|
||||||
"42i/marvin-2609-core:none",
|
"42i/marvin-2609-core:none",
|
||||||
@@ -102,7 +103,8 @@
|
|||||||
"42i/marvin-stt-turbo",
|
"42i/marvin-stt-turbo",
|
||||||
"42i/marvin-tts",
|
"42i/marvin-tts",
|
||||||
"42i/marvin-tts-turbo",
|
"42i/marvin-tts-turbo",
|
||||||
"openrouter/openai/gpt-5.6-luna"
|
"openrouter/openai/gpt-5.6-luna",
|
||||||
|
"role/inka"
|
||||||
],
|
],
|
||||||
"persona-andreas": [],
|
"persona-andreas": [],
|
||||||
"nightjob": [
|
"nightjob": [
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ function jsonBody(orig: Record<string, unknown>, ziel: Ziel, pfad: string): Reco
|
|||||||
// Sprechtext vor der Synthese normalisieren (Postleitzahlen als Ziffernfolge mit Pause),
|
// Sprechtext vor der Synthese normalisieren (Postleitzahlen als Ziffernfolge mit Pause),
|
||||||
// fuer jedes TTS-Ziel gleich -- die Modelle lesen Betraege und Daten, PLZ nicht.
|
// fuer jedes TTS-Ziel gleich -- die Modelle lesen Betraege und Daten, PLZ nicht.
|
||||||
if (pfad === "/audio/speech" && typeof b.input === "string") b.input = sprechtextNormalisieren(b.input);
|
if (pfad === "/audio/speech" && typeof b.input === "string") b.input = sprechtextNormalisieren(b.input);
|
||||||
|
// OpenRouter-TTS kennt nur mp3 und pcm. Clients wie Inkas Telefon-Front-End
|
||||||
|
// verlangen WAV -- wir holen PCM und setzen den RIFF-Kopf selbst (s. unten).
|
||||||
|
if (pfad === "/audio/speech" && ziel.upstreamName === "openrouter" && b.response_format === "wav") b.response_format = "pcm";
|
||||||
if (pfad.startsWith("/messages")) {
|
if (pfad.startsWith("/messages")) {
|
||||||
// Anthropic-Format: kein usage.include, Effort als reasoning.effort (OpenRouter nimmt es dort an)
|
// Anthropic-Format: kein usage.include, Effort als reasoning.effort (OpenRouter nimmt es dort an)
|
||||||
if (ziel.effortSetzen) { delete b.thinking; delete b.output_config; if (ziel.effort !== null) b.reasoning = { effort: ziel.effort }; }
|
if (ziel.effortSetzen) { delete b.thinking; delete b.output_config; if (ziel.effort !== null) b.reasoning = { effort: ziel.effort }; }
|
||||||
@@ -197,6 +200,18 @@ async function modellListe(keyName: string, auth: string): Promise<{ id: string;
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Setzt einen RIFF/WAVE-Kopf (PCM 16 bit) vor rohe s16le-Daten. */
|
||||||
|
function wavVerpacken(pcm: Uint8Array, rate: number, kanaele: number): Uint8Array {
|
||||||
|
const kopf = new ArrayBuffer(44); const v = new DataView(kopf);
|
||||||
|
const str = (o: number, t: string) => { for (let i = 0; i < t.length; i++) v.setUint8(o + i, t.charCodeAt(i)); };
|
||||||
|
str(0, "RIFF"); v.setUint32(4, 36 + pcm.byteLength, true); str(8, "WAVE");
|
||||||
|
str(12, "fmt "); v.setUint32(16, 16, true); v.setUint16(20, 1, true); v.setUint16(22, kanaele, true);
|
||||||
|
v.setUint32(24, rate, true); v.setUint32(28, rate * kanaele * 2, true); v.setUint16(32, kanaele * 2, true); v.setUint16(34, 16, true);
|
||||||
|
str(36, "data"); v.setUint32(40, pcm.byteLength, true);
|
||||||
|
const aus = new Uint8Array(44 + pcm.byteLength); aus.set(new Uint8Array(kopf), 0); aus.set(pcm, 44);
|
||||||
|
return aus;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Server -------------------------------------------------------------------
|
// --- Server -------------------------------------------------------------------
|
||||||
Bun.serve({
|
Bun.serve({
|
||||||
port: Number(process.env.PORT ?? config.port),
|
port: Number(process.env.PORT ?? config.port),
|
||||||
@@ -224,6 +239,7 @@ Bun.serve({
|
|||||||
let angefragt = "";
|
let angefragt = "";
|
||||||
let upstreamBody: BodyInit;
|
let upstreamBody: BodyInit;
|
||||||
let stream = false;
|
let stream = false;
|
||||||
|
let wavGewuenscht = false;
|
||||||
let ziel: Ziel;
|
let ziel: Ziel;
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
|
|
||||||
@@ -237,6 +253,7 @@ Bun.serve({
|
|||||||
if ("fehler" in z) return Response.json({ error: { message: z.fehler } }, { status: z.status });
|
if ("fehler" in z) return Response.json({ error: { message: z.fehler } }, { status: z.status });
|
||||||
ziel = z;
|
ziel = z;
|
||||||
stream = orig.stream === true;
|
stream = orig.stream === true;
|
||||||
|
wavGewuenscht = pfad === "/audio/speech" && orig.response_format === "wav";
|
||||||
upstreamBody = JSON.stringify(ziel.upstream.clientKey === true ? orig : jsonBody(orig, ziel, pfad));
|
upstreamBody = JSON.stringify(ziel.upstream.clientKey === true ? orig : jsonBody(orig, ziel, pfad));
|
||||||
headers.set("content-type", "application/json");
|
headers.set("content-type", "application/json");
|
||||||
} else {
|
} else {
|
||||||
@@ -310,6 +327,16 @@ Bun.serve({
|
|||||||
return new Response(antwort.body.pipeThrough(tee), { status: antwort.status, headers: weiter });
|
return new Response(antwort.body.pipeThrough(tee), { status: antwort.status, headers: weiter });
|
||||||
}
|
}
|
||||||
freigeben();
|
freigeben();
|
||||||
|
if (ct.startsWith("audio/pcm") && wavGewuenscht) {
|
||||||
|
// PCM s16le -> WAV; Rate und Kanaele stehen im Content-Type (audio/pcm;rate=24000;channels=1)
|
||||||
|
const rate = Number(/rate=(\d+)/u.exec(ct)?.[1] ?? 24000);
|
||||||
|
const kanaele = Number(/channels=(\d+)/u.exec(ct)?.[1] ?? 1);
|
||||||
|
const pcm = new Uint8Array(await antwort.arrayBuffer());
|
||||||
|
const wav = wavVerpacken(pcm, rate, kanaele);
|
||||||
|
weiter.set("content-type", "audio/wav"); weiter.set("content-length", String(wav.byteLength));
|
||||||
|
abschliessen();
|
||||||
|
return new Response(wav, { status: antwort.status, headers: weiter });
|
||||||
|
}
|
||||||
if (ct.includes("application/json")) {
|
if (ct.includes("application/json")) {
|
||||||
const text = await antwort.text();
|
const text = await antwort.text();
|
||||||
let usage: unknown; let fehler: string | undefined;
|
let usage: unknown; let fehler: string | undefined;
|
||||||
|
|||||||
Reference in New Issue
Block a user