TL;DR
- LLM(Gemini)による評価レポートの点数が毎回ブレて困ってた
- プロンプトにルーブリック(評価基準表)を組み込んで解決
- ついでに低スコア時の自動アラート機能も実装
- 評価の一貫性が劇的に改善した
背景:「なんでこんなに点数がブレるんだ...?」
業務でGemini APIを使って、音声データから評価レポートを自動生成するシステムを運用していました。最初は「LLMすげー!」って感じで喜んでたんですが、しばらく使ってると気になることが...
同じような内容なのに、点数がバラバラ。
- 月曜日の評価:82点
- 水曜日の同じような内容:67点
- 金曜日:74点
「えっ、なにこれ...」ってなりますよね。
当時のプロンプトはこんな感じでした:
prompt = """
あなたはエキスパートです。
以下のテキストを100点満点で評価してください。
【評価観点】
- 良かった点
- 改善点
- その他
"""
これ、今見ると「そりゃブレるわ」って感じなんですが、当時は「LLMが賢いから大丈夫でしょ」って思ってました。甘かった。
何が問題だったのか
根本的な問題は**「評価基準が曖昧すぎる」**こと。
LLMからすると、「良かった点って何?どれくらい良かったら何点?」っていう基準がないわけです。そりゃ毎回違う点数になりますよね。
さらに厄介なのが、低スコアの評価レポートが出ても気づくのが遅れること。通知は全部同じ形式だったので、「あ、レポートできたんだ〜」くらいの感覚で、中身をちゃんと見るのは後回しになりがちでした。
解決策を考える
調べてみると、教育現場では**ルーブリック(評価基準表)**っていう手法があるらしい。
「これだ!」と思って、プロンプトに評価基準を具体的に埋め込むことにしました。
スコア配分を決める
まず、何を評価するのか整理して、点数配分を決めました:
| 評価項目 | 配点 |
|---|---|
| ヒアリング力 | 30点 |
| 提案力 | 25点 |
| 共感・寄り添い力 | 20点 |
| インサイト発見力 | 15点 |
| 専門知識の活用 | 10点 |
各項目をさらに細分化して、合計12個のサブ項目に分けました。
5段階評価基準
そして、各項目に5段階の評価基準を設定:
| 評価 | 点数 | 基準 |
|---|---|---|
| 優秀 | 100% | 期待を大きく上回る |
| 良好 | 80% | 期待を上回る |
| 標準 | 60% | 期待通り |
| 要改善 | 40% | やや不足 |
| 不足 | 20% | 大きく不足 |
例えば「ヒアリング力 - 基本情報の聞き取り(10点満点)」なら:
- 優秀(10点): 留学目的・期間・予算・英語力・希望国を全て確認し、深掘りしている
- 良好(8点): 上記5項目中4項目を確認
- 標準(6点): 3項目を確認
- 要改善(4点): 2項目のみ
- 不足(2点): 1項目以下
ここまで具体的にすると、LLMも判断しやすくなります。
実装してみた
プロンプトの書き換え
ルーブリックをプロンプトに埋め込みました。結構長くなりますが、仕方ない:
def get_prompt(self, transcription_text):
return f"""あなたはエキスパートです。
以下の評価基準(ルーブリック)に従って100点満点で評価してください。
## 評価基準(ルーブリック)合計100点
### 1. ヒアリング力(30点)
**1-1. 基本情報の聞き取り(10点)**
- 優秀(10点): 留学目的・期間・予算・英語力・希望国を全て確認し、深掘りしている
- 良好(8点): 上記5項目中4項目を確認している
- 標準(6点): 上記5項目中3項目を確認している
- 要改善(4点): 上記5項目中2項目のみ確認
- 不足(2点): 1項目以下、またはほとんど確認していない
# ... 他の項目も同様に定義 ...
## 出力形式
# 評価レポート
## Overall Score: XX/100
## スコア内訳
| 評価項目 | 得点 | 満点 | 評価 |
|---------|------|------|------|
| ヒアリング力 | XX | 30 | 優秀/良好/標準/要改善/不足 |
| └ 基本情報の聞き取り | XX | 10 | ... |
# ... 以下同様 ...
音声書き起こしテキスト:
{transcription_text}
"""
プロンプトが長くなってトークン数増えるのが少し気になりましたが、評価の一貫性の方が重要だと判断しました。
スコア抽出機能
レポートテキストから正規表現でスコアを抜き出します。シンプル:
import re
def extract_score(self, report_text):
match = re.search(r'Overall Score:\s*(\d+)/100', report_text)
if match:
score = int(match.group(1))
return score
return None
低スコア時の自動アラート
せっかくなので、スコアが75点未満の時は自動でSlackメンションを飛ばすようにしました:
def create_and_save(self, ...):
# レポート生成
report_text = self.generate(transcription)
# スコア抽出
score = self.extract_score(report_text)
# 保存
report_file = self.save(...)
# スコアに応じて通知を切り替え
if score is not None and score < 75:
# 低スコア: メンション付き
self._send_low_score_notification(...)
else:
# 通常通知
self.send_notification(...)
Slack通知はBlock Kitで見やすく:
blocks = [
{
"type": "header",
"text": {"type": "plain_text", "text": "📊 評価レポート完了"}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"<@{USER_ID}> ⚠️ *スコア: {score}/100(要改善)*\n\n"
f"スコアが75点未満のため、ご確認ください。"
}
},
# ... ボタンなど ...
]
ハマったポイント
プロンプトの長さとコスト
ルーブリックを詳細に書くとプロンプトが長くなります。最初は「トークン数増えてコストやばいのでは」と心配しましたが、計算してみたら大したことなかったです。
むしろ、評価のブレで何度もやり直す方がコスト高かったので、結果的にはコスト削減になりました。
5段階評価の点数配分
最初は「優秀(10点), 良好(7点), 標準(5点)...」みたいに不均等な配分にしてたんですが、LLMが混乱するみたいで、素直に「100%, 80%, 60%, 40%, 20%」の等間隔にしたら安定しました。
テスト運用の重要性
いきなり本番運用せず、「【テスト】」って明記したメッセージで様子見したのは正解でした。最初の1週間くらいで何度か調整が必要だったので。
結果とわかったこと
評価の一貫性が劇的に改善
同じような内容の評価が、以前は±15点くらいブレてたのが、±3点くらいに収まるようになりました。これはデカい。
詳細スコアが超便利
12項目の詳細スコアが出るので、「どこを改善すればいいか」が一目瞭然になりました。
例えば:
- ヒアリング力:28/30(優秀)
- 提案力:15/25(標準)← ここ弱い
- 共感・寄り添い力:18/20(良好)
みたいな感じで、改善ポイントが明確になります。
低スコアへの対応が早くなった
メンション付きアラートのおかげで、問題のあるケースにすぐ気づけるようになりました。以前は「あれ、この前のレポート、実は点数低かったんだ...」って後から気づくパターンが多かったので。
意外な副次効果
ルーブリックを作る過程で、「そもそも何を評価したいのか」を整理できたのが良かったです。チーム内で「評価基準ってこれで合ってる?」って議論するきっかけにもなりました。
これから試したいこと
閾値の動的調整
今は75点未満でアラートですが、環境変数で調整できるようにしてあります:
ALERT_THRESHOLD = int(os.environ.get('SCORE_ALERT_THRESHOLD', '75'))
運用しながら、最適な閾値を探っていく予定。
評価基準の定期的な見直し
ビジネス要件が変われば評価基準も変わるはず。四半期に1回くらい見直す仕組みを作りたいですね。
まとめ
LLMの評価のブレ、「まあLLMだし仕方ないか」って諦めてたんですが、ルーブリックを入れるだけでかなり改善しました。
ポイントは:
- 評価基準を具体的に(数値で)示すこと
- 5段階評価は等間隔がおすすめ
- スコアを抽出して活用すると色々できる
LLMを業務で使う時、「賢いから適当に指示しても大丈夫でしょ」って思いがちですが、人間と同じで「明確な基準」があった方が良い仕事してくれるんだなと実感しました。
同じような課題で困ってる人の参考になれば幸いです。
参考
技術スタック
- Python 3.14
- Google Cloud Run
- Vertex AI (Gemini 2.5 Pro)
- Slack API
- Google Drive API / Firestore