ガイド
チュートリアル:ルーブリックジャッジによるテキスト生成
自由テキストを書く関数(ここではチケットの要約器)を、形式チェックとルーブリックジャッジで評価します。そのジャッジが何かを判断する前に、人のラベルに対して測定し、そのうえで実際の変更を比較します。ジャッジはこのマシン上で、モデルもネットワークも使わずに動作し、任意のステップで実際のモデルに差し替えます。
作るもの
サポートチケットを 1 文か 2 文にまとめる要約器です。「良い」かどうかは判断であって文字列の一致ではないため、タスクの成功はルーブリックを持つ LLM ジャッジが判断します。要約は、担当者が必要とする事実を述べているか、という問いです。2 つの決定的な評価器が形式を確認しますが、これには参照は必要ありません。ケース、実行、メトリクス、ジャッジ、ゲートなどの用語は 基本概念 で定義しています。
同じ形は抽出やその他の生成にも当てはまります。関数は辞書の中にテキストを返し、参照は良い回答が含むべきものを示し、ルーブリックは判断の仕方を示します。
前提条件
- Python 3.11 以降と、仮想環境にインストールした Oloproof:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof- パッケージに同梱されているサンプルプロジェクト。新しいディレクトリにコピーし、そこで作業します:
oloproof init --example generation ticket-summaries
cd ticket-summaries- 代役のジャッジのためにポート 8799 が空いていること(空いていなければ、両方の箇所で変更してください)。
「任意:実際のモデルをジャッジにする」までのすべてのステップはオフラインかつ決定的です。API キーも、プロバイダーのアカウントも、費用も不要です。
ファイル
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すべてのコマンドは ticket-summaries/ から実行してください。
代役のジャッジと、それが何でないか
ルーブリックジャッジは、プロンプト(ルーブリック、ケースの入力、その expected、出力)をモデルに送り、{"pass": true|false, "rationale": "..."} を読み取る評価器です。Oloproof は OpenAI のチャット API を話す任意のサーバーと通信でき、localhost 上のサーバーにはキーは不要です。
judge_server.py はそのようなサーバーですが、モデルではありません。ケースの expected にある must_mention の下のすべての語句を、大文字小文字を区別せずに含むときにだけ要約を合格にします。これは固定されたルールなので、このチュートリアルはどのマシンでも同じ数値になります。実際のモデルのジャッジには求められる、作り話の事実に気づくことはできません。2 つ目のターミナルで起動し、動かしたままにしてください:
python judge_server.py --port 8799stand-in judge on http://127.0.0.1:8799/v1アプリケーションとそのアダプター
# 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]}Python アプリケーションのアダプターは関数です。ケースの input を受け取り、辞書を返します。自分の生成器の場合は、その中でモデルやチェーンを呼び出し、テキストをあるキーの下に入れて返してください。Oloproof はケースごとに 1 回呼び出し、出力を関数のソースと宣言された version をキーとしてキャッシュします。モデルのクライアント、プロンプト、状態は管理しません。プロンプトテンプレートなど、関数が読むファイルは system.code_paths に列挙してください。
データセット
{"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 は関数が受け取るものです。expected はジャッジが読む参照で、ここでは完全な参照要約ではなく、要約が含むべき事実のリストです。正しい要約は数多くあり得るからです。t01 の出力は {"summary": "Hello."} です。
評価器を選ぶ
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| 基準 | 評価器 | expected が必要か | 測るもの |
|---|---|---|---|
| format_valid | json_schema | いいえ | 形式:空でない文字列フィールドが 1 つ |
| short_enough | regex | いいえ | 形式:最大 160 文字 |
| covers_facts | rubric_judge | はい | ルーブリックが定義するタスクの成功 |
Hello. は両方の形式チェックに合格します。それが役に立たない要約だと言うのはジャッジだけです。ジャッジは参照なしでも実行できます。「要約に挨拶が含まれなければ PASS」のようなルーブリックは入力と出力だけを読み、expected のないケースでも判定されます。その場合にできないのは、信頼できる回答に照らして事実を確認することです。
ルーブリック:
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.ポリシー
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 はエンジンの既定値ですが、このチュートリアルの要点なのでここに明記しています。誰も人と比較していないジャッジは、ルールを判断してはなりません。
実行する
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 miss形式ルールは合格します。ジャッジは 20 個中 9 個の要約を合格にしましたが、ルールは理由 evaluator_not_validated で INSUFFICIENT_EVIDENCE となり、ゲートは終了コード 3 でブロックします。ルールは 45% に基づいて判断しませんでした。ジャッジの誤り率は測定されるまで不明であり、その判定から作った区間には明示されない誤差が含まれてしまうからです。エンジンはこれを MANUAL_REVIEW や FAIL ではなく INSUFFICIENT_EVIDENCE として報告します。判断に必要なエビデンスが欠けているのであり、出力にはそれを補う 2 つのコマンドが表示されます。
失敗を調べる
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
...ジャッジの根拠は「judge text, not verified」として表示されます。これはモデルの説明であって、エビデンスではありません。それでもパターンは明らかです。最初の文はしばしば挨拶です。
ジャッジを人に対して測定する
検証では、同じ回答に対するジャッジの判定と人の判定を比較します。実行のケースから無作為なサンプルをシートに抜き出します。ラベル付けする人がジャッジの判定に引きずられないよう、判定はシートから除かれます:
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 はホストされたワークスペースに頼らず、このマシン上で抽出します。シードはエンジンが選びます。ケースが 20 個なので、20 個のサンプルはその全部です。実際には、人が各行のチケットと要約を読み、passed を記入します。このチュートリアルでは、labels/reviewer_verdicts.csv にレビュアーがベースラインの要約に下した判定が入っており、fill_labels.py がそれをシートにコピーします:
python fill_labels.py sample.csv
oloproof labels import sample.csvfilled 20 rows of sample.csv
Recorded 20 labels from sample.csv (20 measurement).レビュアーがジャッジと意見を異にしたのは 1 回だけです。t02(「I was charged twice for order 2210.」)では、金額が欠けていることは重要でないと判断して合格にしました。ラベルは判定した回答そのものを指すため、これらの判定はベースラインの実行にのみ適用されます。
ジャッジのバージョン ID を調べて検証します:
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 judged95% ではなく区間を読んでください。20 個のラベルが示すのは、一致度が少なくとも 75.1% であることです。ポリシーは minimum_evaluator_agreement でそれ以上を要求でき、これはその下限と比較されます。validate はそれを下回るジャッジを拒否します。基準、バイアス、プローブ、ターミナルでのラベル付けに使う oloproof review については ジャッジ ガイドで扱います。
次に、要約器もジャッジも呼び出さずに、保存された実行を判断し直します:
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)これでジャッジは判断でき、その判断は要約器についてのものです。示される割合 0.500 は、ジャッジの 45% ではありません。この実行には測定用ラベルの盲検・無作為なサンプルがあるので、ゲートはそれらのラベルで補正したジャッジを読みます(ジャッジ ガイドの「ジャッジを補正したゲート」)。この補正は PPI(prediction-powered inference、予測で強化した推論)です。ラベル付きサンプルを使ってジャッジの割合が人の割合からどれだけ離れているかを測り、その分だけ推定値を動かし、区間を広げます。エクスポート時の PPI に関する注記が指しているのもこれです。いずれにせよ、ベースラインは下限を満たしておらず、ケースを増やしてもそれは変わりません。
実際に変更する
app_v2.py は短い挨拶を飛ばし、続く 2 文を残します。それを app.py に上書きコピーし、oloproof.yaml の system の下で version: skip-pleasantries を設定し、ジャッジを動かしたまま、次を実行します:
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 missジャッジは同じ検証済みのバージョンなので、そのルールは直接判断します。6 件の判定は、両バージョンがまったく同じに書いた要約についてキャッシュから取られました。これらの新しい要約には誰もラベルを付けていません。ジャッジの判定を有効にしているのは、ジャッジの検証です。
候補をベースラインと比較する
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)比較では PPI 補正は適用されません。2 つの実行に対するジャッジ自身の判定を比較するため、改善幅は上の補正後の 0.500 ではなく、ジャッジの 45% から出発します。11 個の要約が改善し、悪化したものはありません。改善幅の区間は完全にゼロより上にあるので、優越性ルールは合格し、コマンドは 0 で終了します。形式は比較ではなく、失敗を許さない実行ルールで守られています。20 ケースでは、2 つの完全な形式スコアの比較は、差が 23.6 ポイント以内であるとしか言えません。
任意:実際のモデルをジャッジにする
このステップはオフラインの道筋から外れます。モデルサーバーが必要で、クラウドプロバイダーの場合はキーと費用もかかります。
- ローカル、キー不要で費用なし:localhost 上の Ollama、LM Studio、llama.cpp。チャットモデルを取得します(Ollama なら ollama pull llama3.1)。
- クラウド:provider: anthropic または openai で、api_key_env にキーを保持する変数名を指定するか、openai_compatible で base_url と api_key_env を指定します。各ケースはジャッジ呼び出し 1 回(最初の返答が有効な JSON でない場合は 2 回)で、プロバイダーの料金で課金されます。Oloproof は、すでに判定した回答に対してジャッジを再び呼び出すことはありません。
下書きのジャッジを、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.mdそして、検証も採用もせずに、レビュアーがすでにラベルを付けた回答に対して試します:
oloproof evaluators try live_judge.yamlローカルサーバーは既定では 1 度に 1 つのリクエストに応答します。キューに入った呼び出しがタイムアウトしないよう、oloproof.yaml に concurrency: {system: 2, judge: 2} を追加してください。ノートパソコン上の小さなローカルモデル(qwen2.5vl)でこのステップを実行すると、次のように表示されました:
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.76911 件の呼び出しがタイムアウトし、一致度の区間はそれぞれを両方向に数えるので、19.1% まで下がります。応答しないジャッジは測定されません。より大きなモデル、より長いタイムアウト、あるいは同時呼び出しを減らすことが対処法です。モデルを採用するには、代役の代わりに oloproof.yaml に入れてください。それは新しい評価器のバージョンです。設定(モデル、エンドポイント、ルーブリック)がその同一性なので、代役の検証は引き継がれません。それを使ってベースラインを再実行し、上と同じようにラベルに対して検証してください。
トラブルシューティング
| 症状 | 原因と対処 |
|---|---|
| covers_facts がすべて欠損、no_observations | ジャッジのサーバーが動いていないか、base_url 上にありません。すべてのジャッジ呼び出しがエラーになりました。oloproof inspect RUN_ID --failures で理由がわかります。 |
| 検証したのに evaluator_not_validated | ジャッジ(モデル、エンドポイント、ポート、ルーブリック)を変更し、新しいバージョンを作りました。そちらを検証してください。 |
| labels import がファイルを拒否し、行を示す | その行は実行が保持していないケースまたは実行を指しています。ラベルを付ける実行から再度エクスポートしてください。 |
| labels export がワークスペースに到達できないと言う | ワークスペースにログインしているため、そこに抽出を依頼しました。--local ならここで抽出します。 |
| クラウドのジャッジが呼び出し前に失敗する | キーが、api_key_env が指定する変数にありません。 |
制限事項
- 代役のジャッジは語句の一致です。示しているのはワークフローであって、判定の品質ではありません。
- BLEU、ROUGE、埋め込み類似度の評価器はありません。SDK では @evaluator で書いてください。oloproof.yaml はまだカスタム評価器を指定できません。
- ジャッジが見るのはテキスト、つまり入力、参照、出力の JSON です。画像や音声は見ません。
- 20 個のラベルでは一致度の区間は広くなります。頼りにするジャッジには、無作為かつ盲検で、より多くのラベルを付けてください。
- ローカルのサンプルは善意のものにとどまります。他の人が頼りにするジャッジには、実行をプッシュし、ホストされたワークスペースにサンプルを抽出させてください(ジャッジ)。