Zum Inhalt springen

Anleitungen

Tutorial: Textgenerierung mit einem Rubrik-Judge

Evaluieren Sie eine Funktion, die freien Text schreibt, hier einen Ticket-Zusammenfasser, mit Formatprüfungen und einem Rubrik-Judge; messen Sie diesen Judge an den Labels einer Person, bevor er irgendetwas entscheiden darf; vergleichen Sie dann eine echte Änderung. Der Judge läuft auf diesem Rechner ohne Modell und ohne Netzwerk, und ein optionaler Schritt setzt ein echtes Modell ein.

Was Sie bauen

Einen Zusammenfasser, der aus einem Support-Ticket ein oder zwei Sätze macht. "Gut" ist ein Urteil, kein String-Vergleich, daher entscheidet ein LLM-Judge mit einer Rubrik über den Aufgabenerfolg: Nennt die Zusammenfassung die Fakten, die ein Support-Mitarbeiter braucht? Zwei deterministische Evaluatoren prüfen das Format, wofür keine Referenz nötig ist. Begriffe wie Fall, Lauf, Metrik, Judge und Gate sind in Kernkonzepte definiert.

Dieselbe Form passt auf Extraktion oder jede andere Generierung: Eine Funktion gibt Text in einem Dictionary zurück, die Referenz sagt, was eine gute Antwort enthalten muss, und eine Rubrik sagt, wie entschieden wird.

Voraussetzungen

  • Python 3.11 oder neuer und Oloproof in einer virtuellen Umgebung:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof
  • Das Beispielprojekt, das mit dem Paket ausgeliefert wird. Kopieren Sie es in ein neues Verzeichnis und arbeiten Sie dort:
oloproof init --example generation ticket-summaries
cd ticket-summaries
  • Port 8799 frei für den Ersatz-Judge (andernfalls ändern Sie ihn an beiden Stellen).

Jeder Schritt bis "Optional: ein echtes Modell als Judge" ist offline und deterministisch: kein API-Schlüssel, kein Provider-Konto, keine Kosten.

Die Dateien

ticket-summaries/
  app.py                        the summariser under test (baseline)
  app_v2.py                     the candidate change
  judge_server.py               a stand-in judge speaking the OpenAI API on 127.0.0.1
  rubrics/covers_facts.md       the judge's rubric
  oloproof.yaml                 the suite
  release.yaml                  rules for a run
  compare.yaml                  a rule for a comparison
  data/tickets.jsonl            20 cases
  labels/reviewer_verdicts.csv  one person's verdicts on the baseline's summaries
  fill_labels.py                copies those verdicts into a labelling sheet

Führen Sie jeden Befehl in ticket-summaries/ aus.

Der Ersatz-Judge, und was er nicht ist

Ein Rubrik-Judge ist ein Evaluator, der einen Prompt (die Rubrik, die Eingabe des Falls, sein expected und die Ausgabe) an ein Modell schickt und {"pass": true|false, "rationale": "..."} zurückliest. Oloproof spricht mit jedem Server, der die Chat-API von OpenAI spricht, und ein Server auf localhost braucht keinen Schlüssel.

judge_server.py ist ein solcher Server, aber kein Modell. Er lässt eine Zusammenfassung nur dann bestehen, wenn sie jede Phrase unter must_mention im expected des Falls enthält, ohne Beachtung der Groß- und Kleinschreibung. Das ist eine feste Regel, sodass das Tutorial auf jedem Rechner dieselben Zahlen liefert. Eine erfundene Tatsache kann er nicht bemerken, was von einem echten Modell-Judge verlangt wird. Starten Sie ihn in einem zweiten Terminal und lassen Sie ihn laufen:

python judge_server.py --port 8799
stand-in judge on http://127.0.0.1:8799/v1

Die Anwendung und ihr Adapter

# app.py
@system(name="ticket-summariser", version="first-sentence")
def summarise(case: dict[str, Any]) -> dict[str, str]:
    return {"summary": sentences(str(case["ticket"]))[0]}

Der Adapter für eine Python-Anwendung ist die Funktion: Sie erhält den input des Falls und gibt ein Dictionary zurück. Für Ihren eigenen Generator rufen Sie darin Ihr Modell oder Ihre Kette auf und geben den Text unter einem Schlüssel zurück. Oloproof ruft sie einmal pro Fall auf und cacht die Ausgabe anhand des Quellcodes der Funktion und der deklarierten version; Ihren Modell-Client, Ihre Prompts oder Ihren Zustand verwaltet es nicht. Führen Sie Dateien, die die Funktion liest, etwa eine Prompt-Vorlage, unter system.code_paths auf.

Der Datensatz

{"id":"t01","input":{"ticket":"Hello. Order 1042 arrived with a cracked screen. I would like a replacement, not a refund."},"expected":{"must_mention":["1042","cracked","replacement"]}}
{"id":"t06","input":{"ticket":"Please cancel my subscription at the end of this month. I am moving abroad."},"expected":{"must_mention":["cancel","end of this month"]}}

input ist das, was die Funktion erhält. expected ist die Referenz, die der Judge liest: hier eine Liste von Fakten, die die Zusammenfassung enthalten muss, keine vollständige Referenzzusammenfassung, weil viele verschiedene Zusammenfassungen richtig sind. Die Ausgabe für t01 ist {"summary": "Hello."}.

Die Evaluatoren wählen

version: 1
project: ticket-summaries
dataset: data/tickets.jsonl
system:
  name: ticket-summariser
  version: first-sentence
  callable: app:summarise
  timeout_s: 30
evaluators:
  - type: json_schema
    criterion: format_valid
    field: null
    schema:
      type: object
      required: [summary]
      properties:
        summary: {type: string, minLength: 1}
      additionalProperties: false
  - type: regex
    criterion: short_enough
    field: summary
    pattern: '^.{1,160}$'
    pass_if: match
  - type: rubric_judge
    criterion: covers_facts
    provider: openai_compatible
    model: stand-in-judge
    base_url: http://127.0.0.1:8799/v1
    rubric_file: rubrics/covers_facts.md
KriteriumEvaluatorBraucht expectedMisst
format_validjson_schemaneinFormat: ein nicht leeres String-Feld
short_enoughregexneinFormat: höchstens 160 Zeichen
covers_factsrubric_judgejaAufgabenerfolg, wie die Rubrik ihn definiert

Hello. besteht beide Formatprüfungen. Nur der Judge sagt, dass es eine nutzlose Zusammenfassung ist. Ein Judge kann auch ohne Referenz laufen: Eine Rubrik wie "PASS, wenn die Zusammenfassung keine Begrüßung enthält" liest nur Eingabe und Ausgabe, und ein Fall ohne expected wird trotzdem beurteilt. Was er dann nicht kann, ist Fakten gegen eine Antwort prüfen, der Sie vertrauen.

Die Rubrik:

PASS when the summary states every fact listed under must_mention in the expected answer, in
words a support agent would recognise, and adds nothing the ticket does not say.
FAIL when any listed fact is missing, changed or contradicted.

Die Policy

version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
require_validated_evaluators: true
rules:
  - id: valid-format
    metric: format_valid
    kind: observed_count
    max_failures: 0
  - id: short-enough
    metric: short_enough
    kind: observed_count
    max_failures: 0
  - id: covers-facts-floor
    metric: covers_facts
    min: 0.60

require_validated_evaluators: true ist die Voreinstellung der Engine, hier ausgeschrieben, weil es der Kern dieses Tutorials ist: Ein Judge, den niemand mit Menschen verglichen hat, darf keine Regel entscheiden.

Ausführen

oloproof run
Run run_01M4... [DECIDED/COMPLETE]
Gate: BLOCK (exit 3)
│ valid-format       │ format_valid │ PASS                  │ observed_failures_within_limit │
│ short-enough       │ short_enough │ PASS                  │ observed_failures_within_limit │
│ covers-facts-floor │ covers_facts │ INSUFFICIENT_EVIDENCE │ evaluator_not_validated        │
covers-facts-floor: the judge (or model or custom evaluator) behind this rule has not been measured against
people yet, so it may not decide.
  Label a sample:  oloproof review run_01M4... --criterion covers_facts --by YOU --sample 20
  Then measure it: oloproof evaluators validate EVALUATOR_ID --by YOU (ids: oloproof evaluators list)
│ format_valid │ 100.0%   │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
│ short_enough │ 100.0%   │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
│ covers_facts │ 45.0%    │ [23.0%, 68.5%]  │ 9 / 20 observed · 0 missing · 0 excluded  │
Cache: execution 0 hit/20 miss; judgment 0 hit/60 miss

Die Formatregeln bestehen. Der Judge hat 9 von 20 Zusammenfassungen bestehen lassen, aber die Regel ist INSUFFICIENT_EVIDENCE mit dem Grund evaluator_not_validated, und das Gate blockiert mit Exit 3. Die Regel hat nicht anhand der 45% entschieden: Die Fehlerrate eines Judges ist unbekannt, bis sie gemessen wird, also trüge ein auf seinen Urteilen gebautes Intervall einen unausgesprochenen Fehler. Die Engine meldet dies als INSUFFICIENT_EVIDENCE, nicht als MANUAL_REVIEW oder FAIL: Die Evidenz für eine Entscheidung fehlt, und die Ausgabe nennt die zwei Befehle, die sie liefern.

Die Fehlschläge untersuchen

oloproof inspect RUN_ID --failures
11 of 20 cases failed, errored or did not finish

t01
  output: {"summary": "Hello."}
  covers_facts: failed
    judge text, not verified: missing: 1042, cracked, replacement

t02
  output: {"summary": "I was charged twice for order 2210."}
  covers_facts: failed
    judge text, not verified: missing: 49
...

Die Begründung des Judges wird als "judge text, not verified" angezeigt: Sie ist die Erklärung des Modells, keine Evidenz. Das Muster ist trotzdem klar: Der erste Satz ist oft eine Begrüßung.

Den Judge an einer Person messen

Die Validierung vergleicht die Urteile des Judges mit denen einer Person auf denselben Antworten. Ziehen Sie eine Zufallsstichprobe der Fälle des Laufs in eine Tabelle. Die Urteile des Judges sind darin weggelassen, damit die labelnde Person nicht von ihnen beeinflusst wird:

oloproof labels export RUN_ID --criterion covers_facts --sample 20 --local --out sample.csv
Wrote 20 cases to sample.csv, drawn at random with seed 2701013296, without the judge's verdict.
  This is a local sample, good-faith only, because it was drawn on this machine.
Fill in `passed` (pass or fail) and `labelled_by` on each row you judge, then run `oloproof labels import sample.csv`.

--local zieht auf diesem Rechner, ohne einen gehosteten Workspace zu fragen; die Engine wählt den Seed. Bei 20 Fällen ist eine Stichprobe von 20 alle Fälle. In der Praxis liest eine Person das Ticket und die Zusammenfassung jeder Zeile und füllt passed aus. Für dieses Tutorial enthält labels/reviewer_verdicts.csv Urteile, die eine Reviewerin zu den Zusammenfassungen der Baseline abgegeben hat, und fill_labels.py kopiert sie in die Tabelle:

python fill_labels.py sample.csv
oloproof labels import sample.csv
filled 20 rows of sample.csv
Recorded 20 labels from sample.csv (20 measurement).

Die Reviewerin war einmal anderer Meinung als der Judge: Bei t02 ("I was charged twice for order 2210.") hielt sie den fehlenden Betrag für unerheblich und ließ die Zusammenfassung bestehen. Labels benennen die genaue Antwort, die sie beurteilt haben, daher gelten diese Urteile nur für den Lauf der Baseline.

Ermitteln Sie die Versions-ID des Judges und validieren Sie ihn:

oloproof evaluators list
oloproof evaluators validate EVALUATOR_ID --by alice
covers_facts  LLM_JUDGE  UNVALIDATED  (declared)  sha256:a662...

covers_facts: sha256:a662... is now VALIDATED
  agreement 95.0% [75.1%, 99.9%] · 19 of 20 labelled cases agreed · 0 labelled but not judged · kappa 0.900
  bias -5.0 points [-32.4, +20.7] · the judge's pass rate minus the people's · 20 cases · 0 labelled but not judged
  passes what people pass 90.0% [55.4%, 99.8%] · the judge passed 9 of 10 cases people passed · 0 labelled but not judged
  fails what people fail 100.0% [69.1%, 100.0%] · the judge failed 10 of 10 cases people failed · 0 labelled but not judged

Lesen Sie die Intervalle, nicht die 95%: 20 Labels zeigen eine Übereinstimmung von mindestens 75,1%. Eine Policy kann mit minimum_evaluator_agreement mehr verlangen, was mit dieser unteren Grenze verglichen wird, und validate lehnt einen Judge darunter ab. Die Anleitung Judges behandelt die Messlatte, Bias, Probes und oloproof review für das Labeln im Terminal.

Entscheiden Sie den gespeicherten Lauf nun neu, ohne den Zusammenfasser oder den Judge aufzurufen:

oloproof gate RUN_ID --policy release.yaml
valid-format: PASS (observed_failures_within_limit)
short-enough: PASS (observed_failures_within_limit)
covers-facts-floor: INSUFFICIENT_EVIDENCE (interval_overlaps_threshold)
  no sample size would make this PASS: the observed rate (0.500) is itself below the threshold (0.600), so more cases would move it toward FAIL
Gate: BLOCK (exit 3)

Der Judge darf jetzt entscheiden, und die Entscheidung betrifft den Zusammenfasser: Die genannte Rate, 0,500, sind nicht die 45% des Judges. Weil dieser Lauf eine blinde Zufallsstichprobe von Mess-Labels hat, liest das Gate den durch diese Labels korrigierten Judge ("Judge-corrected gates" in der Anleitung Judges). Die Korrektur ist PPI, Prediction-Powered Inference: Sie nutzt die gelabelte Stichprobe, um zu messen, wie weit die Rate des Judges von der der Menschen entfernt liegt, und verschiebt die Schätzung und verbreitert das Intervall um genau so viel. Darauf beziehen sich auch die Hinweise des Exports zu PPI. So oder so erfüllt die Baseline die Untergrenze nicht, und mehr Fälle würden daran nichts ändern.

Eine echte Änderung vornehmen

app_v2.py überspringt kurze Höflichkeitsfloskeln und behält die nächsten zwei Sätze. Kopieren Sie es über app.py, setzen Sie version: skip-pleasantries unter system in oloproof.yaml, lassen Sie den Judge laufen, und:

oloproof run
Gate: ALLOW (exit 0)
│ covers-facts-floor │ covers_facts │ PASS  │ lower_bound_meets_minimum      │
│ covers_facts │ 100.0%   │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
Cache: execution 0 hit/20 miss; judgment 6 hit/54 miss

Der Judge ist dieselbe validierte Version, also entscheidet seine Regel direkt. Sechs Urteile kamen aus dem Cache, für Zusammenfassungen, die beide Versionen identisch geschrieben haben. Niemand hat diese neuen Zusammenfassungen gelabelt; die Validierung des Judges ist es, die seine Urteile gelten lässt.

Den Kandidaten mit der Baseline vergleichen

version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
require_validated_evaluators: true
rules:
  - id: covers-more-facts
    kind: superiority
    metric: covers_facts
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yaml
format_valid: +0.0 points [-23.6, +23.6] · 20 paired · 0 missing · 0 excluded
short_enough: +0.0 points [-23.6, +23.6] · 20 paired · 0 missing · 0 excluded
covers_facts: +55.0 points [+13.0, +84.4] · 20 paired · 0 missing · 0 excluded
Decisions
  covers-more-facts  covers_facts  superiority  PASS  difference_above_zero
Gate: ALLOW (exit 0)

Ein Vergleich wendet die PPI-Korrektur nicht an: Er vergleicht die eigenen Urteile des Judges über die beiden Läufe, weshalb der Gewinn bei den 45% des Judges ansetzt und nicht bei den korrigierten 0,500 von oben. Elf Zusammenfassungen wurden besser, keine schlechter; das Intervall für den Gewinn liegt vollständig über null, also besteht die Superiority-Regel, und der Befehl endet mit 0. Das Format wird durch die Laufregeln abgesichert, die keinen Fehlschlag erlauben, nicht durch einen Vergleich: Über 20 Fälle könnte ein Vergleich zweier perfekter Format-Scores nur sagen, dass der Unterschied innerhalb von 23,6 Punkten liegt.

Optional: ein echtes Modell als Judge

Dieser Schritt verlässt den Offline-Pfad. Er braucht einen Modellserver und bei einem Cloud-Provider einen Schlüssel und Geld.

  • Lokal, ohne Schlüssel und ohne Kosten: Ollama, LM Studio oder llama.cpp auf localhost. Laden Sie ein Chat-Modell (für Ollama ollama pull llama3.1).
  • Cloud: provider: anthropic oder openai mit api_key_env, das die Variable mit Ihrem Schlüssel benennt, oder openai_compatible mit base_url und api_key_env. Jeder Fall ist ein Judge-Aufruf (zwei, wenn die erste Antwort kein gültiges JSON ist), abgerechnet zu den Preisen Ihres Providers, und Oloproof ruft einen Judge nie erneut für eine Antwort auf, die er bereits beurteilt hat.

Schreiben Sie den Entwurfs-Judge in eine eigene Datei, so wie er unter evaluators: stehen würde:

# live_judge.yaml
type: rubric_judge
criterion: covers_facts
provider: openai_compatible
model: llama3.1
base_url: http://localhost:11434/v1
rubric_file: rubrics/covers_facts.md

und probieren Sie ihn an den Antworten aus, die Ihre Reviewerin bereits gelabelt hat, ohne ihn zu validieren oder zu übernehmen:

oloproof evaluators try live_judge.yaml

Lokale Server beantworten standardmäßig eine Anfrage nach der anderen; ergänzen Sie concurrency: {system: 2, judge: 2} in oloproof.yaml, damit wartende Aufrufe kein Timeout erreichen. Ein Durchlauf dieses Schritts mit einem kleinen lokalen Modell (qwen2.5vl) auf einem Laptop gab aus:

covers_facts: draft sha256:b88a... on 20 labelled cases · 20 judged now, 0 from cache, 11 errored
  agreement 88.9% [19.1%, 99.9%] · 8 of 9 labelled cases agreed · 11 labelled but not judged · kappa 0.769

Elf Aufrufe erreichten ein Timeout, und das Übereinstimmungsintervall zählt jeden davon in beide Richtungen, also reicht es bis 19,1% hinunter: Ein Judge, der nicht antwortet, wird nicht gemessen. Ein größeres Modell, ein längeres Timeout oder weniger gleichzeitige Aufrufe sind die Abhilfe. Um das Modell zu übernehmen, setzen Sie es in oloproof.yaml an die Stelle des Ersatz-Judges. Das ist eine neue Evaluator-Version: Ihre Konfiguration (Modell, Endpunkt, Rubrik) ist ihre Identität, daher überträgt sich die Validierung des Ersatz-Judges nicht. Führen Sie die Baseline erneut damit aus und validieren Sie ihn an den Labels, wie oben.

Fehlerbehebung

SymptomUrsache und Abhilfe
covers_facts fehlt überall, no_observationsDer Judge-Server läuft nicht oder nicht unter base_url. Jeder Judge-Aufruf schlug fehl; oloproof inspect RUN_ID --failures zeigt warum.
evaluator_not_validated, nachdem Sie validiert habenSie haben den Judge geändert (Modell, Endpunkt, Port, Rubrik) und eine neue Version erzeugt. Validieren Sie diese.
labels import lehnt die Datei ab und nennt eine ZeileDie Zeile nennt einen Fall oder eine Ausführung, die der Lauf nicht enthält; exportieren Sie erneut aus dem Lauf, den Sie labeln.
labels export meldet, ein Workspace sei nicht erreichbarSie sind bei einem angemeldet, also wurde er gebeten zu ziehen. --local zieht stattdessen hier.
Ein Cloud-Judge schlägt vor jedem Aufruf fehlSein Schlüssel steht nicht in der Variable, die api_key_env benennt.

Einschränkungen

  • Der Ersatz-Judge ist ein Phrasenabgleich. Er demonstriert den Ablauf, nicht die Qualität des Urteilens.
  • Es gibt keine Evaluatoren für BLEU, ROUGE oder Embedding-Ähnlichkeit. Schreiben Sie im SDK einen mit @evaluator; oloproof.yaml kann noch keinen eigenen Evaluator benennen.
  • Ein Judge sieht Text: JSON der Eingabe, der Referenz und der Ausgabe. Bilder oder Audio sieht er nicht.
  • Zwanzig Labels ergeben ein breites Übereinstimmungsintervall. Labeln Sie mehr, zufällig und blind, für einen Judge, auf den Sie sich verlassen.
  • Eine lokale Stichprobe gilt nur nach Treu und Glauben. Für einen Judge, auf den sich andere verlassen, pushen Sie den Lauf und lassen Sie einen gehosteten Workspace die Stichprobe ziehen (Judges).