Skip to content

Compare LLM judges in the LLM Scorer, with support for TypeSafe's Jev - #461

Merged
ianarawjo merged 9 commits into
mainfrom
claude/jev-model-support-362f5a
Sep 19, 2026
Merged

ianarawjo merged 9 commits into
mainfrom
claude/jev-model-support-362f5a

Conversation

@ianarawjo

Copy link
Copy Markdown
Owner

Makes the LLM Scorer a way to compare LLM judges, and adds TypeSafe's Jev (a "decision" model on OpenRouter) as one of them. Also adds an example flow for the Jev launch.

What it does

  1. Several judges in one LLM Scorer. "Add judge +" adds a model; each response is scored once per judge, with scores keyed by judge name. Multi-Eval keeps one judge.

  2. Explicit answer formats. Categorical scorers list their categories (optionally with descriptions), and numeric ones their scale's levels. Judges are told exactly what to answer. Answers outside the format are kept as written and counted as invalid, instead of turning the whole run's scores into strings.

  3. Ground truth and a Judges tab. "Compare to" picks a column as the true label. A new Judges tab in the Inspector shows:

    • agreement with the label and between judges (descriptive, with n), with bars in each judge's color;
    • cost and speed per judge (total cost, median time per answer, tokens);
    • the responses where judges disagree;
    • for judges that give probabilities (Jev), how often they're right at each confidence.
  4. Jev support. Jev (~typesafe/jev-latest) is in the OpenRouter menu. As a judge it gets the response as its state, and one typed question built from the rubric and format:

    • true/false → yes/no
    • categorical → choice
    • numeric → scale

    It goes through OpenRouter's /api/alpha/decisions. Its probability for each answer is kept with the score. Formats Jev can't answer fail with a clear message before any request, and using it in a Prompt Node says to use it as a judge.

  5. Cost in response stats. Builds on Record each response's latency, token counts and speed #459: cost is read from OpenRouter's usage.cost, for any model it serves, and exposed as stat_cost_usd.

  6. Data straight into the scorer. Tabular Data, Text Fields, Items and Media nodes can connect directly to an LLM Scorer. A table row's other columns become variables.

  7. Smaller scorer improvements:

    • The node is compact: format and options share one row.
    • Run's tooltip says what a run will send ("Will load scores from cache", "Will send 120 requests per judge").
    • Right-click → "Clear cached scores".
  8. Example flow: "🧑‍⚖️ Compare LLM judges, with Jev" (jev-judges.cforge), run live against human labels:

    Triage 120 bank questions (Banking77) Flag scams in 100 texts (SMS Spam Collection, paraphrased first)
    Jev 99%, $0.0025 98%, $0.00135, 253 ms median
    Claude Sonnet 5 98%, $0.08 98%, $0.03, 1.8 s
    GPT-5.4 Mini 93%, $0.02 98%, $0.009, 823 ms

    Both datasets are CC BY 4.0 and credited in the flow. Samples were hand-picked to leave out crude or sensitive items.

Fixes found along the way

  • queryLLM's cache index dropped earlier runs' cache files. An inner variable shadowed the one it saved, so changing a model's settings and changing them back re-queried everything. This affected Prompt Nodes too.
  • Table View lost scores for responses that shared the same vars (e.g. from a Split Node). Each response now gets its own row when scores have their own columns.
  • Categorical answers with "Reason before scoring" are now read from the SCORE: line. Before, only numbers were extracted.

Behavior changes for existing users

  • Binary and numeric scores are read one at a time, not all-or-nothing. A single stray answer no longer turns every score into a string. Stray answers are kept as written and flagged.
  • The Inspector modal's trigger() can open on a named tab. Other nodes' Inspectors are unchanged; the Judges tab only appears for LLM Scorers.

Testing

  • jest: 1,219 passing, including new tests for:
    • answer parsing and formats
    • multiple judges
    • agreement, disagreements and reliability
    • Jev requests and answers (mocked)
    • caching across rubric changes
    • data inputs
    • cost formatting
  • pytest: response-stats tests pass (with_stats_metavars gained cost_usd).
  • tsc, eslint and prettier are clean on changed files.
  • Live, in the dev server, against OpenRouter:
    • Jev alone and beside GPT-5.4 Mini and Claude Sonnet 5
    • the decisions endpoint's reply format
    • cost reporting for both Jev and chat models
    • the example flow's runs

Not verified

  • Multi-Eval in the browser. It gets the categories and scale fields, but only its prop wiring changed.
  • A production build, and the hosted web build's Example Flows (it needs the usual deploy step to include the new file).
  • YAML export still writes only the first judge.
  • Jev's endpoint is in alpha (/api/alpha/decisions), so its path may change. It's one constant in utils.ts.

🤖 Generated with Claude Code

ianarawjo and others added 9 commits September 18, 2026 18:34
…ment

- Categorical scorers can list their categories (with optional descriptions)
  and numeric scorers their scale levels. Judges are told exactly what to
  answer, and answers outside those bounds are kept as written and counted
  as invalid, instead of turning the whole run's scores into strings.
- An LLM Scorer can have several judges. Each response is scored once per
  judge, with scores keyed by judge name (Multi-Eval keeps one judge).
- "Compare to" picks an input column as the ground-truth label. A new
  "Judges" tab in the Inspector shows each judge's agreement with the label
  and with the other judges (descriptive only, with n), the responses where
  they disagree, and the answers that didn't fit the format.
- The node is compact: format and options share one row, with categories,
  scale and "Reason before scoring" in a popover.
- Table View: long scores wrap in score columns instead of being cut off.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Jev is a "decision" model: it answers typed questions about a text rather
than writing text. OpenRouter serves it at /api/alpha/decisions.

- Jev (~typesafe/jev-latest) is in the OpenRouter menu, and any
  typesafe/ model ID is recognized as a decision model.
- As a judge, Jev gets the response as its state and one question built
  from the scorer: the rubric as instructions, and the format as the type
  (true/false -> noul, categorical -> choice with the categories and their
  descriptions, numeric -> score with the scale's levels). Its score is
  shifted from 0-based levels to the scorer's 1-to-N scale.
- Jev and LLM judges can score side by side; LLM judges still get the full
  grader prompt. Formats Jev can't answer (open-ended, too few categories,
  a scale outside 2-10 levels, an empty rubric) fail with a clear message
  before any request. In a Prompt node, Jev says to use it as a judge.
- Fix: queryLLM's cache index dropped the cache files of earlier runs (an
  inner variable shadowed the one it saved), so changing a model's settings
  and changing them back re-queried everything. Jev hit this on every
  rubric edit, since its question lives in its settings.

Checked live against OpenRouter: 36 decisions, all 200s, in the expected
shape, at about $0.00002 each.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Hovering the Run button says what a run will do, as the Prompt Node's
  does: e.g. "Will load scores from cache", "Will send 36 requests to Jev
  and load others from cache". If the scorer can't run as set up (e.g. Jev
  with an open-ended format), it says why instead.
- Right-clicking an LLM Scorer offers "Clear cached scores" first, which
  clears its saved scores and every judge's cached answers.
- The counting uses the same judge setup and inputs as a run, now shared
  helpers in backend.ts, so the tooltip can't disagree with the run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Response stats record cost where the provider reports it (OpenRouter's
  usage.cost, for any model it serves), also as the stat_cost_usd metavar.
- The scorer totals each judge's stats over a run: answers, cost, median
  time per answer, and tokens. The Judges tab shows them in a table, with
  bars in each judge's model color in the cost and time cells.
- Jev's probability for each answer is kept with its score (eval_res.probs),
  shown beside its answers in the disagreements, and, with a label set, in
  a table of how often Jev is right at each confidence it states.
- The Run button's tooltip is cleared when a run starts, so it isn't stale.

Checked live: Jev and GPT-5.4 Nano both scored 36 responses at 100%
agreement with the label; OpenRouter reported both judges' costs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Repeating the label as its description cost about 10 input tokens per
category and told Jev nothing new. Measured live on 36 responses with 9
categories: input tokens fell from 508 to 419 per call (18%), with the same
100% agreement with the ground-truth label and the same confidence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each judge's agreement with the ground-truth label gets a bar in its
model's color, scaled to 100% (or, for numeric scores, to the largest mean
difference), matching the cost and speed table. Judge colors are now worked
out once for the whole tab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… example

- Data nodes (Tabular Data, Text Fields, Items, Media) can connect straight
  to an LLM Scorer: each value is a response to score, and a table row's
  other columns become its variables, e.g. a ground-truth label. Values keep
  stable ids, so their scores stay cached; "Clear cached scores" clears them.
- Table View: with scores in their own columns, each response gets its own
  row. Responses with the same vars (e.g. from a Split Node) shared a row,
  and all but the first lost their scores.
- "Compare to" shows a saved metavariable by name, e.g. "scam (metavariable)".
- New example flow, jev-judges: TypeSafe's Jev, GPT-5.4 Mini and Claude
  Sonnet 5 judge two tasks against human labels, run live:
  - Triage 120 bank support questions into 8 card topics (Banking77, CC BY
    4.0): Jev 99%, Sonnet 98%, Mini 93%; Jev $0.0025 vs. Sonnet $0.08.
  - Flag scams in 100 text messages (SMS Spam Collection, CC BY 4.0), each
    lightly paraphrased first in case judges memorised the public dataset:
    all three 98%; Jev $0.00135 vs. Sonnet $0.03, 253 ms vs. 1.8 s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…SON answers

- Jev reads text only. Asked to score an image (e.g. from a Media Node, now
  a scorer input), it answered about an empty string; it now fails with a
  message saying to use a judge that takes images, before any request.
- Keeping earlier runs' cache files listed (so switching settings back
  reuses responses) also put them in exports, e.g. responses of models since
  removed. The index lists them apart, as stale_cache_files: lookups and the
  Run tooltip's count still use them, clearing removes them, and exports
  leave them out.
- Only decision judges' answers are read as {"answer", "p"} JSON. A text
  judge that happened to answer in JSON had its answer cut down and a
  made-up probability shown in the Judges tab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A long description pushed the button past the bottom of its fixed-height
card, out of view. The description now takes the space between the title and
the button and scrolls when it doesn't fit; while there's more below, its
bottom edge fades out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ianarawjo
ianarawjo merged commit 0eb00bc into main Sep 19, 2026
21 checks passed
@ianarawjo
ianarawjo deleted the claude/jev-model-support-362f5a branch September 19, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant