Skip to content

Score test runs with the Langfuse judges an agency takes in use - #203

Draft
kentare wants to merge 6 commits into
feat/langfuse-deletesfrom
feat/langfuse-judges
Draft

kentare wants to merge 6 commits into
feat/langfuse-deletesfrom
feat/langfuse-judges

Conversation

@kentare

@kentare kentare commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #197.

What and why

Agencies that judge answers in Langfuse want those judges' scores on genki's test runs, as advice that never decides a publish (ADR 0040). Part of #98; judge cost (98b) and labels (98c) follow.

  • Each active experiment judge in a connection's Langfuse project is a judge building block. The agency takes it in use with a reason. A judge that genki cannot name or that would leave cases unscored (default model, a field genki does not send, sampling or a filter, a connection without answer text) is refused.
  • A run scored by such a judge goes to Langfuse with only the fields the judge maps. Scores are read back per case trace (experimentId returns nothing on 4.4.0), marked judge changed when the judge changed, and not applicable for holdout cases and missing fields. The judge's reason is kept only on the case, so retention purges it.
  • Langfuse scores stay out of the run's metrics and never weaken a gate. A gate on a Langfuse metric is refused on save in Kvalitetskrav and at publish.
  • Portal: judge blocks in Byggeklosser, a locked "Send til Langfuse" with a residency check before start (GET .../eval-runs/sending), and a separate advisory "Langfuse-dommere" part on a run, with per-case verdicts and a link to the experiment. The link needs Langfuse's "V4 Preview".
  • ADR 0040, the providers-langfuse module and OpenAPI are updated.

Not in this PR: judge cost, Langfuse labels, and naming a Langfuse judge directly in an API eval run.

:camera_with_flash: Screenshots

Byggeklosser: a Langfuse judge with its model, version, answer type, fields and what it gets access to
The judge block for svar_oppfyller_kriteriene-eksperimenter

Kjør testsettet: sending to Langfuse is ticked and locked, naming the judge
The run dialog with the locked Langfuse checkbox

Resultat: the Langfuse judge apart from genki's metrics, marked advisory
The run result with the Langfuse-dommere part

Per testtilfelle: the judge's verdict and reason
A case with the judge's Ja and its reason

How it was tested

  • npm run check: 471 files, 5133 tests pass.
  • 29 new tests, each seen failing first or with its code switched off (names below).
  • Once against a local Langfuse 4.4.0 with a real judge and made-up data: the run was scored, with a reason in Norwegian, and the criteria reached the judge.
  • In the portal, on a separate stack (API on 8090, portal on 5175, mock login as Kari Testansatt) with the local Langfuse: the judge block, taking it in use, the locked checkbox, a run of the Klarspråk test set scored by the real judge, and the refusal in Kvalitetskrav. This found that cases with only a reference answer were not judged (fixed).
New tests
  • "scores a run's cases with a Langfuse judge under the identity langfuse://@v+"
  • "keeps two agencies', or two connections', judges of the same name apart, scores and labels alike"
  • "takes only scores from the snapshotted rule, never a same-named score from another rule"
  • "marks scores judge changed when the rule, its mapping, the evaluator version or its model changed since the start"
  • "refuses a judge on the project's or organisation's default model"
  • "refuses a judge whose mapping reads a field genki does not send, such as tool calls"
  • "is not applicable on a case without the fields its mapping reads, on a connection without content, and on a holdout case"
  • "reads a boolean as 1 or 0, scales a number by its config's bounds, and keeps a category as it came"
  • "refuses a service definition with a gate on a Langfuse judge's metric, naming the rule"
  • "purges a judge's reason with the case, as its retention says"
  • "lists each experiment rule of a connection's project as a judge building block, and scores a run only with the ones the agency added"
  • "refuses a run whose adopted Langfuse judge is refused or gone when it starts, and never adopts a refused one"
  • "checks a gate's calibration only against the judges that scored its metric"
  • "leaves an advisory judge out of every gate, so a judge taken in use since the baseline never fails a publish"
  • "keeps an advisory judge's scores on its cases but out of the run's metrics"
  • "refuses a judge that judges only some items, by sampling or a filter, or that is not an LLM judge"
  • "refuses a run with a reason when the connection's Langfuse cannot be read as it starts"
  • "reads a criteria case's reference answer as its criteria when it names none, as genki's criteria judge does"
  • "sends a criteria case's reference answer as its criteria when it names none, as genki's criteria judge reads it"
  • "refuses a Langfuse judge on a connection that gets no answer text, when it is taken in use and when a run starts"
  • "shows that a Langfuse judge on a connection without answer text cannot be taken in use"
  • "says before a run starts that the Langfuse judges' connection gets no answer text, and does not start it"
  • "shows each active experiment rule of a connection's project as a judge block with its model, evaluator version, data type and mapped fields, and adds it only with Ta i bruk and a reason"
  • "shows why a Langfuse judge block is refused: the project's default model, or a field genki does not send"
  • "ticks and locks Send til Langfuse when the service uses a Langfuse judge, naming the judges"
  • "says before a run starts that the service's residency forbids sending to a Langfuse judge's connection, and does not start it" (portal and API)
  • "shows a Langfuse judge's scores apart from genki's metrics, marked advisory, with judge changed, not-applicable counts, missing scores and a link to the experiment"
  • "shows each Langfuse judge's verdict and reason per case"
  • "refuses a Langfuse judge's metric in Kvalitetskrav, naming the rule" (portal and API)
  • npm run check passes

Checklist

  • Every new rule has a test whose name states it
  • User-facing text is in the i18n catalogues, in bokmål and English, with no em-dashes
  • Vendor SDKs stay in packages/providers-*
  • ADRs, docs and docs/architecture/modules are updated if a contract, boundary or module changed
  • No secrets, personal data or local paths

Each active experiment judge in the project of a tenant's Langfuse
connection is a judge building block. Once taken in use with a reason,
it scores every run of a service the connection covers: the run is sent
to Langfuse with only the fields the judges map (question, answer and
the case's criteria), and the scores are read back per case trace, since
reading them by experimentId returns nothing on Langfuse 4.4.0.

A judge on the project's default model, or one that reads a field genki
does not send, is refused. Only scores from the snapshotted rule count;
they are marked judge changed when the rule, its mapping, the evaluator
version or its model changed, and a judge's reason stays only on the
case's task, so retention purges it with the case.

Langfuse judges are advisory: a gate on their metric is refused when it
is saved in Kvalitetskrav and at publish. The portal lists the judges
under Byggeklosser, ticks and locks Send til Langfuse when a service
uses one, refuses a run before it starts when residency forbids the
connection, and shows the judges' scores and per-case verdicts apart
from genki's metrics, with a link to the experiment.

ADR 0040 amended to match.
…its metric

A run scored by a Langfuse judge lists that judge in its provenance, and
a Langfuse judge has no accepted labels yet, so every judge gate on the
run turned advisory. A gate now checks only the judges that scored its
own metric, and keeps all of them when the run's judges cannot be told
apart.

Also test that a run refuses an adopted Langfuse judge that is refused
or gone when it starts, and that a refused one cannot be adopted, and
add the bokmål texts for those refusals.
…d leave cases unscored

An advisory evaluator's scores stay on their cases but out of the run's
metrics, so a Langfuse judge's mean no longer counts in a block's
score, the console's regressions or the test set table. Gates leave
advisory judges out when they compare runs, so a judge taken in use
since the baseline no longer fails a publish.

A rule with sampling below 1 or a filter, an evaluator that is not an
LLM judge, and a mapping into the question or the answer are refused.
A rule that is no longer active marks its scores judge changed. A
Langfuse that cannot be read when a run starts refuses the run with a
reason, and the refusal reads in bokmål. The building block's judge
field is in OpenAPI.
@kentare

kentare commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Ready for review. A blind review and a claim-checking review are done and CI passes; Copilot did not review (no quota). Open: judge cost and Langfuse labels come in 98b and 98c; the experiment link works only for Langfuse users with "V4 Preview" on; Langfuse also judges cases genki marks not applicable, which costs a call each (98b); naming a Langfuse judge directly in an API eval run is not built; no screenshots yet.

Langfuse bills a judge call for every case it receives, so a judge on a
connection without content cost money and scored every case not
applicable. Its building block is now refused with the reason, and a
run whose connection stopped receiving text after the judge was taken
in use is refused before it starts, as the portal says in the dialog.
…criteria

A browser check showed six of seven cases of a seeded test set as not
applicable: their criteria are their reference answers, which genki's
criteria judge reads but the run did not send. The evaluation context
and the judge's empty-field check now read a case's criteria as the
criteria judge does (judgedExpectation).

A Langfuse judge's building block also names its source, says what it
does, and lists where the runs go, what it reads and the model it runs
on, instead of an empty access table.
… in the ADRs

A criteria case without named criteria sends its reference answer as
criteria, as genki's criteria judge reads it; nothing tested what the
run sends, and ADR 0040 and 0011 named only expected.criteria. The
no-content refusal names its connection in a field of its own.
@kentare

kentare commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Update: the three commits after the last ready comment (7df1d59, 575d837 and the judge block's source and access rows) have now had a claim-checking review. It found two gaps, fixed in 8133242: a test that a reference answer reaches Langfuse as criteria, and ADR 0040 and 0011 saying so. CI passes on the head. Still open as before: cost and labels come in 98b (#207) and 98c; the experiment link needs Langfuse's "V4 Preview"; naming a Langfuse judge directly in an API eval run is not built.

This branch has not been deployed

No deployments
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