ブログに戻る
コラム

LLM-as-a-Judgeとは?モデルに採点させる方法とバイアス対策

FastMetal

LLM-as-a-Judgeとは、LLMの出力の良し悪しを、別のLLMに採点させる手法です。 「Judge(審査員)」の名のとおり、人間の評価者の代わりをモデルに務めさせます。

なぜ必要かというと、自由記述は文字列一致で採点できないからです。要約・翻訳・カスタマーサポートの返答といった出力に「正解の文字列」はありません。かといって人手でのレビューは、モデルを1つ差し替えるたびに数百件を読み直すことになり、続きません。

ただし審査員もLLMなので、明確な癖があります。この記事では、実装方法と、その癖を潰す具体的な手順を扱います。評価全体の枠組みはEvals(評価)とはにまとめています。

2つの採点方式

直接採点(スコアリング)

1つの出力を見て点数を付けます。実装が単純で、絶対的な基準が欲しいときに向きます。

ペア比較(対戦)

2つの出力を並べて「どちらが良いか」を選ばせます。人間もそうですが、モデルも「絶対評価」より「相対評価」のほうが安定します。 モデルAとモデルBのどちらを採用するか、という判断にはこちらが向きます。

リーダーボードがEloで順位を付けているのも同じ理屈です。「良さ」を直接測るより、対戦結果を積み上げるほうが確からしい数字になります。

3つの既知のバイアス

ここがこの手法の肝です。審査員モデルには、繰り返し観測されている癖があります。

1. 位置バイアス

ペア比較で、提示された順番によって勝者が変わります。同じ2つの出力でも、AとBを入れ替えると判定がひっくり返ることがあります。

対策:必ず順序を入れ替えて2回聞き、両方で勝った方だけを勝ちとする。 判定が割れたら「引き分け」として扱います。呼び出しは2倍になりますが、これをやらない判定は信用できません。

2. 冗長性バイアス

長い回答を高く評価します。 内容が同じでも、丁寧な前置きと箇条書きの整理が付いた方が高得点になりがちです。

対策:評価基準に「簡潔さ」を明示的に入れる。 そして採点対象の出力は、max_tokens を揃えて生成してください。片方だけ長く書ける条件で比べても意味がありません。

3. 自己贔屓(self-preference)

モデルは自分自身、あるいは同じ系統のモデルが書いた文章を高く評価する傾向があります。

対策:採点対象と審査員に別系統のモデルを使う。 Claude系の出力を評価するならGemini系やGPT系を審査員にする、という具合です。1つのAPIキーで複数系統のモデルを呼べる環境だと、これは model の値を変えるだけで済みます。

おまけ:甘さ

点数は高い方に偏ります。5段階で聞くと4と5ばかりになりがちです。各点数が何を意味するかを言葉で定義すると改善します(「3 = 事実誤りはないが質問に部分的にしか答えていない」など)。

実装:構造化出力で受け取る

採点結果はプログラムで集計するので、構造化出力で受け取るのが必須です。「4点です。理由は…」という文章をパースするのは無駄な苦労になります。

import json
from openai import OpenAI

client = OpenAI(api_key="<FASTMETAL_API_KEY>", base_url="https://api.fastmetal.ai/v1")

JUDGE_MODEL = "gemini-3.5-flash"   # 採点対象とは別系統にする

RUBRIC = """あなたは厳格な評価者です。次の基準で回答を採点してください。
1. 事実の正確さ(誤りがないか)
2. 質問への適合(聞かれたことに答えているか)
3. 簡潔さ(冗長な前置きや繰り返しがないか)

点数の定義:
1 = 事実誤りがある、または質問に答えていない
2 = 部分的にしか答えていない
3 = 答えているが冗長、または要点が埋もれている
4 = 正確で要点を押さえている
5 = 正確・簡潔で、過不足がない"""

SCHEMA = {
    "type": "object",
    "properties": {
        "reasoning": {"type": "string", "description": "採点の根拠。先に書くこと"},
        "score": {"type": "integer", "description": "1〜5"},
    },
    "required": ["reasoning", "score"],
    "additionalProperties": False,
}

def judge(question: str, answer: str) -> dict:
    r = client.chat.completions.create(
        model=JUDGE_MODEL,
        max_tokens=500,
        messages=[
            {"role": "system", "content": RUBRIC},
            {"role": "user", "content": f"質問:\n{question}\n\n回答:\n{answer}"},
        ],
        response_format={
            "type": "json_schema",
            "json_schema": {"name": "verdict", "strict": True, "schema": SCHEMA},
        },
    )
    return json.loads(r.choices[0].message.content)

reasoningscore より先に置いているのは意図的です。 JSONのキーは書かれた順に生成されるので、根拠を先に書かせると、点数がその根拠に引きずられます。逆にすると、先に出した点数を後から正当化する文章が生成されます。

ペア比較で順序を入れ替える

位置バイアス対策を入れると、こうなります。

PAIR_SCHEMA = {
    "type": "object",
    "properties": {
        "reasoning": {"type": "string"},
        "winner": {"type": "string", "enum": ["A", "B", "tie"]},
    },
    "required": ["reasoning", "winner"],
    "additionalProperties": False,
}

def compare_once(question: str, a: str, b: str) -> str:
    r = client.chat.completions.create(
        model=JUDGE_MODEL, max_tokens=500,
        messages=[
            {"role": "system", "content": RUBRIC},
            {"role": "user", "content": f"質問:\n{question}\n\n回答A:\n{a}\n\n回答B:\n{b}"},
        ],
        response_format={
            "type": "json_schema",
            "json_schema": {"name": "pair", "strict": True, "schema": PAIR_SCHEMA},
        },
    )
    return json.loads(r.choices[0].message.content)["winner"]

def compare(question: str, x: str, y: str) -> str:
    """順序を入れ替えて2回聞き、一致したときだけ勝ちとする"""
    first  = compare_once(question, x, y)          # x=A, y=B
    second = compare_once(question, y, x)          # 入れ替え
    if first == "A" and second == "B":
        return "x"
    if first == "B" and second == "A":
        return "y"
    return "tie"                                    # 割れたら引き分け

compare が返す「引き分け」の多さは、そのままモデル間の差の小ささを表します。大量に引き分けが出るなら、その2モデルはその用途で差がないということです。有益な情報なので、無理に勝者を決めさせないでください。

審査員は必ず較正する

いちばん大事な工程です。審査員の判定が人間の判断と合っているかを、一度だけ確認してください。

  1. 30〜50件を人手で採点する(1〜2時間で終わります)
  2. 同じ件を審査員モデルに採点させる
  3. 一致率を見る

一致率が低ければ、評価基準の書き方か、審査員モデルの選択に問題があります。この較正をしていないLLM-as-a-Judgeは、何を測っているか分からない数字を生産しているだけです。

一度較正しておけば、以降はモデルを差し替えるたびに自動で回せます。それが人手評価に対する唯一の優位性です。

コストの見積もり

見落とされがちですが、審査員の呼び出しは評価対象と同じだけ課金されます。

  • 直接採点:評価1件につき、生成1回+採点1回
  • ペア比較(順序入れ替えあり):生成2回+採点2回

つまりペア比較の1件は4回の呼び出しです。テストケース200件でモデル3つを比べるなら、呼び出しは数千回規模になります。

審査員には安いモデルを使ってください。 採点は生成より簡単な仕事なので、フラッグシップである必要はほとんどありません。較正で一致率が確認できていれば、安いモデルで十分です。単価は料金ページで比べられます。コスト全般の考え方はLLM APIのコストを下げる7つの方法にまとめました。

向かない場面

LLM-as-a-Judgeが答えにならないケースもあります。

  • 正解が一意に決まるタスク — 分類や抽出は、正解ラベルとの一致で測るほうが速く・安く・確実です
  • 専門知識が必要な判断 — 医療・法務など、審査員モデルの知識が及ばない領域
  • 最終的な合否判断 — 出荷判断は人間が持つべきです。審査員は候補を絞り、劣化を検知する道具です

よくある質問

Q. LLM-as-a-Judgeの結果は信用できますか? 較正すれば実用になります。30〜50件を人手で採点し、審査員の判定との一致率を確認してください。較正していない判定は、何を測っているか分からない数字です。

Q. 審査員にはどのモデルを使うべきですか? 採点対象と別系統のモデルです。モデルは自分と同系統の出力を高く評価する傾向(自己贔屓)があります。採点は生成より簡単なので、安いモデルで構いません。

Q. 順序を入れ替えて2回聞く必要は本当にありますか? あります。位置バイアスにより、同じ2つの出力でも提示順で勝者が変わることがあります。両方の順序で勝った場合だけ勝ちとし、割れたら引き分けとして扱ってください。

Q. 点数が4と5ばかりになります。 甘さのバイアスです。各点数が何を意味するかを言葉で定義し(「3 = 事実誤りはないが部分的にしか答えていない」など)、根拠を点数より先に書かせてください。

まとめ

LLM-as-a-Judgeは、自由記述の評価を自動化する現実的な手段です。ただし審査員には位置・冗長さ・自己贔屓の3つの癖があり、順序の入れ替え・基準への簡潔さの明記・別系統モデルの起用で潰せます。

そして人手30〜50件との較正を一度やるかどうかが、使える数字か気休めかの分かれ目です。採点結果は構造化出力で受け取り、審査員には安いモデルを充ててください。1つのキーで系統の違うモデルを呼び分けられると実装が楽になります——モデルカタログから選べます。

最新のAIモデルを今すぐ試す

最新のAIモデルはFastMetalのAPIキー1つで利用できます。ブラウザですぐに試す、またはOpenAI SDKからそのまま呼び出せます。