Studio: head a decision score with its most likely level (#13111)
Some checks are pending
CodeQL / Languages to analyse (push) Waiting to run
CodeQL / Analyze () (push) Blocked by required conditions
Core / Core zoo (HF=4.57.6 + TRL<1) (push) Blocked by required conditions
Core / Core zoo (HF=latest + TRL=latest) (push) Blocked by required conditions
Core / llama.cpp build + smoke (push) Waiting to run
Kaggle T4 Notebook CI / gate (push) Waiting to run
Kaggle T4 Notebook CI / T4 smoke (push) Blocked by required conditions
Local Agent Guides CI / connection (opencode, stable) (push) Waiting to run
Core / Resolve the unsloth_zoo revision (push) Waiting to run
Core / Core (HF=default + TRL=default) (push) Blocked by required conditions
Core / Core (HF=4.57.6 + TRL<1) (push) Blocked by required conditions
Core / Core (HF=latest + TRL=latest) (push) Blocked by required conditions
Core / Core zoo (HF=default + TRL=default) (push) Blocked by required conditions
Kaggle T4 Unsloth GPU CI / gate (push) Waiting to run
Kaggle T4 Unsloth GPU CI / Unsloth GPU smoke (push) Blocked by required conditions
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Waiting to run
Local Agent Guides CI / connection (claude, stable) (push) Waiting to run
Local Agent Guides CI / connection (codex, stable) (push) Waiting to run
Local Agent Guides CI / connection (dsh, stable) (push) Waiting to run
Local Agent Guides CI / connection (hermes, stable) (push) Waiting to run
Local Agent Guides CI / connection (openclaw, stable) (push) Waiting to run
Local Agent Guides CI / connection (pi, stable) (push) Waiting to run
Local Agent Guides CI / connection (vibe, stable) (push) Waiting to run
Local Agent Guides CI / file-edit (claude) (push) Waiting to run
Local Agent Guides CI / file-edit (codex) (push) Waiting to run
Local Agent Guides CI / file-edit (dsh) (push) Waiting to run
Local Agent Guides CI / file-edit (hermes) (push) Waiting to run
Local Agent Guides CI / file-edit (openclaw) (push) Waiting to run
Local Agent Guides CI / file-edit (opencode) (push) Waiting to run
Local Agent Guides CI / file-edit (pi) (push) Waiting to run
Local Agent Guides CI / resume (claude) (push) Waiting to run
Local Agent Guides CI / resume (codex) (push) Waiting to run
Local Agent Guides CI / connection (opencode, v2) (push) Waiting to run
Local Agent Guides CI / file-edit (vibe) (push) Waiting to run
Local Agent Guides CI / resume (opencode) (push) Waiting to run
Local Agent Guides CI / resume (pi) (push) Waiting to run
Local Agent Guides CI / prompt-cache (gemma-3-270m) (push) Waiting to run
Unsloth API CI / Unsloth API & Auth Tests (push) Waiting to run
Backend CI / (Python 3.13, l-r) (push) Waiting to run
Frontend CI / Frontend build + bundle sanity (push) Waiting to run
Security audit / advisory audit (pip + npm + cargo) (push) Waiting to run
Security audit / pip scan-packages :: audio-torch280 (push) Waiting to run
Security audit / pip scan-packages :: audio-torch290 (push) Waiting to run
Security audit / pip scan-packages :: extras (push) Waiting to run
Security audit / pip scan-packages :: studio (push) Waiting to run
Security audit / pip scan-packages :: hf-stack (push) Waiting to run
Security audit / npm scan-packages (Unsloth frontend tarballs) (push) Waiting to run
Security audit / npm provenance + new install-script diff (push) Waiting to run
Backend CI / (Python 3.11, floor) (push) Waiting to run
Backend CI / (Python 3.13, a-k) (push) Waiting to run
Backend CI / (Python 3.13, rest) (push) Waiting to run
Backend CI / Repo tests (CPU, rest) (push) Waiting to run
Backend CI / Repo tests (CPU, studio) (push) Waiting to run
Backend CI / Repo tests (CPU, studio-install) (push) Waiting to run
Backend CI / Shell installer tests (push) Waiting to run
Unsloth GGUF CI / GGUF inference smoke (API, tools, vision) (push) Waiting to run
Frontend CI / Frontend unit tests (Windows) (push) Waiting to run
Unsloth Tauri CI / Rust unit tests (macos) (push) Waiting to run
Unsloth UI CI / Chat UI Tests (extra) (push) Waiting to run
Windows Unsloth API CI / Unsloth API & Auth Tests (push) Waiting to run
Mac Unsloth UI + API + Update + Inference CI / Chat UI, API, Update and GGUF inference (push) Waiting to run
Unsloth Tauri CI / Tauri Linux debug build (no codesign) (push) Waiting to run
Unsloth Tauri CI / Rust unit tests (windows) (push) Waiting to run
Unsloth UI CI / Chat UI Tests (chat) (push) Waiting to run
Unsloth UI CI / Loaded-models indicator (cross-browser) (push) Waiting to run
Studio VAE seam guard / seam-guard (push) Waiting to run
Windows Unsloth GGUF CI / GGUF inference smoke (API, tools, vision) (push) Waiting to run
Windows Unsloth GGUF CI / real-VS detection (VS 2022) (push) Waiting to run
Windows Unsloth GGUF CI / real-VS detection + VC++ round-trip (VS 2026) (push) Waiting to run
Windows Unsloth GGUF CI / setup.ps1 units, no-VS resolve, VC++ round-trip (windows-latest) (push) Waiting to run
Windows Unsloth UI CI / Chat UI Tests (push) Waiting to run
Windows Unsloth Update CI / Unsloth Updating Tests (push) Waiting to run
studiobench CI / Selftests, lint and the single-file build (push) Waiting to run
studiobench CI / Real-path session, every action ran (push) Waiting to run
Windows Unsloth GGUF CI / Unsloth install + inference without Visual Studio (push) Waiting to run
Wheel CI / Wheel build + content sanity + import smoke (push) Waiting to run
Workflow trigger lint / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Waiting to run
Workflow trigger lint / security suites (torch-dependent) (push) Waiting to run

* Studio: head a decision score with its most likely level

The Try a decision dialog named the level nearest the expected score, so a
split answer (83% on 0, 17% on 3, expectation 0.50) was headed with level 1,
which the model gave 0.2%. Use the most likely level, falling back to the
nearest when no probabilities came back.

* test(studio): pin a decision score's top level away from the first level and on ties

The split-score case expected level 0, which an implementation returning the first level whenever probabilities exist also satisfies, and nothing covered a tie. Add a mirrored split that must head with level 3 and a flat distribution that must keep the nearest level. Also shorten the comment in scoreLevels and wrap the reduce the way the formatter does.

---------

Co-authored-by: Lyxot <longyixing331@gmail.com>
This commit is contained in:
Leo Borcherding 2026-10-09 04:58:55 -05:00 • committed by GitHub
parent 9d0f0e1f48
commit 8e3fd25af0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 36 additions and 8 deletions

View file

@ -601,11 +601,11 @@ function AnswerPreview({
</div>
);
}
const { keys, nearest, max } = scoreLevels(answer);
const { keys, top, max } = scoreLevels(answer);
return (
<div className="flex flex-col gap-6">
<AnswerHeading
answer={criterionText(answer.legend[nearest]) ?? nearest}
answer={criterionText(answer.legend[top]) ?? top}
detail={t("decisions.scoreDetail", {
score: answer.score.toFixed(2),
max,
@ -618,7 +618,7 @@ function AnswerPreview({
index={key}
label={criterionText(answer.legend[key]) ?? key}
value={answer.probabilities[key] ?? 0}
top={key === nearest}
top={key === top}
percent={percent}
/>
))}

View file

@ -375,12 +375,18 @@ export function choiceCriterion(
export function scoreLevels(
answer: Extract<DecisionAnswer, { type: "score" }>,
): { keys: string[]; nearest: string; max: number } {
): { keys: string[]; top: string; max: number } {
const keys = Object.keys(answer.legend).sort((a, b) => Number(a) - Number(b));
const distance = (key: string) => Math.abs(Number(key) - answer.score);
const nearest = keys.reduce(
(best, key) => (distance(key) < distance(best) ? key : best),
keys[0] ?? "0",
);
return { keys, nearest, max: Number(keys.at(-1) ?? 0) };
// The score is an expectation, so a split answer's nearest level can be one the model barely picked.
const p = (key: string) => answer.probabilities[key] ?? -1;
const top = keys.reduce(
(best, key) => (p(key) > p(best) ? key : best),
nearest,
);
return { keys, top, max: Number(keys.at(-1) ?? 0) };
}

View file

@ -165,7 +165,7 @@ test("every type starts from its first preset and every preset is a valid reques
}
});
test("a score lands on the nearest level of its legend", () => {
test("a score with no probabilities lands on the nearest level of its legend", () => {
const answer = {
type: "score" as const,
score: 2.6,
@ -175,10 +175,32 @@ test("a score lands on the nearest level of its legend", () => {
};
assert.deepEqual(scoreLevels(answer), {
keys: ["0", "1", "2", "10"],
nearest: "2",
top: "2",
max: 10,
});
assert.equal(scoreLevels({ ...answer, score: -1 }).nearest, "0");
assert.equal(scoreLevels({ ...answer, score: -1 }).top, "0");
});
test("a split score heads with its most likely level, not the one nearest the expectation", () => {
// Measured in Unsloth Desktop 0.1.905: the 429 grading example came back like this.
const answer = {
type: "score" as const,
score: 0.5032,
confidence: 0.6559,
legend: { "0": "wrong", "1": "touches", "2": "partial", "3": "correct" },
probabilities: { "0": 0.8303, "1": 0.0021, "2": 0.0018, "3": 0.1658 },
};
assert.equal(scoreLevels(answer).top, "0");
const mirrored = { "0": 0.1658, "1": 0.0018, "2": 0.0021, "3": 0.8303 };
assert.equal(
scoreLevels({ ...answer, score: 2.4968, probabilities: mirrored }).top,
"3",
);
const flat = { "0": 0.25, "1": 0.25, "2": 0.25, "3": 0.25 };
assert.equal(
scoreLevels({ ...answer, score: 1.5, probabilities: flat }).top,
"1",
);
});
test("Try it in Settings > API opens the decision playground", () => {