#はじめに、
この記事は、chatgpt workを使って基本自動生成しています。
その上で不足記事を追加・修正した上で、動作確認を行っています。
この企画はもともと公式のMLFlowのチュートリアルがちょっとイケてなかったので、chatgpt workを使ってもっと簡単なチュートリアルを作成出来るか検証してみようと思い立ったところから開始しました。
そしてせっかくやるなら面白いテーマでやろうと思い、「生成AIで面白いなろう系小説第一話を書けるのか?」
という実験も兼ねています。
前回までの振り返り
第一回目は、まずは荒いプロンプトで実行、トレース出来るところまで確認しました。
第二回目は、まずは荒い評価基準を導入し、出力した第一話を評価出来るところまで確認しました。
第二の一回目は、急遽評価基準の精度を改善することにしました。
ここがかなり試行錯誤した上で、ようやく及第点(今のところ)の評価基準精度になりました。
第三回目は、改善したプロンプトを作成し、
第四回目は、バージョン毎のプロンプト精度の比較をしてみました。
比較した結果、プロンプトの精度と評価自体にそれぞれ問題がある結果となっていた事からの今回の改善です。
今回もトライアンドエラーを何度か繰り返しました。
STEP3で作成したプロンプトでは、5000文字前後まで文字が伸びていない。内容としても描写が足りていないので唐突感もある。
そこで描写を増やし、唐突感を減らすために、5個ぐらいの場面設定から4から5つ採用して描写するように修正したプロンプトをversion3としました。
しかしそれでも文字数が伸びない現象が発生。
| 条件 | 平均文字数 | Quality平均 |
|---|---|---|
| Version 2 | 1,768 | 3.083 |
| Version 3 | 1,564 | 3.167 |
そこでversion4として、構成案生成と本文生成を分ける二段階生成に変更しました。
結果:
- 単一呼び出し Version 3:平均1,564文字
- 二段階Pipeline:平均5,865文字
- 4,500〜8,000文字:3件すべてPass
- Quality平均:3.250 → 3.167
- 設定整合性と先取り抑制に各1件の回帰
構成生成と4場面の本文生成を分割することで文字数問題は解決しました。
しかし精度は逆に下がる現象が発生した。
ここでchatgpt workと対話し、原因を深掘りした結果、二段階構成にしたことで、
描写が増えたのでなく、出来事が増えたので深みが出なかったという結論に至る。
そこで
改善策1:Episode Contractを先に作り、
改善策2:構成を「出来事」から「変化曲線」へ変える
ことで質を上がるか検証してみた
結果はさらに下がってしまった。
| 条件 | 平均文字数 | Quality平均 | 4,500文字以上 | 設定整合性 | 先取り抑制 |
|---|---|---|---|---|---|
| 二段階Control | 5,865 | 3.208 | 3/3 | 3/3 | 3/3 |
| 状態管理Candidate | 5,322 | 2.833 | 2/3 | 3/3 | 3/3 |
かなりイケると思ったのに、なぜ?
過剰制約と検査器の誤判定により品質が低下しました。
特に全文検査の指摘を無条件で修正へ流した結果がこのていたらく
MLflow GUIで始めるプロンプト改善 #4-1:場面構成と文字数範囲を明示して再評価する
STEP4では、Prompt Version 2をVersion 1と比較しました。Version 2 fullはinformation_controlとscene_executionで改善したものの、Quality平均は3.125から3.208への小幅な変化でした。
さらに大きな問題は文字数です。Version 2は「5,000文字を目安とし、必要なら最大8,000文字」と指示しましたが、実際の3件は1,564〜1,906文字、平均1,768文字でした。
max_output_tokens=12000を指定しているため、APIの出力上限が1,800文字付近にあるわけではありません。Version 2のPromptが、一つの中心場面、必要な兆候は一つ、説明や反復を避ける、といった圧縮方向の指示を多く含み、モデルが短い本文で条件を満たしたと判断した可能性があります。
そこでSTEP4-1では、Prompt Version 3を作成します。最低4,500文字、最大8,000文字を明示し、一つの中心問題を4〜5個の場面単位で進める構造へ変更します。
実行結果は、Version 3でも1,329〜1,753文字、平均1,564文字でした。Version 2より長くなるどころか、平均で約204文字短くなっています。Traceでは3件とも正常完了しており、トークン上限による打ち切りではありません。
この結果から、今回のモデルと単一呼び出しでは、文字数範囲と場面表をPromptへ追加するだけでは5,000文字前後の本文を安定して生成できないと判断します。
そこで同じSTEP4-1内で、構成生成と場面別本文生成を分ける二段階Pipelineも実行しました。4場面を個別に生成した結果は5,781〜5,984文字となり、3件すべてが4,500〜8,000文字を満たしました。ただしQuality平均は3.167で、設定整合性と先取り抑制に各1件の回帰があります。文字数問題は解決しましたが、Pipelineを採用するには場面間の制約伝播を改善する必要があります。
STEP4-1は成功したPromptへ差し替える記事ではありません。「構造を詳しくすれば長くなる」という仮説を実行し、反証された結果をMLflowへ残します。
今回のゴール
- Version 2が短く終わった原因について仮説を立てる
- 4〜5場面と4,500〜8,000文字を指定したVersion 3を登録する
- Version 3を同じ3企画で実行する
- Version 2と同じJudgeで比較する
- API上限による終了か、モデルの自発終了かをTraceで確認する
- 単一呼び出しが失敗した場合、二段階Pipelineを実行して採否を判断する
1. Version 2が短くなった理由を整理する
Version 2には次の文字数指示がありました。
- 5,000文字を目安とし、場面を成立させるために必要な場合だけ8,000文字まで許容する
- 5,000文字で場面が成立する場合は、8,000文字へ近づけるための回想・設定説明・反復を追加しない
一見すると5,000文字を書く指示ですが、「目安」「必要な場合」「水増ししない」という表現により、モデルは短く完結することを選べます。
同じPromptには圧縮方向の指示もあります。
- 第一話の中心となる問題を一つに絞る
- 必要な情報だけを示す
- 今後の兆候は一つだけ使う
- 案内役の説明を避ける
- 同じ出来事の中で複数要素を働かせる
これらはチェックリスト的な展開を避けるために追加した正しい方向の指示です。しかし、何段階の変化を描くかが定義されていません。モデルは、遭遇、能力発現、選択をそれぞれ数段落で処理し、1,500〜2,000文字で中心問題が完結したと判断しました。
そこでVersion 3では、文字数を努力目標ではなくRequirementにし、中心問題を進展させる場面単位を指定します。
2. 変更する条件と固定する条件
Version 2と3の比較では、次を固定します。
- 同じ3件の企画
- 同じ
episode_roleとepisode_intent - 生成モデル
gpt-4o-mini max_output_tokens=12000- 同じRequirement Judge
- 同じQuality Judge
- 同じDiagnostic Judge
- 最大8,000文字
変更するのは生成Promptです。
| 条件 | Version 2 | Version 3 |
|---|---|---|
| 文字数 | 5,000文字目安 | 4,500文字以上8,000文字以内 |
| 構成 | 一つの中心場面 | 一つの中心問題を4〜5場面で進める |
| 長さ不足時 | 明確な指示なし | 行動・失敗・対話・結果の余波を具体化 |
| 水増し防止 | 反復しない | 同じ感情・設定・風景・会話を反復しない |
Quality Judgeは変更しません。Version 3に合わせてJudgeを緩めると、Prompt改善と採点変更が混ざるためです。
3. GUIまたはAPIでPrompt Version 3を登録する
Prompt Registryのstep01-narou-episode-writerへVersion 3を追加します。今回のCommit messageは次のとおりです。
STEP4-1: 4〜5場面構成と4,500〜8,000文字の明示
Version 3では、Version 2の描写条件を維持し、冒頭へ次の構成指示を追加します。
最初に、本文へ出力しない執筆計画を内部で作ってください。
第一話全体で扱う中心的な対立または問題は一つにし、
その問題を進展させる4〜5個の場面単位へ分けます。
場面構成:
1. 冒頭の異常・問題と、主人公固有の知覚または反応
2. 状況を理解するための観察、行動、または失敗
3. 他者や環境との接触による問題の具体化
4. 能力の発現・試行と、その結果による状況変化
5. 必要な場合、主人公の選択が生んだ結果・危機・疑問で次話へ接続
各場面は前の場面の結果から始め、独立した設定紹介を並べないでください。
4場面で十分なら、内容のない5場面目を追加しないでください。
文字数条件は次のようにします。
文字数条件:
- 日本語の小説本文を4,500文字以上8,000文字以内で出力する
- 5,000〜6,000文字を標準とする
- 4,500文字未満で終わりそうな場合は、設定説明ではなく、観察、行動、失敗、対話、結果とその余波を具体化する
- 文字数を増やすために、同じ感情、設定、風景、会話を言い換えて反復しない
- タイトル、見出し、執筆計画、文字数報告は出力せず、小説本文だけを出力する
今回はAPIから次のように登録しました。
prompt_v3 = mlflow.genai.register_prompt(
name="step01-narou-episode-writer",
template=PROMPT_V3,
commit_message="STEP4-1: 4〜5場面構成と4,500〜8,000文字の明示",
tags={"step": "4-1", "status": "candidate"},
)
登録されたURIは次のとおりです。
prompts:/step01-narou-episode-writer/3
4. Version 3を同じ3企画で実行する
生成関数はVersion 3を明示的に読み込みます。
PROMPT_URI_V3 = "prompts:/step01-narou-episode-writer/3"
@mlflow.trace(name="generate_first_episode_v3")
def generate_v3(story):
prompt = mlflow.genai.load_prompt(PROMPT_URI_V3)
response = client.responses.create(
model="gpt-4o-mini",
input=prompt.format(**story),
max_output_tokens=12000,
)
return response.output_text
生成後、下限と上限をコードで確認します。
@scorer
def at_least_4500_characters(outputs):
return len(outputs) >= 4500
@scorer
def within_8000_characters(outputs):
return len(outputs) <= 8000
「4,500文字以上」は自然言語Judgeへ任せる必要がありません。Pythonのlen()で必ず同じ結果を得られます。
5. Version 2と3を同じJudgeで評価する
Version 2 fullの既存3件と、今回生成したVersion 3の3件を一つのEvaluation runへ渡します。
evaluation = mlflow.genai.evaluate(
data=pd.DataFrame(rows),
scorers=[
at_least_4500_characters,
within_8000_characters,
non_empty,
*build_requirement_judges(),
*build_quality_judges(),
*build_diagnostic_judges(),
],
)
Evaluation run IDは次のとおりです。
1a14f6cd927642ee9b46ee62fc6eed96
GUIではat_least_4500_charactersが0になっています。Version 2と3の6件すべてが4,500文字未満だったためです。
6. 文字数結果
Version 3の結果は次のとおりです。
| 企画 | Version 2 | Version 3 | 4,500文字以上 |
|---|---|---|---|
| 異世界転生 | 1,906 | 1,611 | No |
| 異世界追放 | 1,834 | 1,329 | No |
| 悪役令嬢転生 | 1,564 | 1,753 | No |
| 平均 | 1,768 | 1,564 | 0/3 |
悪役令嬢だけは189文字増えましたが、異世界転生は295文字、異世界追放は505文字減っています。場面構成と最低文字数を明示しても、全体としては短くなりました。
7. API上限で切れたわけではない
短い理由がmax_output_tokensなら、PromptではなくAPI設定を直す必要があります。MLflowのTraceでResponses spanを確認しました。
| 企画 | 出力文字数 | output tokens | max output tokens | 状態 |
|---|---|---|---|---|
| 異世界転生 | 1,611 | 1,356 | 12,000 | completed |
| 異世界追放 | 1,329 | 1,111 | 12,000 | completed |
| 悪役令嬢転生 | 1,753 | 1,458 | 12,000 | completed |
3件ともincomplete_details=Noneで正常終了しています。12,000トークンを使い切る前に、モデル自身が回答を完了しました。
したがって、max_output_tokensをさらに増やしても今回の問題は解決しません。上限は出力可能量を制限しますが、使用量の目標にはならないためです。
8. Quality比較
同じEvaluation run内のQuality平均は次の結果です。
| 条件 | 平均文字数 | Quality平均 |
|---|---|---|
| Version 2 full | 1,768 | 3.083 |
| Version 3 | 1,564 | 3.167 |
Version 3は+0.084ですが、文字数目標を完全に失敗しており、この差だけで採用できません。また、STEP4で同じVersion 2出力を評価したときは3.208でした。今回の再評価では3.083です。
同じ本文、同じJudge定義、同じJudgeモデルでも点数が変動しています。0.08〜0.13程度の差はJudgeの実行揺らぎの範囲である可能性が高く、明確な品質改善とは判断しません。
項目別結果は次のとおりです。
| Quality | Version 2 | Version 3 |
|---|---|---|
narrative_coherence |
3.33 | 3.00 |
characterization |
3.00 | 3.00 |
emotional_credibility |
3.00 | 3.00 |
scene_execution |
3.00 | 3.33 |
opening_engagement |
3.00 | 3.33 |
information_control |
3.33 | 3.33 |
reader_momentum |
3.00 | 3.00 |
episode_role_fulfillment |
3.00 | 3.33 |
場面表を追加したことでscene_execution、opening_engagement、episode_role_fulfillmentは上がりました。一方、narrative_coherenceは下がっています。4〜5場面を要求しても実際の本文量が増えず、短い文章へ複数段階を圧縮した影響が考えられます。
9. Version 3も採用しない
Version 3の採用条件は次のとおりでした。
- 3件すべて4,500〜8,000文字
- Requirementに回帰がない
- Version 2よりQualityが明確に改善する
-
information_controlを維持する - 冗長化によって
scene_executionが低下しない
実測では、文字数条件が0/3です。Quality差もJudgeの揺らぎと区別できない水準です。そのためVersion 3も採用しません。Prompt Registryにはcandidateの失敗Versionとして残します。
10. 単一呼び出しを二段階Pipelineへ変える
構成をJSONで生成し、その4場面を個別に1,150〜1,600文字で書くPipelineへ変更しました。短い場面だけを追補し、本文を結合します。
step04-narou-outline-planner/1
step04-narou-scene-writer/1
step04-narou-scene-continuation/1
結果は異世界転生5,781文字、異世界追放5,829文字、悪役令嬢5,984文字、平均5,865文字でした。3件すべてが4,500〜8,000文字を満たしたため、長さの制御には場面単位への分割が有効です。
一方、Quality平均は3.167で、設定整合性と先取り抑制に各1件の回帰がありました。場面数を増やしたことでagency_levelは5.0になりましたが、characterization、emotional_credibility、reader_momentumはすべて3.0のままです。増えたのは行動量であり、人物理解や感情の蓄積ではありません。
11. 契約とStory Stateを追加する
次に、長さではなく場面間の状態管理を改善しました。
-
Episode Contractで変更禁止の事実、第一話の到達点、禁止する将来展開、残す疑問を定義する - 各場面へ異なる役割と感情・知識・緊張度の変化を割り当てる
- 場面後に
Story StateをJSONで抽出し、次場面へ渡す - 結合後に契約違反を検査し、指摘された場面だけを修正する
6種類のPromptを登録しました。
step04-narou-episode-contract/1
step04-narou-stateful-outline/1
step04-narou-stateful-scene/1
step04-narou-story-state/1
step04-narou-contract-review/1
step04-narou-targeted-repair/1
12. 状態管理版は過剰制約で悪化した
既存の二段階PipelineをControlとして同じRunで再評価しました。
| 条件 | 平均文字数 | Quality平均 | 最低文字数 | 設定整合性 | 先取り抑制 |
|---|---|---|---|---|---|
| 二段階Control | 5,865 | 3.208 | 3/3 | 3/3 | 3/3 |
| 契約・状態管理Candidate | 5,322 | 2.833 | 2/3 | 3/3 | 3/3 |
Candidateは設定整合性と先取り抑制を3/3へ戻しましたが、Qualityは0.375低下しました。emotional_credibilityは3.0から2.33、opening_engagementは3.33から2.67、episode_role_fulfillmentは3.33から2.67です。
異世界追放では修正後の第三場面が28文字になり、合計4,261文字でした。全文検査は存在しない場面番号5、6も返し、許可された能力理解を違反とするなど誤判定しました。これを無条件で修正へ使ったため、必要な動機や感情まで削られました。契約生成自体にも、must_happenとmust_not_happenの境界を過剰に解釈する問題があります。
したがってCandidateは採用しません。次回は、入力から決定的に作れる契約をLLMに推測させずコードで構築し、検査結果をそのまま修正へ流さない設計が必要です。場面番号、引用根拠、契約キーを検証し、違反が確実な場合だけ一場面を修正します。品質向上は制約追加ではなく、人物の選択・迷い・代償を構成へ一つずつ割り当てる実験として分離します。
まとめ
STEP4-1では、Version 2の短文化をPrompt構造の問題と考え、4〜5場面と4,500〜8,000文字を明示したVersion 3を作成しました。
しかし、Version 3の平均は1,564文字で、3件とも最低文字数を満たしませんでした。Responses APIは12,000トークンの上限へ達しておらず、モデルが自発的に回答を完了しています。
Quality平均は3.083から3.167へわずかに上がりましたが、同じVersion 2本文のJudge結果自体が別runで3.208から3.083へ変動しています。この程度の差を改善とは断定できません。
Version 3単体は不採用です。二段階Pipelineによって5,000文字前後の生成は実現しました。さらに契約とStory Stateを追加すると仕様違反は抑制できましたが、過剰制約と検査誤判定によりQualityが3.208から2.833へ低下しました。このCandidateも採用しません。
今回の教訓は、長さ、仕様遵守、品質は別々の目的だという点です。呼び出し分割は長さを、契約は仕様遵守を改善しましたが、制約と自動修正を増やすだけでは人物や感情の品質は上がりません。次は決定的な契約構築と検査器の校正を先に行い、人物の選択・迷い・代償の改善を別の比較実験として扱います。
次はSTEP4-2として、以下の改善を行う予定
Requirement検査とQuality評価からの処理を分ける
Requirement違反:必要なら修正する
Quality低下:その場で自動修正せず、次のPrompt実験の根拠にする
品質Judgeの指摘を自動修正へ流すと、Judgeの揺らぎが本文を破壊します。