feat(models): Test-Knopf — zeigt, welches Modell wirklich geantwortet hat
Bisher liess sich nicht nachsehen, ob eine Modell-Einstellung ueberhaupt greift. Der Proxy hat die Information sogar aktiv weggeworfen: normalizeModelName() nahm den echten Namen aus modelUsage (z.B. "claude-opus-5-5") und schrieb ihn auf "claude-opus-4" um. Das `model`-Feld der Antwort log also — und damit war jede Ueberpruefung unmoeglich. - normalizeModelName gibt den echten Namen durch. Im OpenAI-Protokoll soll `model` das Modell nennen, das geantwortet HAT; ein erfundener Name ist schlechter als keiner. - Zusatzfelder aria_model / aria_error / aria_context_window / aria_max_output aus modelUsage.canonicalModel. Noetig, weil die CLI bei einem unbekannten Modell gar nicht bis zum Call kommt und ihre Begruendung einfach in den Antworttext legt — ohne Flag waere ein Fehlschlag nicht von einer Antwort zu unterscheiden. - Knopf "🔬 Testen" neben Dropdown und Freitextfeld. Er schickt eine echte Mini-Anfrage durch den GANZEN Weg (Diagnostic → Proxy → extractModel → claude CLI → Anthropic). Ausdruecklich NICHT gemacht: das Modell nach seiner eigenen Version fragen. Darauf ist kein Verlass — Modelle kennen ihren Versionsstring oft nicht und raten. Und den Dropdown-Wert zurueckzugeben waere sinnlos. Die Wahrheit kommt aus modelUsage der CLI. Vier unterscheidbare Ergebnisse: Tier-Alias gruen — zeigt die aufgeloeste Version ("opus" → Opus 5.5) exakte Version gruen — angefragt == geantwortet nicht erkannt GELB — der Proxy hat still "opus" eingesetzt; warnt ausdruecklich davor, die ID so zu setzen ungueltig ROT — CLI-Begruendung im Klartext Der gelbe Fall war wichtig: ein Name, der nicht wie ein Claude-Modell aussieht, faellt auf "opus" zurueck und haette sonst als Erfolg gegolten — man setzt dann eine ID, die nie benutzt wird, und merkt es nie. Geprueft gegen ECHTE CLI-Ausgaben aus dem Proxy-Container (9 Faelle: Alias, exakte Version, Tippfehler) plus 7 Faelle fuer die Anzeige-Logik. Nebenbefund: `--model opus` loest derzeit auf claude-opus-5-5 auf. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3215,6 +3215,8 @@ wss.on("connection", (ws) => {
|
||||
handleGetModel(ws);
|
||||
} else if (msg.action === "set_model") {
|
||||
handleSetModel(ws, msg.model);
|
||||
} else if (msg.action === "test_model") {
|
||||
handleTestModel(ws, msg.model);
|
||||
} else if (msg.action === "get_attachment_limit") {
|
||||
handleGetAttachmentLimit(ws);
|
||||
} else if (msg.action === "set_attachment_limit") {
|
||||
@@ -3641,6 +3643,79 @@ function handleSetModel(clientWs, model) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Einstellungen: Model-Test ───────────────────────────────────────
|
||||
//
|
||||
// Schickt eine winzige ECHTE Anfrage durch den kompletten Weg
|
||||
// (Diagnostic -> Proxy -> extractModel -> claude CLI -> Anthropic) und meldet,
|
||||
// welches Modell tatsaechlich geantwortet hat.
|
||||
//
|
||||
// Wichtig: wir fragen NICHT das Modell selbst ("welche Version bist du?") —
|
||||
// darauf kann man sich nicht verlassen, Modelle kennen ihren eigenen
|
||||
// Versionsstring oft nicht und raten. Die Wahrheit kommt aus modelUsage der
|
||||
// CLI (Feld canonicalModel): das ist der Name, unter dem der Call wirklich
|
||||
// lief. Bei einem Alias sieht man damit die aufgeloeste Version
|
||||
// ("opus" -> "claude-opus-5-5"), bei einem ungueltigen Namen den Fehler.
|
||||
function handleTestModel(clientWs, model) {
|
||||
const m = String(model || "").trim();
|
||||
if (!m) {
|
||||
clientWs.send(JSON.stringify({
|
||||
type: "model_test_result", error: "Kein Modell angegeben" }));
|
||||
return;
|
||||
}
|
||||
const started = Date.now();
|
||||
(async () => {
|
||||
try {
|
||||
const r = await fetch(`${PROXY_URL}/v1/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
model: m,
|
||||
// Kurz halten: der Test soll Sekunden kosten, nicht Minuten.
|
||||
messages: [{ role: "user", content: "Antworte nur mit: OK" }],
|
||||
max_tokens: 16,
|
||||
}),
|
||||
signal: AbortSignal.timeout(180000),
|
||||
});
|
||||
const d = await r.json().catch(() => null);
|
||||
if (!r.ok || !d) {
|
||||
throw new Error(`Proxy antwortete HTTP ${r.status}`);
|
||||
}
|
||||
const tookMs = Date.now() - started;
|
||||
const reply = (d.choices && d.choices[0] && d.choices[0].message
|
||||
&& d.choices[0].message.content) || "";
|
||||
// Kein Modell in modelUsage ODER is_error => der Call kam nie
|
||||
// zustande. Die CLI legt ihre Begruendung dann in den Text.
|
||||
if (d.aria_error || !d.aria_model) {
|
||||
clientWs.send(JSON.stringify({
|
||||
type: "model_test_result",
|
||||
requested: m,
|
||||
error: (reply || "Modell hat nicht geantwortet").slice(0, 400),
|
||||
tookMs,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
clientWs.send(JSON.stringify({
|
||||
type: "model_test_result",
|
||||
requested: m,
|
||||
actual: d.aria_model,
|
||||
contextWindow: d.aria_context_window || 0,
|
||||
maxOutput: d.aria_max_output || 0,
|
||||
reply: reply.slice(0, 120),
|
||||
tookMs,
|
||||
}));
|
||||
log("info", "server", `Model-Test: ${m} -> ${d.aria_model} (${tookMs}ms)`);
|
||||
} catch (err) {
|
||||
const msg = err && err.name === "TimeoutError"
|
||||
? "Zeitüberschreitung (3 min) — Proxy oder CLI antwortet nicht"
|
||||
: String((err && err.message) || err);
|
||||
clientWs.send(JSON.stringify({
|
||||
type: "model_test_result", requested: m, error: msg,
|
||||
tookMs: Date.now() - started,
|
||||
}));
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
// ── Einstellungen: Groessen-Limit fuer Memory-Anhaenge ──────────────
|
||||
// Steht in runtime.json als `memoryAttachmentMaxMb`. Das Brain liest es bei
|
||||
// JEDEM Upload neu (memory_attachments.max_bytes()) — eine Aenderung greift
|
||||
|
||||
Reference in New Issue
Block a user