#はじめに、
この記事は、chatgpt workを使って基本自動生成しています。
その上で不足記事を追加・修正した上で、動作確認を行っています。
この企画はもともと公式のMLFlowのチュートリアルがちょっとイケてなかったので、chatgpt workを使ってもっと簡単なチュートリアルを作成出来るか検証してみようと思い立ったところから開始しました。
そしてせっかくやるなら面白いテーマでやろうと思い、「生成AIで面白いなろう系小説第一話を書けるのか?」
という実験も兼ねています。
今回はトライアンドエラーを何度か繰り返しました。
まず自動生成による評価項目に、4つから5ぐらいの条件をANDEで判定しても、ほとんどの評価項目がTrueになる現象が出た。
このままなにが正解かを具体的に例示しないと進まない可能性がありつつ、例示するよりもchatgpt workに3つぐらいの有名小説の第一話から共通する点を評価項目として抽出してもらった。
ここで、true/falseから五段階評価方式に変わったが、それでもクソつまらない第一話の評価点が高い状態であることをchatgpt workに指摘したことで、加点方式になっていたことが発覚。
Gate方式に変更したことで、面白くない状態と評価点が大体釣り合った。
ただこの評価が適切なのか確認するために、有名小説の第一話を評価したところ、
今度は評価が厳しすぎて、同じような低評価になってしまった。
どうやら、第一話の出だしの手法がそれぞれ異なるためにあまり良い評価基準にならなかったようだ。
第一話の進め方は複数カテゴライズした上で、評価基準を
作った方が良さそうです。
そこでにた系統の2作品に絞り、そして第一話がプロローグになっていることも踏まえ、2話分含めて
評価基準の抽出を行った。
この時点で当初の1200文字で作成するというのが非現実的になっていますが、その改善は次の記事で改善する
今回の評価基準の調整によって現在はこのようになりました。
今回のプロンプトの荒さから考えると、まだv1の評価点が高いとは思いますが、この段階ではご愛敬とさせてください。
| 対象 | Quality平均 | 分布 |
|---|---|---|
| Prompt Version 1生成文 | 2.88 | 2点×1、3点×7 |
| Re:ゼロ プロローグ | 3.75 | 3点×2、4点×6 |
| 無職転生 プロローグ | 3.88 | 3点×1、4点×7 |
ここから自動生成部分です。
MLflow GUIで始めるプロンプト改善 #2-1:LLM Judgeの評価構造を見直す
STEP2では、Prompt Version 1で生成した3件の第一話をコード評価とLLM Judgeで評価しました。最初の二値JudgeはほとんどがTrueになり、5段階へ変更した後も生成文へ3〜5点が多く付きました。
そこで基準を厳しくすると、今度は公開済みの人気作品まで平均2点台になりました。点数を下げることには成功しても、人が読む品質との対応を失っています。
STEP2-1ではPromptと生成本文を変更せず、評価を次の3層へ分離します。
- 入力仕様への適合:Yes/No
- 作品としての品質:1〜5点
- 作風・構成上の特徴:1〜5の診断値
STEP3で生成Promptを変更する前にJudgeを固定します。PromptとJudgeを同時に変更すると、スコア差が生成品質によるものか採点条件によるものか分からなくなるためです。
今回のゴール
- 二値評価と一律な厳格化が失敗した理由を確認する
- 第一話がシリーズ内で担う役割を評価入力へ追加する
- 仕様適合、品質、作風診断を分離する
- プロローグを「本編が始まっていない」という理由で減点しない
- 人の評価とJudgeのRationaleを比較できる形にする
- 校正できた評価構造だけをSTEP3以降へ引き継ぐ
対象となる生成Promptと本文はSTEP2から変更しません。
Prompt: prompts:/step01-narou-episode-writer/1
Dataset: step2-narou-evaluation-v1
1. 5段階にするだけでは解決しなかった
最初のJudgeは「要素が存在するか」をYes/Noで判定していました。そのため、驚きや困惑が一文あるだけでもemotional_realism=Trueになり、能力が発動しただけでもability_awakening=Trueになりました。
そこで1〜5点へ変更し、次のような共通上限を追加しました。
- 定型的、説明的、受け身なら2点以下
- 重要な因果の省略や具体性不足が一つでもあれば3点以下
- 4点は重要な弱点が一つもない場合だけ
この変更後、生成文の過大評価は減りました。しかし、すべての観点へ同じ上限を適用したため、別の誤りが発生しました。
たとえば回想が長い作品に対し、opening_hookだけでなくprotagonist_agencyやdialogue_functionまで「説明的」という同じ理由で減点します。一つの弱点が全Judgeへ伝播し、13項目あるように見えて実際には同じ印象を繰り返し採点していました。
2. 公開作品を反例として確認する
現行Judgeを変更する前に、次の3作品を共通13項目へ通しました。
結果はそれぞれ平均2.77、2.54、2.46で、4点はRe:ゼロのopening_hookだけでした。
ここで重要なのは、公開作品だから自動的に高得点へ合わせることではありません。Judgeの誤った減点理由を見つける反例として使います。
Re:ゼロの第一話は未来の危機を先に見せるプロローグです。第二話の第一章1「ギザ十は使えない」から異世界へ移った現在の出来事が始まります。第一話で「彼女」の関係や世界設定を説明しないことは、情報不足ではなく疑問を作る構成です。
無職転生も第一話は転生前の人生と死を描くプロローグです。第二話の第一話「もしかして:異世界」から転生後の認識が始まります。現行Judgeはプロローグに異世界知識がないことを理由にknowledge_grounding=1としましたが、まだ登場していない知識を採点対象にした誤判定です。
この確認から、第一話に常に次を要求する構造が誤りだと分かりました。
- 異世界の制度や知識
- 能力発現
- 主人公の明示的な当面目標
- 主体的な選択
- 本編の主要設定
- 次話の具体的な行動予定
これらは作品によっては必要ですが、プロローグの普遍的な必須条件ではありません。
3. 第一話の役割を評価入力へ追加する
「第一話」という話数だけでは役割を判断できません。Datasetの入力に次の2項目を追加します。
{
"episode_role": "main_story_opening",
"episode_intent": "異世界到着後の最初の問題を立ち上げる"
}
| 値 | 役割 |
|---|---|
prologue |
本編前の人物、事件、問い、転換を準備する |
cold_open |
後の危機や結果を先に示し、疑問を作る |
main_story_opening |
本編の現在時点を開始し、当面の問題を立ち上げる |
今回の生成Datasetは、異世界到着後の出来事を生成させているためmain_story_openingを指定します。公開作品を評価するときは、Re:ゼロと無職転生の第一話をprologueとして扱います。タイトルだけから自動推定するのではなく、評価者が本文の役割を確認して記録します。
4. 評価を3層へ分離する
Layer 1:入力仕様への適合
入力との一致は品質の程度ではなく、要件を満たしたかという問題です。ここはYes/Noを維持します。
| Judge | 判定内容 |
|---|---|
setting_consistency |
主人公、能力、世界、到達点と重要な矛盾がない |
ability_requirement |
能力発現が入力上の必須要件なら満たしている |
no_plot_spoilers |
episode_goalを越えて長期展開を完了していない |
入力に能力発現の必須指定がなければ、能力が登場しなくてもability_requirementをFailにしません。公開作品のように比較用入力がない場合、このLayerは実行しません。
Layer 2:作品としての品質
品質は8項目を1〜5点で評価します。
| Judge | 評価内容 |
|---|---|
narrative_coherence |
選んだ範囲内で出来事、認識、反応、変化を追えるか |
characterization |
語りや反応から人物固有の声、価値観、弱さが伝わるか |
emotional_credibility |
文体と話の長さに応じた感情・判断のつながりがあるか |
scene_execution |
具体描写、要約、焦点化の配分が狙いに寄与するか |
opening_engagement |
危機、人物の声、疑問、雰囲気などで読む理由を作るか |
information_control |
必要情報と意図的に伏せる情報を制御できているか |
reader_momentum |
疑問、感情、危機、変化、約束が次を読む動機になるか |
episode_role_fulfillment |
指定した話の役割を果たしているか |
| 点数 | 判定 |
|---|---|
| 1 | 観点が成立せず、読解を妨げる重大な破綻がある |
| 2 | 一部は機能するが、観点全体を損なう大きな弱点がある |
| 3 | 十分に機能する。目立つ改善余地はあるが狙いは伝わる |
| 4 | 強く機能する。二つ以上の具体的な表現・構成上の工夫が相互に効果を高める |
| 5 | 卓越して機能し、表現、人物、展開の結びつきが特に優れる |
3点を「成立している通常の完成水準」とします。4点を「欠点が一つもない作品」に限定しませんが、必要要素が揃っているだけでは4点にしません。Judgeは担当観点内の具体的な工夫を二つ以上特定し、それらが単純な順序説明や要素充足より強い理由を示します。一方、作品の知名度を理由に点を上げることも禁止します。
各Judgeは担当観点だけを評価します。たとえば回想が長いことは、回想の配分が場面効果を損なう場合にscene_executionで扱います。同じ理由をcharacterizationやreader_momentumへ自動適用しません。
生成文の定型的な要素をJudgeが過大評価しないよう、上限は各観点の中で個別に設定します。
| 観点 | その観点だけに適用する上限例 |
|---|---|
narrative_coherence |
時系列に並べただけ、都合よい人物・道具・能力で進むなら3以下 |
characterization |
経歴の言い換えと一般的反応だけなら2以下 |
emotional_credibility |
感情ラベルと即時受容が中心なら2以下 |
scene_execution |
一般的な形容、要約、短い場面の連結が中心なら3以下 |
opening_engagement |
疲れた日常、光、転生を提示しただけなら2以下 |
information_control |
案内役や内なる声が設定をまとめて教えるなら2以下 |
reader_momentum |
「村へ行く」「助ける」など次の予定だけなら3以下 |
episode_role_fulfillment |
指定要素を順番に消化しただけなら3以下 |
これは以前の「弱点が一つでも全項目3以下」という共通上限とは異なります。たとえば案内役の説明はinformation_controlで評価しますが、それだけを理由にemotional_credibilityまで下げません。
さらに、3点と4点の境界を文章だけでなく、匿名化した構成アンカーで示します。
- 2〜3点側:疲弊した会社員、転生願望、光、色鮮やかな異世界、案内役、光る道具、能力説明、村を助ける宣言を順番に置いた導入
- 4点以上になり得る側:身体危機、固有の語り、伏せた人物関係、誓いが一場面で相互に働き、伏せた情報自体が疑問を作る導入
- 4点以上になり得る側:長い回想が人物固有の自己正当化と後悔を作り、最後の代償ある選択へ収束する導入
「赤い現実と青い空の対比」「道具が光る」「次の任務が示される」などは、それだけで4点の複合的な工夫とは数えません。逆に、回想が長いという形式だけで3点以下にも制限しません。作品名や成功実績ではなく、本文内で同等の機能が成立しているかを比較します。
Layer 3:作風・構成の診断値
次の項目は高ければ良いという品質指標ではありません。
| Diagnostic | 1〜5が表すもの |
|---|---|
agency_level |
主人公が展開を変える選択を行う度合い |
exposition_level |
要約、回想、設定説明が本文を占める度合い |
reveal_explicitness |
設定、目標、因果を明示する度合い |
意図的に受動的な主人公、回想中心のプロローグ、情報を伏せるコールドオープンも成立します。Diagnosticの平均や閾値を品質合否へ混ぜません。
5. NotebookでJudgeを作成する
Gateway接続は従来と同じです。
from typing import Literal
import mlflow
from mlflow.genai.scorers import Guidelines
from mlflow.genai.judges import make_judge
JUDGE_ENDPOINT_NAME = "openai-o4-mini-endpoint"
JUDGE_MODEL = f"gateway:/{JUDGE_ENDPOINT_NAME}"
仕様適合はGuidelines、品質と診断値はmake_judge()で作成します。
def build_requirement_judges():
return [
Guidelines(name=name, model=JUDGE_MODEL, guidelines=criterion)
for name, criterion in REQUIREMENT_CRITERIA.items()
]
def build_quality_judges():
return [
make_judge(
name=name,
model=JUDGE_MODEL,
feedback_value_type=Literal[1, 2, 3, 4, 5],
instructions=(
f"{QUALITY_SCALE}\n\n評価観点: {criterion}\n"
"評価コンテキスト: {{ inputs }}\n対象本文: {{ outputs }}"
),
)
for name, criterion in QUALITY_CRITERIA.items()
]
既存Traceを直接再評価すると過去のAssessmentが同じTraceへ累積するため、入力と出力だけをコピーして新しい評価専用Traceを作ります。
version1_outputs = version1_traces[["request", "response"]].rename(
columns={"request": "inputs", "response": "outputs"}
).copy()
6. 同じ生成本文を再評価する
評価直前にJudgeを再生成し、古いNotebookカーネルに残ったScorerを使わないようにします。
requirement_judges = build_requirement_judges()
quality_judges = build_quality_judges()
diagnostic_judges = build_diagnostic_judges()
version1_baseline = mlflow.genai.evaluate(
data=version1_outputs,
scorers=[
within_1200_characters,
non_empty,
*requirement_judges,
*quality_judges,
*diagnostic_judges,
],
)
MLflow GUIでは3層を混ぜずに確認します。
| 層 | 確認方法 |
|---|---|
| Requirement | Falseになった具体的な入力違反を確認する |
| Quality | 人の1〜5点との差と、担当観点内のRationaleを確認する |
| Diagnostic | 作品の特徴として確認し、合否へ使わない |
平均点だけで校正完了とは判断しません。少なくとも次を確認します。
- 本文にない心理や因果を補っていない
- 意図的な情報制限を欠落と誤認していない
- 評価対象のない異世界知識や会話を要求していない
- 一つの一般的な欠点を全項目で繰り返し減点していない
- 3点、4点、5点の理由が担当観点内で区別されている
7. 参考作品の使い方
参考作品は「必ず4点以上にする正解データ」ではありません。次の誤判定を検出する回帰テストとして利用します。
| 作品 | 確認する誤判定 |
|---|---|
| Re:ゼロ プロローグ | 伏せられた人物関係を単純な説明不足としない |
| 無職転生 プロローグ | 転生前なので異世界知識がないことを減点しない |
| 嘆きの亡霊 第一話 | 主人公の受動性だけを総合品質の低さに変換しない |
冗長さ、表現上の弱点、読者との相性まで無条件に無視するわけではありません。該当する品質観点だけで、本文根拠を示して評価します。
観点別上限と構成アンカーを追加した後、同じJudgeモデルで回帰確認しました。
| 対象 | Quality 8項目の平均 | 分布 |
|---|---|---|
| Prompt Version 1の生成文 | 2.88 | 2点×1、3点×7 |
| Re:ゼロ プロローグ | 3.75 | 3点×2、4点×6 |
| 無職転生 プロローグ | 3.88 | 3点×1、4点×7 |
生成文は、案内役と内なる声による情報供給をinformation_control=2、その他を主に3点と判定しました。公開プロローグ側では、異世界知識の不在や明示目標の不在による無関係な減点がなくなりました。
この3件だけでJudgeの妥当性が証明されたわけではありません。LLM Judgeには実行ごとの揺らぎもあります。ここでは、過大評価していた生成文と、異なる構成を持つ反例を区別できるところまで構造を修正できた、と限定して判断します。
8. STEP3とSTEP4へ引き継ぐもの
人の評価との一致を確認できたら、次を固定します。
- 同じEvaluation Dataset
- 同じ
episode_roleとepisode_intent - 同じRequirement、Quality、Diagnostic
- 同じJudgeモデルとGateway Endpoint
- 同じ1,200文字条件
STEP3では生成Promptだけを変更します。文字数を5,000文字へ増やす結論は、この評価見直しからは導けません。文字数緩和は、同じPromptと評価構造を使った独立実験として扱います。
まとめ
最初の二値Judgeは要素の存在だけでPassし、厳格化した5段階Judgeは一つの弱点を全項目へ広げました。さらに、すべての第一話へ異世界知識、能力、主体的行動、明示的目標を要求したため、プロローグを本編導入の欠落として誤判定しました。
STEP2-1では評価を、仕様適合、品質、作風診断の3層へ分離します。品質Judgeは話の役割を入力として受け取り、自分の担当観点だけを評価します。公開作品は点数を高くするためではなく、構成の多様性を誤って欠陥扱いしないことを確認する反例として使います。
この構造で人の評価とRationaleが合うことを確認できるまで、Version 1の正式な基準値は確定しません。
参考資料
- MLflow: Evaluation Quickstart
- MLflow: LLM Judges and Scorers
- MLflow: Evaluate Existing Traces
- MLflow Python API: search_traces
出力された第一話
この段階だと、全然面白くないのは変わらない。
そもそも出力プロンプトは何も調整していないので、当たり前ですが。
第一話:異世界の目覚め
佐藤蓮は、今日も終電間際のオフィスでパソコンの画面を睨みつけていた。電光掲示板は深夜を告げる赤い数字を映し出している。彼の目に映るのは、疲れ切った自分と、どこか遠くにある夢の欠片だけだった。そんな彼の心の片隅には、「異世界に転生してみたい」という憧れが渦巻いていた。
その瞬間、目の前が眩い光に包まれた。目を開けると、彼は全く知らない場所に立っていた。周囲には、青い空と、まばゆい花々、そして、遠くで物々交換をしている人々の姿が見える。目の前には大きな木が立っており、その枝には様々な魔法の道具がぶら下がっていた。
「ここは……異世界?」佐藤は自問自答する。驚きと興奮が入り混じる中で、彼は周囲を見回した。だが、思いを巡らせている暇はなかった。後ろから、何かの気配を感じたからだ。
振り向くと、彼の背後には一人の少年が立っていた。彼は黒い髪を短く刈り、目はキラキラとした青色をしている。「お、お前、誰?」少年は訝しげに尋ねた。
「俺は……佐藤蓮。ここはどこなんだ?」彼は緊張しながら答えた。
「ああ、異世界のエルメリア王国だ。冒険者ギルドが近くにあって、君みたいな転生者はよく見かけるよ!」少年の言葉に、佐藤は自分が本当に異世界に来たのだと心が高鳴った。
「君が異世界に来た理由は何だ?」少年は興味津々な表情を浮かべた。
「特に、目的はない。ただ、もう何もかも諦めていたんだ」と佐藤はぽつりと呟く。すると、少年は目を輝かせて言った。「君、冒険者になるんだ!それじゃあ、最初の仕事を手伝ってくれ!」
その瞬間、自分の足元に目をやると、金色の剣が転がっていた。何かの拍子で動かしてしまったのか、鍛えられた魂がずっと彼の知らぬ間に存在していたのか?指を伸ばし、剣に触れると、なぜかそれが彼の手の中で温かい輝きを放ち始めた。すると、彼はふと、自分の内に秘めた力に気づいた。
「触れた道具の性能を……最大化する?」心の中に響く声。それは、彼が長年働いてきたブラック企業でのストレスをも忘れさせるほどの、清々しい感覚だった。
「おい、どうしたんだ?」少年の問いかけが響く。
「この剣、すごく力強くなった。どうしてだ?」驚きのあまり言葉が詰まる。
「それは君の能力だ!触れた道具の性能を引き出すなんて、すごいじゃないか!」少年は目を輝かせて言った。
佐藤は剣を握りしめ、その力に興奮を覚えた。今までの現実を忘れ、彼は新たな人生の可能性に胸を高鳴らせる。異世界での冒険が始まる予感がした。
「まずは、この剣を使って近くの村を助けよう!あの村には、壊れた道具がたくさんある。君の力を使えば、村を復興できるかもしれない!」少年の提案に、胸が熱くなる。何か大きな未来が待っている気がした。
「よし、行こう!」二人は手を取り合い、村へと向かう。その先に待っているものが何なのか、まだ彼にはわからなかった。しかし、心のどこかに希望が光っていることだけは確かだった。
jupyter notebookの各セル
STEP2の生成済み本文を再利用し、評価を仕様適合・品質・作風診断の3層へ再設計します。Promptと生成本文は変更しません。
from typing import Literal
import mlflow
from openai import OpenAI
from mlflow.genai import scorer
from mlflow.genai.scorers import Guidelines
from mlflow.genai.judges import make_judge
TRACKING_URI = "http://127.0.0.1:5000"
EXPERIMENT_NAME = "STEP1:MVPプロンプトの作成と実行"
JUDGE_ENDPOINT_NAME = "openai-o4-mini-endpoint"
JUDGE_MODEL = f"gateway:/{JUDGE_ENDPOINT_NAME}"
mlflow.set_tracking_uri(TRACKING_URI)
experiment = mlflow.set_experiment(EXPERIMENT_NAME)
STEP2の既存トレースを取得する
MLflow GUIでSTEP2のEvaluation runを開き、Run IDをコピーして貼り付けます。
STEP2_RUN_ID = "af1256a3a88f4766b85255ed98050ec7"
version1_traces = mlflow.search_traces(
run_id=STEP2_RUN_ID,
return_type="pandas",
)
assert len(version1_traces) == 3, f"3件の生成トレースを想定しています: {len(version1_traces)}件"
version1_traces[["trace_id", "request_time"]]
# Trace自体を再利用すると過去のAssessmentも同じ画面に累積される。
# 入出力だけをコピーし、校正後評価専用の新しいTraceを作る。
version1_outputs = version1_traces[["request", "response"]].rename(
columns={"request": "inputs", "response": "outputs"}
).copy()
version1_outputs
決定的なコード評価
文字数と空回答の判定は曖昧さがないため、STEP2から変更しません。
@scorer
def within_1200_characters(outputs: str) -> bool:
return len(outputs) <= 1200
@scorer
def non_empty(outputs: str) -> bool:
return bool(outputs.strip())
評価を3層へ分離する
入力仕様への適合はYes/No、作品としての品質は1〜5点、作風上の特徴は診断値として扱います。低い主体性や少ない説明量を、それだけで品質不良とは判定しません。
REQUIREMENT_CRITERIA = {
"setting_consistency": (
"入力で指定された主人公、能力、世界観、episode_goalと矛盾しない。"
"入力にない要素があるだけでは不合格にせず、重要な矛盾がある場合だけ不合格とする。"
),
"ability_requirement": (
"入力で固有能力の発現が第一話の必須要件になっている場合、その要件を満たす。"
"必須要件でない場合、能力が登場しないことを理由に不合格にしない。"
),
"no_plot_spoilers": (
"入力のepisode_goalを越えてfuture_plotを完了済みの出来事として描かない。"
"伏線、予感、未達成の目標として触れることは許容する。"
),
}
QUALITY_SCALE = (
"作品の知名度や成功実績を推測せず、提示本文だけを評価する。"
"1: 観点が成立せず、読解を妨げる重大な破綻がある。"
"2: 一部は機能するが、観点全体を損なう大きな弱点がある。"
"3: 十分に機能する。目立つ改善余地はあるが、狙いは読者へ伝わる。"
"4: 強く機能する。単に必要要素が揃うだけでなく、少なくとも二つの具体的な表現・構成上の工夫が相互に効果を高めている。"
"5: 卓越して機能し、表現、人物、展開の結びつきが特に優れている。"
"3を、成立している通常の完成水準とする。4を付ける場合、担当観点内の具体的な工夫を二つ以上特定し、それが単純な順序説明や要素充足より強い理由を述べる。"
"4点は『欠点が一つもない』ことを要求しないが、ありふれているという一言だけを弱点にして4点を正当化してはならない。"
"担当観点と無関係な欠点で減点しない。同じ一般的欠点を全Judgeへ横断適用しない。"
"会話、異世界知識、能動的行動、明示的な目標が存在しないこと自体を欠点にしない。"
"プロローグ、コールドオープン、回想中心など、入力のepisode_roleに適した効果を評価する。"
"情報を意図的に伏せた謎と、理解に必要な情報の欠落を区別する。"
"Rationaleは、本文根拠、観点内の長所、観点内の弱点、最終点の順に書く。"
)
QUALITY_CRITERIA = {
"narrative_coherence": (
"この話が選んだ範囲の中で、出来事、認識、反応、変化を読者が追えるか。"
"主人公が受け身でも、危機の提示や認識の変化が因果的なら評価する。"
"出来事を時系列に並べただけ、または都合よい人物・道具・能力が次々現れて進む場合は3以下とする。"
),
"characterization": (
"語り、知覚、反応、選択から、その人物に固有の価値観、弱さ、欲求、声が伝わるか。"
"好感度の高さや英雄的行動を必須にしない。入力設定や経歴の言い換えと一般的な反応だけなら2以下、固有性が一場面だけなら3以下とする。"
),
"emotional_credibility": (
"作品の文体と場面の長さに照らして、刺激、解釈、感情、判断のつながりに納得できるか。"
"感情を詳しく説明しない作風も認め、行動や感覚から読み取れる場合は評価する。"
"『驚いた』『興奮した』『清々しい』などの感情ラベルと即時受容が中心なら2以下、刺激から判断までが説明通りに一直線なら3以下とする。"
),
"scene_execution": (
"具体的な感覚、行動、焦点化、要約の配分が、その話の狙いを効果的に伝えるか。"
"回想や要約を一律に減点せず、目的に対して冗長または混乱を招く場合だけ減点する。"
"一般的な形容、感情の要約、設定を消化する短い場面の連結が中心なら3以下とする。"
),
"opening_engagement": (
"冒頭が、危機、人物の声、状況、疑問、雰囲気などの方法で読み進める理由を作るか。"
"事件の即時発生だけを唯一の正解にしない。疲れた日常、光、転生など定番要素を提示しただけなら2以下、具体的な問いや人物固有の緊張へ結びつく場合でも単純なら3以下とする。"
),
"information_control": (
"読者が今必要な情報は与え、後で明かす情報は意図的な疑問として制御できているか。"
"異世界設定をまだ提示しないプロローグや、正体を伏せるコールドオープンを欠落扱いしない。"
"案内役が名称・制度をまとめて説明する、内なる声が能力名と効果を教えるなど、情報が都合よく供給される場合は2以下とする。"
),
"reader_momentum": (
"話の終わりに、疑問、感情、危機、変化、約束のいずれかが残り、次を読む動機になるか。"
"次の具体的行動や明示的な目標だけを必須にしない。『村へ行く』『助ける』『冒険が始まる』など次の予定を宣言しただけなら3以下とする。"
),
"episode_role_fulfillment": (
"入力のepisode_roleとepisode_intentに照らし、この話がシリーズ内で担う役割を果たすか。"
"prologueなら本編開始前の人物・事件・問い・転換の準備を評価し、本編相当の設定説明や目標達成を要求しない。"
"指定要素を順番に消化しただけなら3以下とし、役割達成の強さは人物・場面・情報提示が結びつく実行品質で判断する。"
),
}
CALIBRATION_ANCHORS = (
"境界例A(通常は2〜3): 疲弊した会社員、転生願望、光、色鮮やかな異世界、案内役の説明、"
"触れた道具の発光、内なる声による能力説明、村を助ける宣言を順番に置いた導入。"
"赤い現実と青い空の対比、光る道具、次の任務の提示は、それだけでは4点の複合的な工夫に数えない。"
"境界例B(4以上になり得る): 瀕死の身体感覚、軽口を交えた固有の語り、伏せられた人物関係、"
"救うという誓いが一つの危機場面で相互に働き、情報を伏せること自体が次の疑問を作る。"
"境界例C(4以上になり得る): 長い回想でも、人物固有の自己正当化と後悔が、最後の代償ある救助選択へ収束し、"
"冒頭から結末までの意味を変える。長さや説明量だけで評価せず、各観点への働きで判断する。"
"アンカーの作品名や成功実績を推測してはならない。類似要素ではなく、本文内で同等の機能が成立するかを比較する。"
)
DIAGNOSTIC_CRITERIA = {
"agency_level": "主人公が展開を変える選択を行う度合い。低得点を品質不良とは扱わない。",
"exposition_level": "要約、回想、設定説明が本文を占める度合い。高得点を品質不良とは扱わない。",
"reveal_explicitness": "設定、目標、因果を明示する度合い。低得点を情報不足と同一視しない。",
}
DIAGNOSTIC_SCALE = (
"これは品質評価ではなく特徴量である。1は非常に低い、2は低い、3は中程度、"
"4は高い、5は非常に高い。良し悪しの語を使わず、本文根拠だけを説明する。"
)
def build_requirement_judges():
return [
Guidelines(name=name, model=JUDGE_MODEL, guidelines=criterion)
for name, criterion in REQUIREMENT_CRITERIA.items()
]
def build_quality_judges():
return [
make_judge(
name=name,
model=JUDGE_MODEL,
feedback_value_type=Literal[1, 2, 3, 4, 5],
instructions=(
f"{QUALITY_SCALE}\n\n{CALIBRATION_ANCHORS}\n\n評価観点: {criterion}\n"
"評価コンテキスト: {{ inputs }}\n対象本文: {{ outputs }}"
),
)
for name, criterion in QUALITY_CRITERIA.items()
]
def build_diagnostic_judges():
return [
make_judge(
name=name,
model=JUDGE_MODEL,
feedback_value_type=Literal[1, 2, 3, 4, 5],
instructions=(
f"{DIAGNOSTIC_SCALE}\n\n診断観点: {criterion}\n"
"評価コンテキスト: {{ inputs }}\n対象本文: {{ outputs }}"
),
)
for name, criterion in DIAGNOSTIC_CRITERIA.items()
]
requirement_judges = build_requirement_judges()
quality_judges = build_quality_judges()
diagnostic_judges = build_diagnostic_judges()
assert len(requirement_judges) == 3
assert len(quality_judges) == 8
assert len(diagnostic_judges) == 3
print(f"requirements={len(requirement_judges)}, quality={len(quality_judges)}, diagnostics={len(diagnostic_judges)}")
Gateway接続を1件だけ確認する
全件評価の前にJudgeを1回だけ呼び、Endpoint名とGateway側の認証設定を確認します。
gateway_client = OpenAI(
base_url=f"{TRACKING_URI}/gateway/mlflow/v1",
api_key="not-needed",
)
gateway_response = gateway_client.chat.completions.create(
model=JUDGE_ENDPOINT_NAME,
messages=[{"role": "user", "content": "1+1を数字だけで答えてください。"}],
)
print(gateway_response.choices[0].message.content)
quality_judges = build_quality_judges()
assert all(judge.model == JUDGE_MODEL for judge in quality_judges)
print({judge.model for judge in quality_judges})
gateway_check = quality_judges[0](
inputs={
"episode_role": "prologue",
"episode_intent": "本編前の危機を示し、疑問を残す",
},
outputs="主人公は倒れ、名も分からない相手を救うと誓った。",
)
gateway_check
Prompt Version 1を再評価する
predict_fnを渡さず、同じ3件の生成本文へ新しい3層評価を適用します。Datasetの各入力にはepisode_roleとepisode_intentを追加してください。今回の生成課題は、異世界到着後の本編導入を求めているためmain_story_openingを指定します。
# カーネルに残った古いScorerを使わないよう、評価直前に必ず再生成する
requirement_judges = build_requirement_judges()
quality_judges = build_quality_judges()
diagnostic_judges = build_diagnostic_judges()
# requestがdictの場合だけ、役割情報を追記する。文字列化済みの場合は元Dataset側へ追加する。
def add_episode_context(value):
if isinstance(value, dict):
return {
**value,
"episode_role": value.get("episode_role", "main_story_opening"),
"episode_intent": value.get(
"episode_intent",
"異世界到着後の最初の出来事を描き、主人公の当面の問題を立ち上げる",
),
}
return value
version1_outputs["inputs"] = version1_outputs["inputs"].map(add_episode_context)
version1_baseline = mlflow.genai.evaluate(
data=version1_outputs,
scorers=[
within_1200_characters,
non_empty,
*requirement_judges,
*quality_judges,
*diagnostic_judges,
],
)
print(f"evaluation_run_id: {version1_baseline.run_id}")
version1_baseline.metrics
この実行は数分待ちます