先週、少々恥ずかしい発見をしました。
私のAIポッドキャストパイプラインには、入念に作り込んだ「対話ガイド」——数日かけて書いたシステムプロンプトがあります。2人のAIサッカー解説者(ホスト)に、互いにどう話すべきかを正確に指示するものです。同じオープニングを繰り返さないこと。セグメントごとに構成を変えること。決め台詞は控えめに使うこと。統計データを捏造しないこと。
LLMは一字一句読みました。そして、ほぼすべてを無視しました。
ベトナム人ホストは1エピソードで「Trời ơi」(なんてこった)を13回。日本人ホストは全セグメントを「いやー、健一さん!」で開始。英語ホストはxG 0.35やポゼッション65%といった——私のデータベースのどこにも存在しない——数字を自信満々に引用しました。
プロンプトは丁寧に「承知しました」と受け取られました。そして無視されました。
これが、私がプロンプトエンジニアリングについて学んだ中で最も有用な教訓です:LLMには2つの読み取りモードがあり、それらはまったく異なる扱いを受けます。
2つの読み取りモード問題
私のオリジナルの対話ガイドは以下のようなものでした。構造は同じなので、ベトナム語版を英訳したものですが:
STYLE GUIDELINES:
HOST INTERACTION PATTERN: Emotional Host + Analytical Host.
STRICT RULES FOR EVERY SEGMENT:
0. NEVER REPEAT THE SAME OPENING. Vary which host initiates,
which match moment they react to first.
1. EMOTIONAL START (mandatory): Every segment begins with
the emotional host's pure reaction.
2. DATA-GROUNDING RULE (mandatory): Every claim must cite
specific numbers. Not "they dominated" but "61% possession,
3.17 xG to 0.65."
3. CATCHPHRASES:
Emotional host: "You see that?!" "Wait wait wait..."
Analytical host: "There's a deeper reason..." "Let me tell you."
徹底的に書いたつもりでした。本当にやり切ったと思っていました。
ところが、実際に生成されたスクリプトを読んでみると、こうなっていました:
| ルール | ガイドの指示内容 | LLMの実際の挙動 |
|---|---|---|
| ルール0(繰り返し禁止) | オープニングにバリエーションを | 全セグメントがエモーショナルホストの決め台詞でスタート |
| ルール2(データ) | 具体的な数字を引用 | 完全な捏造数字を堂々と引用 |
| ルール3(決め台詞) | シグネチャーフレーズのリスト | 各フレーズを1エピソードで5〜13回使用 |
ルールは存在していました。LLMはそれを読みました。しかし、それらは要件ではなく、提案程度の強制力しか持っていませんでした。
ここで気づいたのです:LLMは私のルールを「スタイルの文脈情報」として処理しており——「強制可能な制約」としては処理していなかったのです。
パターン:コンテキスト vs. コマンド
異なる種類のテキストをどう読むか、考えてみてください。レストランのメニューを渡されたら、行動すべき情報として読みます。パスタの歴史についてのWikipedia記事を渡されたら、背景知識として読みます。
LLMもプロンプトに対して似たようなことをします。「STYLE GUIDELINES」や「HOST INTERACTION PATTERN」や「RULES FOR SEGMENTS」といったラベルのセクションにルールが書かれていると、モデルはそれを周辺情報——レスポンスに色付けする文脈ではあるが、制約するものではない——として扱います。
「CRITICAL IMPROVEMENTS REQUIRED」や「SPECIFIC PHRASE LIMITS」といったラベルのセクションにルールが書かれていると、モデルはそれを強制可能なコマンド——検証し修正しなければならないもの——として扱います。
この洞察の核心は「CRITICAL」や「REQUIRED」といった単語ではありません。ルールがパイプラインのどこに配置されているかです。
解決策:レビューステップに、生成ステップが無視するものを強制させる
私のパイプラインには2つのLLM呼び出しがあります:
- 生成ステップ —— 対話ガイドを文脈として、スクリプトをセグメントごとに執筆
- レビューステップ —— 完成したスクリプト全体を読み、改善
当初、すべてのルールは生成ステップにありました。レビューステップには、完全に英語中心の汎用的な「繰り返し禁止ルール」(「'That's fascinating'を2回言わない」など)があるだけで、私のホストの決め台詞や構造的要件については何も知りませんでした。
修正は語彙的なものではなく、構造的なものでした。強制をレビューステップに移したのです——そこでは完全なスクリプトが一度に見えるからです。
何が変わったか:
Before:ルールは生成プロンプト内(無視された)
STYLE GUIDELINES:
0. NEVER REPEAT THE SAME OPENING...
1. EMOTIONAL START...
2. DATA-GROUNDING...
3. CATCHPHRASES: "You see that?!" ...
After:ルールを抽出し、レビュープロンプトに挿入(強制された)
CRITICAL IMPROVEMENTS REQUIRED ACROSS THE ENTIRE SCRIPT:
2a. ENFORCE STYLE GUIDELINES' STRUCTURAL RULES:
The STYLE GUIDELINES above contain specific requirements
about segment structure. These are REQUIREMENTS, not suggestions.
- If the guidelines say "X of Y segments must vary," CHANGE
segments that violate this.
- Check how EACH segment opens. If 4+ out of 5 open identically,
VARY at least 2.
3b. LOCALE-SPECIFIC PHRASE LIMITS (extracted from host profiles —
MAXIMUM 2 TIMES each across entire script):
- "Trời ơi" / "Trời má" — max 4x combined
- "Có một lý do sâu hơn" — max 2x
- "Mày thấy chưa?!" — max 2x
...
ここで2つのことが起こりました:
-
構造的ルールがコンテキストからコマンドに昇格されました——「こちらが推奨パターンです」ではなく「これを強制せよ」「違反しているセグメントを変更せよ」と言い換えられています
-
決め台詞がホストプロファイルから抽出され、提案のリストとしてではなく明示的な制限として表示されました。「Catchphrases: here are some」が「These phrases: MAXIMUM 2 TIMES each」に変わりました
両方の変更は生成プロンプトではなくレビュープロンプトにあります。これが鍵となるアーキテクチャ上の決定です。生成ステップはスタイルガイドを文脈として受け取ります——パーソナリティの風味が必要だからです。レビューステップはルールを強制命令として受け取ります——コンプライアンスの権限が必要だからです。
抽出の詳細(これが7言語で機能する理由)
私は言語ごとのフレーズリストを手動でメンテナンスしたくありませんでした。ホストプロファイルを調整するたびにレビュープロンプトも更新しなければならない——これはソロ開発者のプロダクトを殺す種類のドリフトです。
代わりに、レビュー時にホストプロファイルをパースする小さな抽出関数を書きました。各ロケールのプロファイルには、標準ラベルの付いた決め台詞セクションがあります:
- ベトナム語:
Câu cửa miệng: - 日本語:
口癖: - スペイン語:
Latiguillos: - フランス語:
Phrases fétiches: - 韓国語:
입버릇: - 中国語:
口头禅:
この関数はそのラベルを見つけ、その後に続く引用符で囲まれた文字列を抽出し、強制可能な制限としてフォーマットします:
@staticmethod
def _extract_catchphrase_limits(host_profiles: str) -> str:
patterns = [
(r'Câu cửa miệng:?', r'"([^"]+)"'), # Vietnamese
(r'口癖:?', r'「([^」]+)」'), # Japanese
(r'Latiguillos:?', r'"([^"]+)"'), # Spanish
# ... etc for all 7 languages
]
limits = []
for marker_pattern, quote_pattern in patterns:
match = re.search(marker_pattern, host_profiles)
if match:
phrases = re.findall(quote_pattern, host_profiles[match.end():])
for phrase in phrases:
limits.append(f' - "{phrase}" — max 2x')
return "\n".join(limits)
これでホストプロファイルを編集して決め台詞を変更すると、レビュープロンプトが自動的に更新されます。ドリフトなし。忘れられた言語なし。
これで実際に何が修正されたか(数字で)
これらの修正前と修正後で生成されたエピソードの変化は以下の通りです:
| 指標 | 修正前 | 修正後 |
|---|---|---|
| 「Trời ơi」の回数(上限:最大4回) | 13回 | viでは未テストだが、jaでは全4つの決め台詞が上限内 |
| 「Có một lý do sâu hơn」(上限:最大2回) | 5回 | 同等のjaテストで上限内 |
| 捏造統計(xG、ポゼッション率、フォーメーション幅) | 4つの捏造数字 | 捏造数字ゼロ |
| 同一パターンのセグメントオープニング | 6/6が同一 | jaでは依然5/5——下記参照 |
| ベトナム/日本のつながりの保持 | 1/4が生き残り(レビューが3つ削除) | 11/11が生き残り |
| ホストの声の混在 | 両者が互いのフレーズを使用 | 明確に分離 |
決め台詞の上限、捏造禁止ルール、ローカライゼーション保持はすべて機能しました。ホストプロファイルが各ホストの個性を十分に明確に指示するようになり、LLMが誰が何を言うべきか混乱することはなくなりました。
まだ壊れているもの
1つのルールは依然として機能していません:構造的多様性です。対話ガイドには「5セグメント中少なくとも2つは異なる構造的エントリーを使用すること」とあります。修正後も、日本語のエピソードは全セグメントが同一のオープニングでした:「いやー、健一さん!」——1エピソードで5回。
これはプロンプト強制の問題ではありません。アーキテクチャの問題です。
生成ステップは各セグメントを独立して生成します。セグメント3は、セグメント1と2がエモーショナルホストの決め台詞で始まったことを知りません。各セグメントは同じ対話ガイドで分離して生成されるため、それぞれが同じ「エモーショナルホストで始めよう」という決定に至ります。
レビューステップはこれを捕捉できるはずです——完全なスクリプトを一度に見るからです。そして私はオープニングを変えるよう明示的な強制文言を追加しました。しかし、レビューを実行しているGemini 3.5 Flashは、決め台詞のトリミングや捏造クレームのソフト化よりも、セグメントのオープニングを再構成する方が明らかに難しいようです。オープニングの多様性は、単に削ったり削除したりするのではなく、コンテンツの一部を再生成する必要があります。これはモデルにとってより重い作業です。
私はまだこれに取り組んでいます——おそらくより高性能なレビューモデルか、セグメントがオープニングの状態を共有するパイプライン再構成が必要でしょう。より広いポイントは:プロンプトの強制ではアーキテクチャの問題を修正できないということです。パイプラインの構造がルールを守ることを不可能にしているなら、プロンプトにいくら「CRITICAL」や「MANDATORY」と書いても救われません。
フレームワーク:すべてのルールに対する3つの質問
この経験を経て、私は今、すべてのシステムプロンプトを書く際に3つの質問で監査しています:
1. このルールはどこに配置されるか——生成かレビューか? パーソナリティやトーンに関するルールは生成に属します。コンプライアンス、一貫性、構造に関するルールはレビューに属します。両方をまたぐルール(決め台詞のように——パーソナリティだが制限が必要)は、両方に配置します。
2. このルールはコンテキストとして書かれているか、コマンドとして書かれているか? 「こちらが決め台詞です」はコンテキストです。「最大各2回」はコマンドです。同じルールに両方が必要です——生成ステップは決め台詞が何であるかを知る必要があり、レビューステップはそれがどの程度の頻度で出現するかを強制する必要があります。
3. パイプラインは実際にこのルールに従えるか? セグメント単位の生成では、セグメント間の多様性を強制できません。どんなプロンプトもこれを修正できません。ルールをレビューステップに移動する(完全なスクリプトが見える)か、セグメントが状態を共有するようパイプラインを再構築する必要があります。
このフレームワークはポッドキャスト生成に限ったものではありません。コンテンツ生成、コードレビュー、文書要約など、生成と品質管理のパスが分離しているあらゆるマルチステップLLMパイプラインに適用されます。
私はまだこれを反復していますが、もしあなたが最初のマルチステップLLMパイプラインを構築しているなら:最低でも2つのステップに分けてください。最初のステップはパーソナリティと自由さを持って生成します——文脈、例、トーンのガイダンスを与えます。2番目のステップは正確さと強制力を持ってレビューします——明示的な制限、コンプライアンスチェック、そして変更を加える権限を与えます。
生成ステップはクリエイティブな協力者にブリーフィングするように感じるべきです。レビューステップは編集者にチェックリストを渡すように感じるべきです。
そして両方をやった後でもルールが無視され続けるなら、まずアーキテクチャの質問をしてください。プロンプトの問題なのか、それともパイプラインの問題なのか?
よくある質問
これは単一プロンプトのセットアップ(パイプラインなし)にも適用されますか?
はい、ただし限定的に。単一プロンプトでは、同じモデルに創造的であることとコンプライアントであることを同時に求めます——2つが衝突するときは、常に創造性がコンプライアンスより優先されます。単一プロンプトでも、指示を「コンテキスト」セクションと明示的な「制約」セクションに分けることは役立ちます。しかし、上限があります。異なる指示を持つ2つの別々の呼び出しは、ほぼ常に1つを上回ります。
なぜ生成プロンプトのルールをもっと強く書かなかったのですか?
試しました。「MANDATORY」「CRITICAL」「NEVER」とすべて大文字で追加しました。生成ステップには競合する要求が多すぎます——魅力的で、自然に聞こえ、多様で、ローカルに本物らしく、かつコンプライアントであろうと同時に試みています。モデルが「興奮したベトナム人サッカーファンのように聞こえる」と「'Trời ơi'は2回だけ」の間で選択を迫られたとき、パーソナリティが常に勝ちます。レビューステップにはそのような競合はありません——唯一の仕事は強制です。
レビューステップにどのモデルを使っていますか?
Gemini 3.5 Flash(コスト面から——レビューは7言語すべての全エピソードで実行されます)。決め台詞のトリミングや捏造統計のソフト化などの単純な強制はうまく処理します。セグメントのオープニングの再構成のような、コンテンツの再生成を必要とするタスクには苦労します。より強力なモデルが役立つでしょうが、まだコストを正当化できていません。
私はDIALØGUE(ダイアログ)——AIポッドキャストプラットフォーム——を、夜と週末に一人で開発しています。決め台詞抽出とレビュープロンプトの変更のコードは公開されています——完全なシステムプロンプトと強制ロジックはPodcast-Engineリポジトリで読めます。道中で学んだことをここに書いています。
似たような問題——読まれるが強制されないルール——に遭遇したことがあれば、ぜひ聞かせてください:そのルールは何で、パイプラインのどこに配置されていましたか?
今回は以上です。
それでは、Chandler