つれづれなる Agent OPS
つれづれなる Agent OPS
設計

ルーブリックとEvalは最初から正しく作れないので、失敗ログで育てる

LLMの出力改善ループで、評価基準が粗いままだと弱い出力が合格してしまう問題を、外部APIに依存しない検証スクリプトで確認します。ルーブリックを運用しながら育てるための判断軸を整理しました。

Xでシェア
Markdown を表示

AIの出力を安定させるために、生成・評価・フィードバック・再生成のループを作ります。考え方としては分かりやすいのですが、実際にやってみると別の場所で止まってしまいます。

ループを回しているのに、何を良くしたのか分からない。

最初はプロンプトやモデルの問題だと思っていました。しかし、最小実装へ落としてみると、問題は評価基準そのものにありました。ルーブリックが粗いままだと、弱い出力が合格してしまいます。逆に、評価基準を細かくしすぎると、今度は実務上そこまで求めていない項目まで不合格になります。

今回は、ループ設計シリーズの続きとして、ルーブリックとEvalを「最初から完成した判定器」ではなく、「失敗ログで育てる運用資産」として扱うための考え方を整理します。外部のLLM APIは呼ばず、記事用に用意した検証スクリプトで、粗いルーブリックと具体化したルーブリックの差だけを確認しました。

LLM Lab APIで最小ループを組むと、プロンプト改善ではなく停止条件の設計になる 生成と評価を分離した最小ループを実装し、成功と最大試行回数による停止の両経路を検証しています。 https://llm-lab.dev/posts/llm-loop-engineering-minimal-api/

題材にしたのは、問い合わせに対する一次返信案の生成です。たとえば、ユーザーから障害や不具合の連絡を受けたときに、サポート担当が最初に返す短い文章を想定しています。この種の返信では、文章が丁寧であることだけでは不十分です。相手が次に何を期待してよいのか、誰が状況を持っているのか、読み手が早く判断できる形になっているのかが重要になります。

そのため、この記事では「丁寧であること」「次の対応が書かれていること」という粗い基準から始め、実務で使うには足りない点を見つけるために、「冒頭で結論を述べているか」「次に取る行動が2件以上あるか」「誰が対応するのかが分かるか」という基準へ具体化しています。つまり、ルーブリックそのものの正解を示したいのではなく、題材に応じて評価基準をどう育てるかを見るための例です。

粗いルーブリックは弱い出力を通してしまう

問い合わせに対する一次返信案を作るタスクを例にします。まず、次のような評価基準を置いたとします。

丁寧であること
次の対応が書かれていること

人間が読むと、なんとなく分かった気になります。しかし、Evalとして見るとかなり危うい基準です。「丁寧」とは何か、「次の対応」は何件必要か、誰が対応するのか、いつ判断できるのかが決まっていないからです。

この粗いルーブリックでは、次のような出力も合格してしまいます。

お問い合わせありがとうございます。
状況を確認し、必要に応じて対応します。

文章としては破綻していません。ただ、業務で使う返信案としては弱いです。結論が先に出ていない、次の行動が曖昧、担当者の役割も分からない。にもかかわらず、粗いルーブリックだけを見ると「丁寧」「対応に言及している」と判定できてしまいます。

ここがつらいところです。Evalが緑になっているのに、実際の出力はまだ使いづらい。

ルーブリックを運用単位まで具体化する

今回の検証では、同じ出力に対して、粗いルーブリックと具体化したルーブリックを当てました。具体化したルーブリックでは、次の3項目を見ています。

冒頭で結論を述べているか
次に取る行動が2件以上あるか
誰が対応するのかが分かるか

この3項目は、文章の美しさではなく、問い合わせを受けた側が次の動きを説明できているかを見るための最低条件です。重要なのは、基準を「良い文章」のような印象語で止めず、未達時に次のフィードバックへ変換できる単位まで落とすことです。

検証スクリプトでは、同じ出力を2つのルーブリックで評価しました。これは記事用に用意した自作の検証スクリプトで、外部APIやモデル呼び出しは行いません。

入力として固定の返信案サンプルを受け取り、粗いルーブリックと具体化したルーブリックの判定結果をJSONで返します。

npm run verify

結果は次のようになります。

{
  "loose": {
    "passed": true,
    "failedRubricIds": []
  },
  "strict": {
    "passed": false,
    "failedRubricIds": [
      "conclusion_first",
      "two_actions",
      "owner_named"
    ]
  }
}

見るべきなのは、どちらが正しいかを一度で決めることではありません。同じ出力でも、評価基準の粒度によって合否が変わるという点です。つまり、Evalの失敗はモデルの失敗だけではなく、評価設計の失敗でもあります。

粗いルーブリックと具体化したルーブリックの比較

失敗ログはルーブリックを直す材料になる

ルーブリックは、最初から正しく作ろうとすると重くなります。自分も最初は、評価項目をきれいに作り込もうとして、かえって実験が進まなくなりました。

しかし、実際には最初のルーブリックは仮説で十分です。大事なのは、合格したのに使いづらかった出力、不合格だが人間としては許容できる出力、毎回同じ項目で落ちる出力をログとして残し、評価基準へ戻すことです。

今回の例であれば、粗いルーブリックで合格した出力を見て、次のように基準を更新できます。

  • 「丁寧であること」は単独の合格条件にしない
  • 「次の対応」は、2件以上の具体的な行動として見る
  • 「誰が対応するか」を別項目として切り出す
  • 「結論が先にあるか」を、判断速度を上げる条件として入れる

この更新は、プロンプト改善ではなくEval改善です。AIに「もっと具体的に」と頼む前に、何をもって「具体的」と判断するのかを評価側で持つ必要があります。

Evalを緑にすること自体を目的にしない

ここで注意したいのは、Evalを厳しくすればよいわけではないという点です。

評価項目を増やしすぎると、出力は細かい条件に引っ張られます。問い合わせ返信案に、毎回詳細な担当分担、期限、リスク、代替案、エスカレーション条件まで求めると、短く済むはずの返信が過剰になります。実務では「そのタスクで本当に必要な品質」までで止める判断も必要です。

ルーブリックを育てるときは、少なくとも次の3つを分けて見ると扱いやすくなります。

  • 合格したが、実際には使いづらかった出力
  • 不合格だが、実務上は許容できた出力
  • 不合格で、次の実行に具体的なフィードバックを返せた出力

特に重要なのは3つ目です。ループ設計では、不合格という判定だけでは不十分です。どの項目が未達で、次に何を変えるべきかまで返せなければ、再生成はただの再試行になります。

ルーブリックを育てる最小手順

今回の検証から見ると、ルーブリックとEvalは次の順序で育てるのが現実的です。

  1. まず3項目程度の粗いルーブリックで始める
  2. 合格したが使いづらい出力を1つ残す
  3. その出力がなぜ使いづらいのかを、判定可能な項目へ分解する
  4. 追加した項目で、過去の出力を再評価する
  5. 厳しすぎる項目は削るか、必須条件ではなく警告にする

この手順なら、評価基準を机上で作り込みすぎず、実際の失敗から更新できます。個人的には、ルーブリックは仕様書というより、運用しながら育つテストケース集に近いと感じています。

まとめ

LLMの出力改善ループでは、生成器だけを見ても品質は安定しません。評価基準が粗ければ、弱い出力が合格します。評価基準が過剰であれば、実務上は十分な出力まで不合格になります。

そのため、ルーブリックとEvalは最初から正しく作るものではなく、失敗ログで育てるものとして扱うほうが現実的です。合格したのに使えなかった出力、不合格だが許容できた出力、再生成に効いたフィードバック。この3つを残していくと、ループは単なる再試行ではなく、改善可能な運用単位になります。

次に観測へつなぐなら、最終出力だけでなく、ルーブリックのバージョン、未達項目、フィードバック、再評価結果を残す必要があります。ここまで残して初めて、「AIが良くなった」のではなく、「どの評価基準で、何が改善したのか」を説明できます。

DUOps

Author

DUOps(デュオプス)

LLMOps、Agent、MCP、Langfuse、Cloudflare 周辺の実装と運用を、個人で試しながら記録しています。

Xを見る

コメント

Related