fix(benchmark): fair to reasoning models, fair to router degradation, real sub-cent costs
Three false signals in benchmark results, all seen on a live run:
- Reasoning models were scored ❌ because the per-test max_tokens cap cut
them mid-<think> before the actual answer. Every run now gets +256 tokens
of headroom; non-thinking models stop at EOS as before, thinking ones get
to finish (math-mult on nanbeige: false 0/1 → honest 1/1).
- When the router degraded (timeout → fallback chain) the table showed
"✗ degraded" and counted it against classification accuracy. There was no
classification to judge — such rows now show a muted "⚠ router degraded"
with an explanatory tooltip and are excluded from the accuracy KPI.
- Costs below $0.0001 (cheap flash models) collapsed to "$0.0000". money
formatting now grows precision until two significant digits survive
($0.000035 instead of $0.0000).
2 new strings in all 7 languages.
This commit is contained in:
Vendored
+2
@@ -616,4 +616,6 @@ export default {
|
||||
"keys.h.st.installed": "Auf der Gateway-Maschine installiert",
|
||||
"keys.h.st.configured": "Bereits für dieses Gateway konfiguriert",
|
||||
"keys.h.st.notFound": "Auf der Gateway-Maschine nicht gefunden",
|
||||
"bm.degraded": "Router degradiert",
|
||||
"bm.routerDegraded": "Der Router hat nicht rechtzeitig geantwortet — die Anfrage nutzte die Fallback-Kette; die Klassifikation wird nicht gewertet.",
|
||||
};
|
||||
|
||||
Vendored
+2
@@ -609,4 +609,6 @@ export default {
|
||||
"keys.h.st.installed": "Installed on the gateway machine",
|
||||
"keys.h.st.configured": "Already configured for this gateway",
|
||||
"keys.h.st.notFound": "Not found on the gateway machine",
|
||||
"bm.degraded": "router degraded",
|
||||
"bm.routerDegraded": "The router did not answer in time — the request used the fallback chain; classification is not judged.",
|
||||
};
|
||||
|
||||
Vendored
+2
@@ -616,4 +616,6 @@ export default {
|
||||
"keys.h.st.installed": "Instalado en la máquina de la pasarela",
|
||||
"keys.h.st.configured": "Ya configurado para esta pasarela",
|
||||
"keys.h.st.notFound": "No encontrado en la máquina de la pasarela",
|
||||
"bm.degraded": "router degradado",
|
||||
"bm.routerDegraded": "El router no respondió a tiempo — la petición usó la cadena de reserva; la clasificación no se cuenta.",
|
||||
};
|
||||
|
||||
Vendored
+2
@@ -616,4 +616,6 @@ export default {
|
||||
"keys.h.st.installed": "Installé sur la machine de la passerelle",
|
||||
"keys.h.st.configured": "Déjà configuré pour cette passerelle",
|
||||
"keys.h.st.notFound": "Introuvable sur la machine de la passerelle",
|
||||
"bm.degraded": "routeur dégradé",
|
||||
"bm.routerDegraded": "Le routeur n'a pas répondu à temps — la requête a suivi la chaîne de secours ; la classification n'est pas comptée.",
|
||||
};
|
||||
|
||||
Vendored
+2
@@ -608,4 +608,6 @@ export default {
|
||||
"keys.h.st.installed": "Установлен на машине шлюза",
|
||||
"keys.h.st.configured": "Уже настроен на этот шлюз",
|
||||
"keys.h.st.notFound": "Не найден на машине шлюза",
|
||||
"bm.degraded": "роутер деградировал",
|
||||
"bm.routerDegraded": "Роутер не успел ответить — запрос ушёл по резервной цепочке; классификация не засчитывается.",
|
||||
};
|
||||
|
||||
Vendored
+2
@@ -616,4 +616,6 @@ export default {
|
||||
"keys.h.st.installed": "Ağ geçidi makinesinde kurulu",
|
||||
"keys.h.st.configured": "Bu ağ geçidi için zaten yapılandırılmış",
|
||||
"keys.h.st.notFound": "Ağ geçidi makinesinde bulunamadı",
|
||||
"bm.degraded": "yönlendirici yedeğe düştü",
|
||||
"bm.routerDegraded": "Yönlendirici zamanında yanıt vermedi — istek yedek zincire gitti; sınıflandırma sayılmaz.",
|
||||
};
|
||||
|
||||
Vendored
+2
@@ -616,4 +616,6 @@ export default {
|
||||
"keys.h.st.installed": "已安装在网关所在机器上",
|
||||
"keys.h.st.configured": "已配置为使用此网关",
|
||||
"keys.h.st.notFound": "网关所在机器上未找到",
|
||||
"bm.degraded": "路由器降级",
|
||||
"bm.routerDegraded": "路由器未及时响应 — 请求走了备用链路;分类不计入统计。",
|
||||
};
|
||||
|
||||
Vendored
+4
-1
@@ -100,13 +100,16 @@ export function confirmDialog(message) {
|
||||
export function moneyEl(n) {
|
||||
if (n == null) return document.createTextNode("—");
|
||||
if (n === 0) return document.createTextNode("$0");
|
||||
const s = n.toFixed(4);
|
||||
// sub-0.0001 costs (cheap flash models) would collapse to $0.0000 — grow
|
||||
// precision until two significant digits survive
|
||||
const s = Math.abs(n) >= 0.0001 ? n.toFixed(4) : n.toFixed(Math.min(8, 1 - Math.floor(Math.log10(Math.abs(n)))));
|
||||
return h("span", { class: "money" }, "$" + s.slice(0, -2), h("small", { class: "money-tail" }, s.slice(-2)));
|
||||
}
|
||||
|
||||
export function money(n) {
|
||||
if (n == null) return "—";
|
||||
if (n === 0) return "$0";
|
||||
if (Math.abs(n) > 0 && Math.abs(n) < 0.0001) return "$" + n.toFixed(Math.min(8, 1 - Math.floor(Math.log10(Math.abs(n)))));
|
||||
// up to 4 decimals, trailing zeros trimmed (but keep money-like 2 minimum)
|
||||
let s = n.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
||||
if (/\.\d$/.test(s)) s += "0";
|
||||
|
||||
Vendored
+16
-7
@@ -170,7 +170,9 @@ export async function renderBenchmark() {
|
||||
let text = "";
|
||||
try {
|
||||
const info = await testStream(
|
||||
{ model, messages: [{ role: "user", content: test.prompt }], temperature: 0, max_tokens: test.max || 128 },
|
||||
// +256: headroom for reasoning models — a tight cap cuts them
|
||||
// mid-<think> and turns a correct model into a false ❌
|
||||
{ model, messages: [{ role: "user", content: test.prompt }], temperature: 0, max_tokens: (test.max || 128) + 256 },
|
||||
(d) => (text += d)
|
||||
);
|
||||
const latency = performance.now() - started;
|
||||
@@ -183,12 +185,17 @@ export async function renderBenchmark() {
|
||||
if (info.ttft_ms != null) rec.ttft.push(info.ttft_ms);
|
||||
const row = { id: test.id, cat: test.cat, ...ev, latency: Math.round(latency), cost: info.cost_usd || 0, answer: stripThink(text).slice(0, 300) };
|
||||
if (mode === "routing") {
|
||||
// router timed out / fell back — there was no classification to
|
||||
// judge, so it must not count against accuracy
|
||||
row.degraded = !info.task_label
|
||||
|| info.task_label === "degraded"
|
||||
|| String(info.route_source || "").includes("degraded");
|
||||
row.task = info.task_label || "—";
|
||||
row.tier = info.tier || "—";
|
||||
row.chosen = info.selected_model || "—";
|
||||
row.source = info.route_source || "—";
|
||||
row.expected = test.label; // null = classification not judged
|
||||
row.taskOk = test.label ? test.label.includes(info.task_label) : null;
|
||||
row.taskOk = test.label && !row.degraded ? test.label.includes(info.task_label) : null;
|
||||
}
|
||||
rec.results.push(row);
|
||||
} catch (e) {
|
||||
@@ -311,11 +318,13 @@ export async function renderBenchmark() {
|
||||
h("tr", {},
|
||||
h("td", {}, h("div", {}, r.id), h("div", { class: "muted small" }, r.cat)),
|
||||
h("td", {},
|
||||
r.taskOk == null
|
||||
? h("span", { class: "muted" }, r.task || "—")
|
||||
: h("span", { class: r.taskOk ? "" : "err" },
|
||||
(r.taskOk ? "✓ " : "✗ ") + r.task,
|
||||
r.taskOk ? "" : h("div", { class: "muted small" }, t("bm.expectedLbl") + " " + (r.expected || []).join("/")))),
|
||||
r.degraded
|
||||
? h("span", { class: "muted", title: t("bm.routerDegraded") }, "⚠ " + t("bm.degraded"))
|
||||
: r.taskOk == null
|
||||
? h("span", { class: "muted" }, r.task || "—")
|
||||
: h("span", { class: r.taskOk ? "" : "err" },
|
||||
(r.taskOk ? "✓ " : "✗ ") + r.task,
|
||||
r.taskOk ? "" : h("div", { class: "muted small" }, t("bm.expectedLbl") + " " + (r.expected || []).join("/")))),
|
||||
h("td", {}, r.tier ? h("span", { class: "badge tier-" + r.tier }, r.tier) : "—"),
|
||||
h("td", {},
|
||||
h("span", { class: "mono small" }, r.chosen || "—"),
|
||||
|
||||
Reference in New Issue
Block a user