Przewodniki
Samouczek: generowanie tekstu z sędzią z rubryką
Oceń funkcję, która pisze swobodny tekst, tutaj narzędzie streszczające zgłoszenia, za pomocą sprawdzeń formatu i sędziego z rubryką; zmierz tego sędziego względem etykiet nadanych przez człowieka, zanim będzie mógł cokolwiek rozstrzygać; potem porównaj prawdziwą zmianę. Sędzia działa na tej maszynie bez modelu i bez sieci, a opcjonalny krok podstawia prawdziwy model.
Co zbudujesz
Narzędzie, które zamienia zgłoszenie do wsparcia w jedno lub dwa zdania. "Dobre" to kwestia oceny, a nie dopasowania tekstu, więc o sukcesie zadania decyduje sędzia LLM z rubryką: czy streszczenie podaje fakty potrzebne konsultantowi? Dwa ewaluatory deterministyczne sprawdzają format, co nie wymaga wzorca. Terminy takie jak przypadek, przebieg, metryka, sędzia i bramka są zdefiniowane w Pojęciach.
Ten sam schemat pasuje do ekstrakcji i każdego innego generowania: funkcja zwraca tekst w słowniku, wzorzec mówi, co musi zawierać dobra odpowiedź, a rubryka mówi, jak rozstrzygać.
Wymagania wstępne
- Python 3.11 lub nowszy oraz Oloproof w środowisku wirtualnym:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof- Przykładowy projekt, dostarczany z pakietem. Skopiuj go do nowego katalogu i pracuj tam:
oloproof init --example generation ticket-summaries
cd ticket-summaries- Wolny port 8799 dla zastępczego sędziego (jeśli nie, zmień go w obu miejscach).
Każdy krok aż do "Opcjonalnie: prawdziwy model jako sędzia" działa offline i deterministycznie: bez klucza API, bez konta u dostawcy, bez kosztów.
Pliki
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 sheetUruchamiaj każde polecenie z ticket-summaries/.
Zastępczy sędzia i czym nie jest
Sędzia z rubryką to ewaluator, który wysyła prompt (rubrykę, wejście przypadku, jego expected i wynik) do modelu i odczytuje {"pass": true|false, "rationale": "..."}. Oloproof rozmawia z każdym serwerem obsługującym API czatu OpenAI, a serwer na localhost nie potrzebuje klucza.
judge_server.py jest takim serwerem, ale nie jest modelem. Przepuszcza streszczenie tylko wtedy, gdy zawiera ono każdą frazę z must_mention w expected przypadku, bez względu na wielkość liter. To stała reguła, więc samouczek daje te same liczby na każdej maszynie. Nie zauważy zmyślonego faktu, o co prosi się prawdziwego sędziego-model. Uruchom go w drugim terminalu i zostaw włączonego:
python judge_server.py --port 8799stand-in judge on http://127.0.0.1:8799/v1Aplikacja i jej 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]}Adapterem dla aplikacji w Pythonie jest funkcja: otrzymuje input przypadku i zwraca słownik. Dla własnego generatora wywołaj w niej swój model lub łańcuch i zwróć tekst pod kluczem. Oloproof wywołuje ją raz na przypadek i cache'uje wynik według kodu źródłowego funkcji i zadeklarowanej version; nie zarządza Twoim klientem modelu, promptami ani stanem. Pliki, które funkcja czyta, na przykład szablon promptu, wymień w system.code_paths.
Zbiór danych
{"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 to to, co otrzymuje funkcja. expected to wzorzec, który czyta sędzia: tutaj lista faktów, które streszczenie musi zawierać, a nie pełne streszczenie wzorcowe, ponieważ poprawnych streszczeń jest wiele. Wynik dla t01 to {"summary": "Hello."}.
Wybór ewaluatorów
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| Kryterium | Ewaluator | Wymaga expected | Mierzy |
|---|---|---|---|
| format_valid | json_schema | nie | format: jedno niepuste pole tekstowe |
| short_enough | regex | nie | format: co najwyżej 160 znaków |
| covers_facts | rubric_judge | tak | sukces zadania, tak jak definiuje go rubryka |
Hello. przechodzi oba sprawdzenia formatu. Tylko sędzia mówi, że to bezużyteczne streszczenie. Sędzia może też działać bez wzorca: rubryka taka jak "PASS if the summary contains no greeting" czyta tylko wejście i wynik, a przypadek bez expected nadal jest oceniany. Nie może wtedy sprawdzać faktów względem odpowiedzi, której ufasz.
Rubryka:
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.Polityka
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.60require_validated_evaluators: true to domyślne ustawienie silnika, zapisane tu jawnie, bo to sedno tego samouczka: sędzia, którego nikt nie porównał z ludźmi, nie może rozstrzygać reguły.
Uruchom
oloproof runRun 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 missReguły formatu przechodzą. Sędzia przepuścił 9 z 20 streszczeń, ale reguła ma stan INSUFFICIENT_EVIDENCE z przyczyną evaluator_not_validated, a bramka blokuje z kodem 3. Reguła nie rozstrzygnęła na podstawie 45%: odsetek błędów sędziego jest nieznany, dopóki się go nie zmierzy, więc przedział zbudowany na jego werdyktach niósłby niezadeklarowany błąd. Silnik raportuje to jako INSUFFICIENT_EVIDENCE, a nie MANUAL_REVIEW czy FAIL: brakuje dowodów do rozstrzygnięcia, a wynik wypisuje dwa polecenia, które je dostarczają.
Przejrzyj niepowodzenia
oloproof inspect RUN_ID --failures11 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
...Uzasadnienie sędziego jest pokazane jako "judge text, not verified": to wyjaśnienie modelu, a nie dowód. Wzorzec i tak jest jasny: pierwsze zdanie to często powitanie.
Zmierz sędziego względem człowieka
Walidacja porównuje werdykty sędziego z werdyktami człowieka na tych samych odpowiedziach. Wylosuj próbę przypadków przebiegu do arkusza. Werdykty sędziego są z niego pominięte, więc osoba etykietująca nie jest nimi zakotwiczona:
oloproof labels export RUN_ID --criterion covers_facts --sample 20 --local --out sample.csvWrote 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 losuje na tej maszynie, bez pytania hostowanego obszaru roboczego; ziarno wybiera silnik. Przy 20 przypadkach próba 20 to wszystkie. W praktyce osoba czyta zgłoszenie i streszczenie w każdym wierszu i wypełnia passed. Na potrzeby tego samouczka labels/reviewer_verdicts.csv zawiera werdykty, które recenzent wydał o streszczeniach punktu odniesienia, a fill_labels.py kopiuje je do arkusza:
python fill_labels.py sample.csv
oloproof labels import sample.csvfilled 20 rows of sample.csv
Recorded 20 labels from sample.csv (20 measurement).Recenzent nie zgodził się z sędzią raz: przy t02 ("I was charged twice for order 2210.") uznał brakującą kwotę za nieistotną i przepuścił streszczenie. Etykiety wskazują dokładną odpowiedź, którą oceniono, więc te werdykty dotyczą tylko przebiegu punktu odniesienia.
Znajdź identyfikator wersji sędziego i zwaliduj go:
oloproof evaluators list
oloproof evaluators validate EVALUATOR_ID --by alicecovers_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 judgedCzytaj przedziały, a nie 95%: 20 etykiet wykazuje zgodność co najmniej 75,1%. Polityka może żądać więcej przez minimum_evaluator_agreement, które porównuje tę dolną granicę, a validate odrzuca sędziego poniżej niej. Przewodnik Sędziowie omawia poprzeczkę, obciążenie, sondy i oloproof review do etykietowania w terminalu.
Teraz podejmij ponownie decyzję dla zapisanego przebiegu bez wywoływania narzędzia streszczającego ani sędziego:
oloproof gate RUN_ID --policy release.yamlvalid-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)Sędzia może teraz rozstrzygać, a decyzja dotyczy narzędzia streszczającego: podany odsetek, 0.500, to nie 45% sędziego. Ponieważ ten przebieg ma ślepą, losową próbę etykiet pomiarowych, bramka czyta sędziego skorygowanego tymi etykietami ("Judge-corrected gates" w przewodniku Sędziowie). Korekta to PPI, prediction-powered inference: wykorzystuje próbę z etykietami, by zmierzyć, jak daleko odsetek sędziego leży od odsetka ludzi, i o tyle przesuwa estymatę i poszerza przedział. Do tego też odnoszą się uwagi eksportu o PPI. Tak czy inaczej punkt odniesienia nie spełnia progu, a więcej przypadków by tego nie zmieniło.
Wprowadź prawdziwą zmianę
app_v2.py pomija krótkie uprzejmości i zachowuje dwa kolejne zdania. Skopiuj go na app.py, ustaw version: skip-pleasantries pod system w oloproof.yaml, nie wyłączaj sędziego i:
oloproof runGate: 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 missSędzia to ta sama zwalidowana wersja, więc jego reguła rozstrzyga bezpośrednio. Sześć ocen pochodziło z cache, dla streszczeń, które obie wersje napisały identycznie. Nikt nie etykietował tych nowych streszczeń; to walidacja sędziego pozwala jego werdyktom obowiązywać.
Porównaj kandydata z punktem odniesienia
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_factsoloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlformat_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)Porównanie nie stosuje korekty PPI: porównuje własne werdykty sędziego w obu przebiegach, dlatego zysk liczy się od 45% sędziego, a nie od skorygowanego 0.500 powyżej. Jedenaście streszczeń się poprawiło, a żadne nie pogorszyło; przedział zysku leży w całości powyżej zera, więc reguła wyższości przechodzi, a polecenie kończy się kodem 0. Format pilnują reguły przebiegu, które nie dopuszczają żadnego niepowodzenia, a nie porównanie: na 20 przypadkach porównanie dwóch idealnych wyników formatu mogłoby jedynie stwierdzić, że różnica mieści się w 23,6 punktu.
Opcjonalnie: prawdziwy model jako sędzia
Ten krok opuszcza ścieżkę offline. Wymaga serwera modelu, a u dostawcy chmurowego klucza i pieniędzy.
- Lokalnie, bez klucza i bez kosztów: Ollama, LM Studio lub llama.cpp na localhost. Pobierz model czatu (dla Ollama ollama pull llama3.1).
- Chmura: provider: anthropic lub openai z api_key_env wskazującym zmienną z Twoim kluczem albo openai_compatible z base_url i api_key_env. Każdy przypadek to jedno wywołanie sędziego (dwa, gdy pierwsza odpowiedź nie jest poprawnym JSON), rozliczane według stawek Twojego dostawcy, a Oloproof nigdy nie wywołuje sędziego ponownie dla odpowiedzi, którą już ocenił.
Zapisz roboczego sędziego w osobnym pliku, tak jak wyglądałby pod evaluators::
# 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.mdi wypróbuj go na odpowiedziach, które recenzent już oetykietował, bez walidowania go i bez przyjmowania:
oloproof evaluators try live_judge.yamlSerwery lokalne domyślnie odpowiadają na jedno żądanie naraz; dodaj concurrency: {system: 2, judge: 2} do oloproof.yaml, aby zakolejkowane wywołania nie przekraczały limitu czasu. Przebieg tego kroku z małym lokalnym modelem (qwen2.5vl) na laptopie wypisał:
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.769Jedenaście wywołań przekroczyło limit czasu, a przedział zgodności liczy każde z nich na obie strony, więc sięga w dół do 19,1%: sędzia, który nie odpowiada, nie jest zmierzony. Rozwiązaniem jest większy model, dłuższy limit czasu lub mniej równoległych wywołań. Aby przyjąć model, wstaw go do oloproof.yaml w miejsce zastępczego. To nowa wersja ewaluatora: jej konfiguracja (model, punkt końcowy, rubryka) jest jej tożsamością, więc walidacja zastępczego sędziego nie przechodzi na nią. Uruchom z nim ponownie punkt odniesienia i zwaliduj go względem etykiet, jak wyżej.
Rozwiązywanie problemów
| Objaw | Przyczyna i rozwiązanie |
|---|---|
| covers_facts w całości brakujące, no_observations | Serwer sędziego nie działa albo nie jest pod base_url. Każde wywołanie sędziego zakończyło się błędem; oloproof inspect RUN_ID --failures pokazuje dlaczego. |
| evaluator_not_validated po walidacji | Zmieniłeś sędziego (model, punkt końcowy, port, rubrykę) i powstała nowa wersja. Zwaliduj tę. |
| labels import odrzuca plik i wskazuje wiersz | Wiersz wskazuje przypadek lub wykonanie, którego przebieg nie zawiera; wyeksportuj ponownie z przebiegu, który etykietujesz. |
| labels export mówi, że nie udało się połączyć z obszarem roboczym | Jesteś zalogowany do jakiegoś, więc poprosił go o losowanie. --local losuje zamiast tego tutaj. |
| Sędzia chmurowy zawodzi przed jakimkolwiek wywołaniem | Jego klucza nie ma w zmiennej wskazanej przez api_key_env. |
Ograniczenia
- Zastępczy sędzia to dopasowanie fraz. Pokazuje przebieg pracy, a nie jakość oceniania.
- Nie ma ewaluatorów BLEU, ROUGE ani podobieństwa embeddingów. W SDK napisz taki z @evaluator; oloproof.yaml nie może jeszcze wskazać własnego ewaluatora.
- Sędzia widzi tekst: JSON wejścia, wzorca i wyniku. Nie widzi obrazów ani dźwięku.
- Dwadzieścia etykiet daje szeroki przedział zgodności. Dla sędziego, na którym polegasz, etykietuj więcej, losowo i na ślepo.
- Lokalna próba ma charakter wyłącznie good-faith. Dla sędziego, na którym polegają inni, wypchnij przebieg i pozwól hostowanemu obszarowi roboczemu wylosować próbę (Sędziowie).