0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

pytest で llm-as-a-judge

0
Posted at

はじめに

AI生成コードのドリフト問題

AI コーディングエージェントを使っていてよく感じるのは、AI は同じ情報をドキュメントやソースコードのあちこちに書き込むのが好きだということです。数値や仕様情報のベタ書きです。

開発が進むにつれて、その内容がメインのドキュメントや実装とズレていきドリフトするのは避けられません。AI あるあるだと思います。

AI エージェントのスキル生成でも同じ問題に直面します。共通するルールを持つスキルを複数作ったとき、最初はそれぞれに同じルールが書き込まれていますが、その後の更新で内容がズレていき、挙動がちぐはぐになることがあります。

放っておくと取っ散らかります。

ドリフト対策

軽減する方法はいくつか考えられます:

  • 定期的にチェック用 AI エージェントを呼び出す
  • CLAUDE.mdAGENTS.md にチェックするように書いておく
  • 同期させるテンプレートマクロの仕組みを整備する
  • 正規表現のチェックで頑張る
  • あきらめて「ブラックボックスでも動けばok😎」マインドに切り替える

対策をするなら、とにかくあいまいな記述をチェックする仕組みが必要です。

LLM as a Judge

LLM を使って何かを判定させることは "LLM as a Judge"1 と呼ばれたりします。あいまいなテキストをチェックする際によく使われます。

テキストの内容チェックなら、次のようなコードをイメージできます:

# 「すばやい茶色のキツネ🦊が怠けた犬🐶を飛び越える」
text = "The quick brown fox jumps over the lazy dog."

# LLM に「テキストが評価項目を満たすか」を判定させる
llm_judge(text, "二匹の動物が登場する")  # PASS
llm_judge(text, "キツネは寝ている")      # FAIL

判定の仕方にはコツがあり、ルーブリック (rubric) 評価と呼ばれる成績評価の考え方がよく用いられます。ルーブリックの定義は様々ですが、基本的には次のことを行います:

  • 評価項目を判断しやすい粒度に分解する
  • その結果を集計することで、ばらつきを抑えて可能な限り定量化した形にする

体系的な評価項目のセットを作る感じです。

AI エージェントのスキル評価ではルーブリックを使うことがベストプラクティスとされています23。また、コードの内容一貫性チェックでも活用できると思います。ただし、導入の仕方はプロジェクトによって様々になると思います。

pytest に LLM as a Judge を組み込む

LLM によるチェックの導入方法の一つとして、Python のプロジェクトであればテストで使う pytest に組み込むことが考えられます。

pytest のコード内で単に Anthropic や OpenAI などの AI プロバイダーの API を呼ぶか、サブプロセスとして AI エージェントの一問一答コマンド (claude -p, copilot -p ) を呼び出すことで、LLM によるチェックが可能になります。また既存のサードパーティライブラリもいくつかあるようで、わりと簡単に組み込めます。

組み込み例

最近、自作のエージェントスキルを作り込んでいました。

その中で内容の一貫性チェックをしたくなり、軽量な pytest-llm-rubric という pytest プラグインを作りました。これを組み込みながら試行錯誤中です。

このプラグインでは pytest のテスト関数に LLM 判定メソッドを提供します:

# tests/test_consistency.py
from pytest_llm_rubric import JudgeLLM

# テスト関数
def test_semantic_check_pass(judge_llm: JudgeLLM):
    # チェック対象のテキスト
    # 「すばやい茶色のキツネ🦊が怠けた犬🐶を飛び越える」
    text: str = "The quick brown fox jumps over the lazy dog."
    
    # 単純な判定
    assert judge_llm.judge(text, "二匹の動物が登場する")

    # しきい値ベースの判定
    results: list[bool] = [
        judge_llm.judge(text, "キツネは犬を飛び越える"),
        judge_llm.judge(text, "犬がキツネの下にいる"),
    ]
    assert sum(results) / len(results) >= 0.5

このテストは全て成功します。結果の出力は PASSED になります:

tests/test_consistency.py::test_semantic_check_pass PASSED               [100%]
================================= LLM Rubric ==================================
Model: anthropic:claude-haiku-4-5  Preflight: preflight passed (12/12) in 13.8s
3 passed, 0 failed
============================= 1 passed in 16.18s ==============================

失敗するケースとして、テキストの意味が変わった場合を試してみます:

# tests/test_consistency.py
def test_semantic_check_fail(judge_llm: JudgeLLM):
    # ドリフトしたテキスト
    # 「すばやい茶色の犬🐶が怠けた犬🐶と猫😺を飛び越える」
    text: str = "The quick brown dog jumps over the lazy dog and a cat."

    assert judge_llm.judge(text, "二匹の動物が登場する")  # FAIL
tests/test_consistency.py::test_semantic_check_pass PASSED               [ 50%]
tests/test_consistency.py::test_semantic_check_fail FAILED               [100%]

================================== FAILURES ===================================
__________________________ test_semantic_check_fail ___________________________

    def test_semantic_check_fail(judge_llm):
        text = "The quick brown dog jumps over the lazy dog and a cat."
>       assert judge_llm.judge(text, "二匹の動物が登場する")
E       AssertionError: assert False

================================= LLM Rubric ==================================
Model: anthropic:claude-haiku-4-5  Preflight: preflight passed (12/12) in 15.9s
3 passed, 1 failed

  FAIL tests/test_consistency.py::test_semantic_check_fail
       criterion: "二匹の動物が登場する"

======================== 1 failed, 1 passed in 20.03s =========================

テストが FAILED で失敗し、チェック対象のテキストの意味的な変化を検出しています。

モデルの選択

上記の判定では Anthropic のモデルを利用しましたが、毎回わずかな利用料金がかかってしまうため、Ollama などでローカル LLM を使うのも良いと思います。ただ、チェックの速度と安定性を考えると大手プロバイダーの最安モデルのほうが安定します。

参考までに、同じテストを複数モデルで実行した結果です:

モデル PASS ケース FAIL ケース 所要時間
anthropic:claude-haiku-4-5 PASS ✅ FAIL ✅ 20秒
ollama:gemma4:26b PASS ✅ FAIL ✅ 28分
ollama:gpt-oss:20b PASS ✅ PASS ❌ 35分

NVIDIA RTX 5070 Ti (VRAM 16GB) で実行したため、量子化に対してメモリが不足気味で Ollama のモデルは速度が出ていません。

環境にもよりますが、ローカル LLM は時間がかかる上に、モデルによってはドリフトを見逃すこともあります。もちろん適切な構成にすれば、同等以上の結果になるポテンシャルはあります。

おわりに

AI エージェントがあらゆるワークフローに入り込み、コードやドキュメントの内容がより不確実になっています。ハーネスやガードレールは欠かせません。(今回の判定はカラーコーンレベルですが。。)

LLM の研究者や開発者だけでなく、すべてのエンジニアがAIの不確実性と向き合う時代になりました。

本記事の表・脚注・テストコードの生成および文章の校正には AI を使用しています

  • Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
  1. Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena — LLM-as-a-Judge の概念を広めた論文

  2. Define success criteria and build evaluations — Anthropic による評価設計ガイド。バイナリ分類とルーブリックの考え方

  3. Skill authoring best practices — スキルの expected_behavior を個別に検証可能なステートメントにする

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?