Python & AI Tutorials Logo
LangChain & LangGraph

4. テンプレートを使った再利用可能なプロンプトの設計

第3章では、プロンプトがPythonコードに直接埋め込まれた、動作するストリーミングチャットCLIを構築しました。これは素早いプロトタイプには有効ですが、AIアプリケーションが成長するにつれて、ハードコードされたプロンプトは保守の悪夢となります。複数のファイルにわたって同じプロンプトロジックを更新したり、コードを再デプロイせずに異なるプロンプトのバリエーションをA/Bテストしようとすることを想像してみてください。

この章では、LangChainのテンプレートシステムを使用して再利用可能で保守性の高いプロンプトを設計する方法を学びます。プロンプトロジックをアプリケーションコードから分離し、より良いLLM制御のためにロールベースメッセージングを活用し、チームコラボレーションのためにプロンプトをYAMLファイルに外部化し、実行前にテンプレートを検証してエラーを早期に発見する方法を学びます。

この章でカバーする内容(カバーしない内容):

この章では、テンプレートとプロンプトを手動で扱います—テンプレートを明示的にメッセージにレンダリングし、その後 llm.invoke() を使用してそれらのメッセージをLLMに送信します。このハンズオンアプローチは、テンプレートが実際に何をするのか、どのように機能するのかを正確に理解するのに役立ちます。

第6章では、LCEL(LangChain Expression Language)を学びます。これにより、| 演算子を使用してテンプレートとLLMをパイプラインに構成できます。今のところ、このオーケストレーションレイヤーなしで、テンプレートの基礎に焦点を当てています。

この章の終わりまでに、シンプルなチャットボットから複雑なマルチエージェントワークフローまでスケールする堅牢なプロンプト管理システムを手に入れることができます。

4.1) 関心の分離: コードとプロンプトの分離

なぜプロンプトをコードから分離するのか?

プロンプトをアプリケーションロジックに直接ハードコードすると、いくつかの問題につながる密結合が生じます:

保守の負担: プロンプトを変更するには、Pythonコードの修正、テストの実行、再デプロイが必要です。プロンプトの変更は通常、コードの変更よりもはるかに頻繁に発生するため、単純なテキスト編集であるべきものに対して、このコード修正-テスト-再デプロイサイクルは非常に非効率的です。

バージョン管理の課題: コードとプロンプトが混在していると、バージョン管理が困難になります。マージコンフリクトが発生しやすくなり、各コンフリクトには手動での解決とリファクタリングが必要です。

コラボレーションの摩擦: 非技術的なチームメンバー(プロダクトマネージャー、ドメインエキスパート)は、.py ファイルに存在するプロンプトを直接編集できず、開発者の支援に依存する必要があります。この依存関係により、プロンプト改善サイクルが大幅に遅くなります。

テストの複雑さ: 異なるプロンプトのバリエーションをテストするには、コードをコピーし、文字列を修正し、複数のブランチを管理する必要があり、実験が遅く、エラーが発生しやすくなります。

プロンプトは、従来のアプリケーションにおけるSQLクエリのようなものだと考えてください。SQLの文字列をPythonコード全体にハードコードすることはしないでしょう—ORMを使用するか、少なくともクエリを一元化するでしょう。プロンプトも同じアーキテクチャの規律に値します。

LangChainのテンプレートシステム

LangChainは、プロンプトの固定構造と変化するデータを分離するために PromptTemplateChatPromptTemplate クラスを提供します。{placeholders} を使用してプロンプトを一度記述し、毎回異なる値を差し込みます—f文字列や連結でプロンプトを再構築する必要はもうありません。

テンプレートの構文と使用法

プレースホルダー構文

テンプレートは {variable_name} をプレースホルダーとして使用します。実行時に、一致するキーを持つ辞書を提供します:

python
from langchain_core.prompts import PromptTemplate
 
# プレースホルダーを使用してテンプレートを定義
template = PromptTemplate.from_template(
    "Translate {content} from {source_lang} to {target_lang}"
)
 
# 辞書でプレースホルダーを埋める
result = template.invoke({
    "content": "Hello world",
    "source_lang": "English", 
    "target_lang": "Korean"
})
 
print(result.text)

出力:

Translate Hello world from English to Korean

重要なルール:

  • プレースホルダー名は辞書のキーと正確に一致する必要があります
  • すべてのプレースホルダーを提供する必要があります(キーが不足すると KeyError が発生します)
  • 余分な辞書のキーは無視されます
  • invoke() を使用して、値でテンプレートをレンダリングします

PromptTemplate vs ChatPromptTemplate

PromptTemplate: プレーンな文字列を返します(StringPromptValue でラップされます)

  • シンプルなテキスト補完やレガシーモデル用
  • 出力: "Summarize: {content}" のような単一の文字列

ChatPromptTemplate: ロールを持つ構造化されたメッセージを返します(ChatPromptValue でラップされます)

  • 最新のチャットモデル用(GPT-4、Claude、Gemini)
  • 出力: ロール分離されたメッセージ(system/user/assistant)
  • 推奨される選択: システム命令をユーザー入力から分離して保守するのに優れています

どちらを使用するか?

  • チャットモデルにはデフォルトで ChatPromptTemplate を使用—より明確で保守しやすいです
  • シンプルな補完やロール分離が不要な場合にのみ PromptTemplate を使用します
python
# PromptTemplate - 単一の文字列出力
from langchain_core.prompts import PromptTemplate
 
template1 = PromptTemplate.from_template("Summarize: {content}")
result1 = template1.invoke({"content": "LangChain is a framework..."})  
print(result1)

出力:

text='Summarize: LangChain is a framework...'
python
# ChatPromptTemplate - ロールベースのメッセージ
from langchain_core.prompts import ChatPromptTemplate
 
template2 = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant"),
    ("user", "{question}")
])
result2 = template2.invoke({"question": "What is LangChain?"})
print(result2)

出力:

messages=[SystemMessage(content='You are a helpful assistant'), HumanMessage(content='What is LangChain?')]

テンプレートは一度定義されます。テンプレート定義を変更せずに、異なる値で再利用できます。PromptTemplate.invoke()ChatPromptTemplate.invoke() の両方が、LLMに直接送信できるプロンプト値を返します。

文字列フォーマットからテンプレートへ

ハードコードされたプロンプトをテンプレートを使用するようにリファクタリングしましょう。これが第3章の「前」バージョンです:

python
# ハードコードされたアプローチ(第3章スタイル)
from langchain_openai import ChatOpenAI
 
llm = ChatOpenAI(model="gpt-4o-mini")
 
user_input = "Explain quantum computing"
# プロンプトロジックがコードと混在
prompt = f"You are a helpful assistant. Answer this question: {user_input}"
 
response = llm.invoke(prompt)
print(response.content)

テンプレートを使用した場合—この章全体で実践するステップバイステップのアプローチを使用:

python
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
 
# テンプレートを別々に定義
template = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant."),
    ("user", "{user_input}")
])
 
# アプリケーションロジック - ステップバイステップの実行
llm = ChatOpenAI(model="gpt-4o-mini")
 
user_input = "Explain quantum computing"
 
# ステップ1: テンプレートをメッセージにレンダリング
messages = template.invoke({"user_input": user_input})
 
# ステップ2: メッセージをLLMに送信
response = llm.invoke(messages)
print(response.content)

何が変わったか?

  1. テンプレート定義: プロンプト構造は、実行ロジックとは別に template で一度定義されます。
  2. プレースホルダー構文: {user_input} は実行時に埋められるプレースホルダーです。
  3. ステップバイステップの実行: テンプレートを明示的にレンダリング(template.invoke())し、その結果をLLMに送信(llm.invoke())します。この2ステップのプロセスは、テンプレートが実際に何をするのかを理解するのに役立ちます。
  4. 再利用性: 同じ template を変更せずに、任意のユーザーの質問に使用できます。
  5. メッセージ構造: template.invoke() は、LLMが期待する適切にフォーマットされた ChatPromptValue を返します。

なぜステップバイステップのアプローチなのか?

この章全体で、このパターンを繰り返し見ることになります:

python
messages = template.invoke(inputs)  # ステップ1: テンプレートをレンダリング
response = llm.invoke(messages)     # ステップ2: LLMに送信

学習のために、この2ステップのアプローチを意図的に使用しています—テンプレートが何をするのかを正確に示します: 入力データを構造化されたメッセージに変換します。第6章では、実世界の本番パターンを学びます: これらのステップをLCELパイプライン(template | llm)で組み合わせます。しかし、最初に各ステップを個別に理解することで、しっかりとした基礎を築きます。

テンプレートの検証

テンプレートはエラーを早期に検出します。存在しないプレースホルダーを参照すると、LangChainはAPI呼び出しを行う前にエラーを発生させます:

python
template = PromptTemplate.from_template("Summarize: {text}")
 
# これは失敗します - 'text' キーが不足しています
try:
    template.invoke({"content": "Some text"})  # 間違ったキー名
except KeyError as e:
    print(f"Template error: {e}")

出力:

Template error: "Input to PromptTemplate is missing variables {'text'}.  Expected: ['text'] Received: ['content']

この検証はテンプレートのレンダリング時に発生し、LLM実行時ではありません—時間とAPIコストの両方を節約します。

4.2) ロール認識プロンプトテンプレート(System、User、Assistant)

メッセージロールの理解

最新のLLM(GPT-4、GPT-5、Claude、Gemini)は、メッセージロールを通じて会話構造を理解します。各メッセージには、モデルにそれをどのように解釈するかを伝える特定のロールがあります。

3つのコアロール:

System: AIがどのように振る舞うべきかを定義します

  • 目的: AIのパーソナリティ、専門知識、操作ルールを設定します
  • : "あなたは簡潔なコード例を書くPythonエキスパートです"
  • 適用時: 開始時に一度設定され、すべての応答に影響します
  • 考え方: AIの取扱説明書

User: 人間の入力を表します

  • 目的: 質問をしたり、リクエストを行います
  • : "Pythonでファイルを読み込む方法は?"
  • 適用時: 人間がメッセージを送信するたびに
  • 考え方: あなたが尋ねる質問

Assistant: AIの以前の応答を表します

  • 目的: 会話履歴を提供します
  • : "ファイルを読み込むには open() 関数を使用できます"
  • 適用時: マルチターン会話が必要な場合
  • 考え方: AIの以前の回答の記憶

システムメッセージ: 制御メカニズム

システムメッセージは、ユーザーとのやり取りの前に、AIがであり、どのように動作すべきかを伝えます。

制御できること:

  1. 専門知識: "あなたはシニアPython開発者です"
  2. 出力形式: "常にJSON形式で応答してください"
  3. 行動ルール: "不確かな場合は「わかりません」と言ってください"
  4. 応答スタイル: "簡潔で技術的にしてください"

なぜこれが重要か:

システムメッセージなし → 一般的で冗長な応答

システムメッセージあり → 一貫性があり、カスタマイズされた動作

システムメッセージの実践

システムメッセージの実際の影響を、同じ質問をシステムメッセージありとなしで比較して見てみましょう。応答が長さだけでなく、トーン、複雑さ、教え方においてどれほど劇的に変化するかに注目してください。

システムメッセージなし:

python
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
 
template = ChatPromptTemplate.from_messages([
    ("user", "What is Python?")
])
 
llm = ChatOpenAI(model="gpt-4o-mini")
messages = template.invoke({})
response = llm.invoke(messages)
print(response.content)

出力:

Python is a high-level, interpreted programming language known for its readability and simplicity. 
It was created by Guido van Rossum and first released in 1991. 
Python emphasizes code readability, allowing programmers to express concepts in fewer lines of code compared to languages such as C++ or Java.
 
Key features of Python include:
...

システムメッセージあり:

python
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
 
# システムメッセージでペルソナと出力スタイルを制御
template = ChatPromptTemplate.from_messages([
    ("system", """あなたは15年の教育経験を持つシニアPythonインストラクターです。
あなたの生徒は、これまでプログラミングをしたことがない完全な初心者です。
 
教え方:
- シンプルで日常的な類推を使用してください
- 技術的な専門用語を避けてください
- 日常生活からの実践的な例を示してください
- 励まし、忍耐強くしてください"""),
    ("user", "What is Python?")
])
 
llm = ChatOpenAI(model="gpt-4o-mini")
messages = template.invoke({})
response = llm.invoke(messages)
print(response.content)

出力:

Great question! Think of Python like a really helpful tool in your toolbox. 
Just like a hammer or a screwdriver helps you build or fix things around the house, Python helps you create software or automate tasks on a computer.
 
Imagine you wanted to bake a cake. 
You need a recipe to follow, right? In this analogy, Python is like that recipe. 
It tells the computer what steps to take to achieve a goal, whether it's doing math, organizing files, or even running a game.
...

違い:

システムメッセージなし:

  • AIはデフォルトの動作を使用します: 丁寧で有益ですが、一般的です
  • 応答は百科事典的で形式的—幅広い聴衆向けに最適化されています
  • 一貫したペルソナなし: 各応答はトーンとスタイルが異なる可能性があります
  • 制約なし: AIは自分でどれだけ詳細または技術的にするかを決定します

システムメッセージあり:

  • AIはあなたの特定の指示に従います: あなたが定義したペルソナ、スタイル、ルール
  • 応答は一貫性があり予測可能—すべての回答があなたの要件に一致します
  • 明確なペルソナが維持される: あなたが割り当てた役割(教師、エキスパート、アシスタント)として行動します
  • 明示的な制約が適用される: あなたが設定した出力形式、言語レベル、行動の境界

重要な洞察: システムメッセージがないと、AIのデフォルトモードが得られます。システムメッセージがあると、あなたのAI—アプリケーションのニーズに合わせてカスタマイズされたものが得られます。システムメッセージは、AIを汎用ツールから、あなたが望む通りに毎回正確に動作する専門アシスタントに変換します。

UserとAssistantロール: 会話の構築

単一の質問(Userのみ):

python
template = ChatPromptTemplate.from_messages([
    ("system", "You are a Python expert."),
    ("user", "{question}")
])
 
llm = ChatOpenAI(model="gpt-4o-mini")
messages = template.invoke({"question": "How do I read a CSV?"})
response = llm.invoke(messages)

独立した質問には問題なく機能します。

コンテキストを持つマルチターン(User + Assistant):

履歴なし:

python
template = ChatPromptTemplate.from_messages([
    ("system", "You are a Python expert."),
    ("user", "How does it work?")  # "it" = ???
])

AIは「it」が何を指しているのかわかりません。

履歴あり:

python
template = ChatPromptTemplate.from_messages([
    ("system", "You are a Python expert."),
    ("user", "What's the pandas library?"),
    ("assistant", "Pandas is a data analysis library."),
    ("user", "How does it work?")  # 今は "it" = pandas
])

会話履歴(以前のユーザーの質問 + アシスタントの応答)がコンテキストを提供します。AIは今、「it」がpandasを意味することを理解します。

例: 履歴を使った会話の構築

それでは、以前のやり取りを記憶する例を構築しましょう。この関数は会話履歴を維持し、新しい質問ごとにAIに渡します:

python
llm = ChatOpenAI(model="gpt-4o-mini")
 
def chat_with_history(user_input: str, history: list):
    messages = [("system", "You are a Python expert.")]
    
    # 履歴を追加
    for msg in history:
        messages.append((msg["role"], msg["content"]))
    
    # 現在の入力を追加
    messages.append(("user", user_input))
    
    # コンテンツに{中括弧}が含まれている場合のエラーを避けるためにmustache形式を使用
    template = ChatPromptTemplate.from_messages(messages, template_format="mustache")
    
    formatted = template.format()
    response = llm.invoke(formatted)
    return response.content
 
# 使用法
history = []
 
# ターン1
resp1 = chat_with_history("What's a Python dictionary?", history)
print(resp1)
 
history.append({"role": "user", "content": "What's a Python dictionary?"})
history.append({"role": "assistant", "content": resp1})
 
# ターン2 - コンテキストを使用
resp2 = chat_with_history("Show an example.", history)
print(resp2)

メッセージの順序ルール

LLMは特定の会話構造を期待します: System → User → Assistant → User → Assistant → ...

なぜこの順序なのか?

このパターンは自然な人間-AI会話を反映しています:

  1. Systemが最初に来る(オプション): 会話全体に適用される行動ルールを設定するため、やり取りが始まる前に定義する必要があります。タスクを開始する前に誰かに説明するのと同じで、タスクの途中ではありません。

  2. UserとAssistantが交互になる: 実際の会話では、人間が話し(User)、AIが応答し(Assistant)、人間がフォローアップし(User)、AIが再び応答します(Assistant)。この順番取りパターンは、AIがトレーニングされた方法であり、この構造を期待します。

  3. Userで終わる必要がある: AIは最後のUserメッセージに対する応答を生成します。会話がAssistantで終わる場合、AIが応答するものがありません。

有効な例:

python
# System + 単一のUser
[("system", "..."), ("user", "...")]
 
# System + 会話
[("system", "..."), ("user", "..."), ("assistant", "..."), ("user", "...")]

問題のあるパターン:

python
# UserよりもAssistantが先 - AIがコンテキストについて混乱
[("system", "..."), ("assistant", "..."), ("user", "...")]
# AIは質問なしで応答を見ます。これがどの質問に答えているのかを幻覚する可能性があり、
# 無関係または混乱した応答につながります。
python
# 2つのUserメッセージが連続 - AI応答が不足
[("system", "..."), ("user", "..."), ("user", "...")]
# AIはどのUserメッセージに応答すべきかわからないか、それらを不自然にマージする可能性があります。
# 会話の流れを失います。
python
# Assistantで終わる - 応答するものがない
[("system", "..."), ("user", "..."), ("assistant", "...")]
# 会話は完了しています。保留中のUser質問がないため、AIは生成するものがありません。
# エラーまたは空の応答を生成する可能性があります。

重要なポイント: これらのパターンは常にハードエラーを引き起こすわけではありませんが、AIがトレーニングされた会話ロジックを破るため、AIを混乱させます。AIは応答を生成するかもしれませんが、信頼性がないか無意味になります。予測可能な動作のために、常に期待されるパターンに従ってください。

シンプルな履歴を超えて: 本番パターン(プレビュー)

重要な注意: 今学んだ会話履歴パターンは素晴らしい基礎ですが、本番システムはより洗練されたアプローチを使用します。

生の履歴の問題:

すべての会話履歴をAIに単純に渡すことには制限があります:

  1. トークンの無駄: すべてのメッセージ(古いものでも)がトークン制限にカウントされ、コストがかかります
  2. 焦点の喪失: AIは無関係な以前の会話に気を取られる可能性があります
  3. 明示的なタスクなし: AIは履歴から何をすべきかを推測するのではなく、明確な指示を受け取りません

より良いアプローチ:

本番システムはコンテキストと指示を分離します:

シンプルな履歴アプローチ(今学んだもの):

python
messages = [
    ("system", "You are a Python expert."),
    ("user", "What's a dictionary?"),
    ("assistant", "A dictionary is a key-value data structure."),
    ("user", "Show an example.")
]

本番アプローチ(後の章で登場):

python
messages = [
    ("system", "You are a Python expert."),
    ("user", """コンテキスト: ユーザーは以前にPythonの辞書について尋ね、それらがキー-値構造であることを学びました。
 
タスク: 辞書の使用法を示すコード例を提供してください。""")
]

違い:

  • 生の履歴: AIは完全な会話を見て、何をすべきかを理解します
  • 本番パターン: AIは要約されたコンテキスト + 明示的な指示を受け取ります

分離の利点:

  • トークンが少ない(コストが低く、応答が速い)
  • より信頼性の高い動作(明確な指示)
  • より良い制御(どのコンテキストが重要かを決定します)

これを学ぶ場所:

  • 第8章: 会話状態とメモリの管理
  • 第11章: コンテキスト検索(RAGと会話メモリの組み合わせ)
  • 第16章: 会話コンテキストに基づく動的ルーティング

今のところ、生の履歴を理解することは不可欠です—これらの高度なパターンの基礎です。しかし、覚えておいてください: 今学んだことは教育ツールであり、最終的な解決策ではありません。

4.3) プロンプトの外部化: テンプレートファイルの管理(.yaml)

なぜプロンプトを外部化するのか?

AIアプリケーションが成長するにつれて、Pythonコードでプロンプトを管理することは扱いにくくなります。プロンプトをYAMLファイルに外部化することで、次のことが可能になります:

非技術的なコラボレーション: プロダクトマネージャー、ドメインエキスパート、プロンプトエンジニアは、Pythonコードに触れたり、プログラミングの概念を理解したりすることなく、YAMLファイルを編集できます。

バージョン管理の明確さ: プロンプトの変更をコードの変更とは別に追跡します。プロンプトの調整とロジックの更新が一緒に表示される混合コミットはもうありません。

環境固有のプロンプト: コード変更なしで、開発、ステージング、本番用の異なるプロンプト。

A/Bテスト: 異なるファイルをロードすることでプロンプトのバリエーションをテスト—コード変更は不要です。

YAMLプロンプトファイルは、従来のアプリケーションの設定ファイルのようなものだと考えてください—コード変更や再デプロイを必要とせずに動作を定義します。

YAMLとは?

YAMLは、設定ファイルによく使用される人間が読める形式のデータフォーマットです。YAMLを見たことがない場合は、JSONのよりクリーンな代替品だと考えてください—括弧の代わりにインデントを使用し、読みやすく編集しやすいです。

YAMLプロンプト構造

LangChainはプロンプト用の標準的なYAMLファイル構造を定義しています。例を見てみましょう:

例1: 変数なしのプロンプト

プロンプトが実行時の値を必要としない場合、input_variables を空のリストに設定します:

yaml
# prompts/system_prompt.yaml
_type: prompt
input_variables: []
template: |
  You are a helpful assistant.
  Please answer in a friendly and encouraging tone.

| 記号を使用すると、複数行のテキストを書くことができ、改行が保持されます。

例2: 変数を持つプロンプト

プロンプトが実行時の値を必要とする場合、それらを input_variables にリストします:

yaml
# prompts/user_prompt.yaml
_type: prompt
input_variables:
  - user_input
template: |
  User question: {user_input}
  Please provide a clear answer.

実行時に、{user_input} プレースホルダーが実際の値に置き換えられます。

主要なコンポーネント:

  • _type: prompt: これがプロンプトテンプレートであることを識別します
  • input_variables: テンプレートで使用されるすべてのプレースホルダーをリストします(なければ空のリスト []
  • template: {placeholders} を含む実際のプロンプトテキスト

YAMLプロンプトのロードと使用

基本的なロード:

それでは、作成したYAMLファイルをロードして、LLMで使用しましょう:

python
from langchain_core.prompts import load_prompt, ChatPromptTemplate
from langchain_openai import ChatOpenAI
 
# YAMLファイルからプロンプトをロード
system_prompt_template = load_prompt("prompts/system_prompt.yaml")
user_prompt_template = load_prompt("prompts/user_prompt.yaml")
 
# ロードされたプロンプトをチャットテンプレートに結合
chat_template = ChatPromptTemplate.from_messages([
    ("system", system_prompt_template.template),
    ("user", user_prompt_template.template)
])
 
llm = ChatOpenAI(model="gpt-4o-mini")
 
# ステップ1: 実行時の値でテンプレートをレンダリング
messages = chat_template.invoke({"user_input": "What is LangChain?"})
 
# ステップ2: LLMに送信
response = llm.invoke(messages)
print(response.content)

ロードされたテンプレートの検証:

テンプレートを使用する前に、正しくロードされたことを確認します:

python
from langchain_core.prompts import load_prompt
 
# テンプレートをロード
user_prompt_template = load_prompt("prompts/user_prompt.yaml")
 
# 期待される変数を確認
print("Input variables:", user_prompt_template.input_variables)
 
# テンプレートテキストを確認
print("Template:", user_prompt_template.template)

出力:

Input variables: ['user_input']
Template: User question: {user_input}
Please provide a clear answer.

一般的なYAMLの間違い

間違い1: 一貫性のないインデント

YAMLは一貫したインデント(通常2スペース)を必要とします。各レベルは同じ量のスペースを使用する必要があります:

間違い:

yaml
_type: prompt
input_variables:
- user_input      # 間違い: リスト項目はインデントする必要があります
  - question      # 間違い: 混在したインデントレベル

正しい:

yaml
_type: prompt
input_variables:
  - user_input    # 正しい: 両方の項目が同じインデントレベル
  - question

間違い2: プレースホルダーの不一致

template のプレースホルダーは input_variables と一致する必要があります:

間違い:

yaml
input_variables:
  - user_input
template: "Question: {question}"  # 'question' は input_variables にありません!

正しい:

yaml
input_variables:
  - user_input
template: "Question: {user_input}"

プレースホルダーが宣言された変数と一致しない場合、LangChainはエラーを発生させます。

4.4) 実行前のテンプレートのプレビューと検証

なぜテンプレートをプレビューするのか?

プロンプトエンジニアリングは反復的です。文言を調整し、構造を調整し、例を追加します—そして各反復はAPIトークンと時間がかかります。実行前にテンプレートをプレビューすることで、次のことができます:

時間とお金の節約: 高価なAPI呼び出しを行う前にエラーを検出します。

正確性の検証: 変数が正しく埋められ、フォーマットが期待通りであることを確認します。

効率的なデバッグ: すべての変数が埋められ、フォーマットが適用された、LLMに送信される正確なプロンプトを確認します。

テンプレートプレビューは、printデバッグのようなものだと考えてください—実行前に中間状態を検査して正確性を確認します。

基本的なテンプレートプレビュー

テンプレート構造の検査:

LLMでテンプレートを使用する前に、その構造を検査し、サンプルデータでどのようにレンダリングされるかをプレビューします:

python
from langchain_core.prompts import ChatPromptTemplate
 
template = ChatPromptTemplate.from_messages([
    ("system", "You are a {role}."),
    ("user", "{user_input}")
])
 
# テンプレート構造をプレビュー
print("Input variables:", template.input_variables)
print("Message count:", len(template.messages))
 
# サンプルデータでプレビュー
prompt_value = template.invoke({
    "role": "Python programming expert",
    "user_input": "What is Python?"
})
 
print("\nPreview:")
for msg in prompt_value.to_messages():
    print(f"{msg.type}: {msg.content}")

出力:

Input variables: ['role', 'user_input']
Message count: 2
 
Preview:
system: You are a Python programming expert.
human: What is Python?

これは、LLMに送信される内容を正確に示し、実行前にプロンプトを検証できます。

テンプレートの検証: 不足している変数の検出

最も一般的なテンプレートエラーは、必要な変数が不足していることです。不足している変数を検出する再利用可能な検証関数は次のとおりです:

python
from langchain_core.prompts import ChatPromptTemplate
 
def preview_template(template: ChatPromptTemplate, inputs: dict):
    """指定された入力でテンプレートをプレビューし、エラーを検出します。"""
    try:
        prompt_value = template.invoke(inputs)
        
        print("TEMPLATE PREVIEW")
        print("=" * 60)
        
        for i, msg in enumerate(prompt_value.to_messages(), 1):
            print(f"Message {i} ({msg.type.upper()}):")
            print(msg.content)
            print("-" * 60)
                
    except KeyError as e:
        print(f"ERROR: {e}")
        print(f"Required variables: {template.input_variables}")
 
# 使用法
template = ChatPromptTemplate.from_messages([
    ("system", "You are a {role}."),
    ("user", "{user_input}")
])
 
# 有効な入力
preview_template(template, {
    "role": "Python programming expert",
    "user_input": "What is Python?"
})
 
# 不足している変数
preview_template(template, {
    "user_input": "What is Python?"  # 'role' が不足
})

出力:

TEMPLATE PREVIEW
============================================================
Message 1 (SYSTEM):
You are a Python programming expert.
------------------------------------------------------------
Message 2 (HUMAN):
What is Python?
------------------------------------------------------------
 
ERROR: "Input to ChatPromptTemplate is missing variables {'role'}.
Expected: ['role', 'user_input'] Received: ['user_input']
...
Required variables: ['role', 'user_input']

検証ワークフロー:

典型的なテンプレート検証プロセスは次のとおりです:

エラー

有効

いいえ

はい

テンプレートを定義

サンプルデータをロード

入力を検証

テンプレート/データを修正

メッセージをプレビュー

準備完了?

LLMで実行

この反復プロセスは、高価なLLM呼び出しの前にエラーを検出するのに役立ちます。

実行前チェックリスト

テンプレートを本番に送る前に:

  • すべての input_variables がYAML/テンプレートで宣言されている
  • サンプルデータがエラーなしでレンダリングされる
  • 複数行のプロンプトが正しく表示される
  • プレースホルダーが変数名と正確に一致する
  • エッジケース(空の文字列、長いテキスト)でテストする

章のまとめ:

LangChainのテンプレートシステムを使用して、保守可能で再利用可能なプロンプトを設計する方法を学びました:

  1. 関心の分離: より簡単な保守と反復のために、プロンプトをコードから分離します
  2. ロール認識テンプレート: 適切な指示階層を持つ構造化されたLLMインタラクションのために、system、user、assistantメッセージを使用します
  3. 外部化されたプロンプト: 非技術的なコラボレーションとバージョン管理のために、YAMLファイルでプロンプトを管理します
  4. プレビューと検証: 実行前にエラーを早期に検出し、テンプレートを検証します

次のステップ:

第5章では、テンプレートがプレビューエージェントの例で自律的な意思決定をどのように可能にするかを見ます。その後、第6章では、LCEL(LangChain Expression Language)を学び、| 演算子を使用してこれらのテンプレートを強力なパイプラインに構成します。