第3章 アプリケーション開発 / 想定学習時間:30〜40分 / 最終確認:2026年8月

3-1. LangChainによるチェーン構築(プロンプト→LLM→出力パーサー)

🎯 この節の学習目標

1. LangChain とは何か:部品を「つなぐ」ためのフレームワーク

第1章で学んだとおり、実用的な生成AIアプリは「プロンプトを組み立てる → LLM を呼ぶ → 出力を整形する」という複数ステップの連なり(チェーン)で構成されます。LangChain は、この各ステップを部品(コンポーネント)として定義し、それらを宣言的につなぐためのオープンソースフレームワークです。試験の第3セクション「Application Development」は全体の30%を占める最重要領域で、その中核が LangChain によるチェーン構築です。

最小のチェーンは、次の3要素からなります。

要素代表的なクラス役割
① プロンプトテンプレートPromptTemplate / ChatPromptTemplateユーザー入力({変数})を差し込んで、モデルに渡すプロンプトを組み立てる
② モデルLLM / ChatModel(例:ChatDatabricks)プロンプトを受け取り、テキスト(またはメッセージ)を生成する
③ 出力パーサーStrOutputParser などモデルの出力(メッセージオブジェクト等)を、後続処理で使いやすい形式(文字列・JSON等)に変換する

PromptTemplate は単一のテキストプロンプト用、ChatPromptTemplate は system / user などのロール付きメッセージ列を組み立てるチャットモデル用です。現在の主流はチャットモデルなので、ChatPromptTemplate + ChatModel の組み合わせを基本形として押さえましょう。

2. LCEL:パイプ記法でチェーンを宣言する

LCEL(LangChain Expression Language)は、コンポーネントを Unix のパイプのように | 演算子でつなぐ記法です。データは左から右へ流れます。

chain = prompt | llm | parser
result = chain.invoke({"question": "Delta Lakeとは何ですか?"})

データの流れ: {"question": "..."} →(prompt が変数を差し込む)→ 完成したプロンプト →(llm が生成)→ AIMessage オブジェクト →(parser が変換)→ 文字列

📝 試験のポイント

試験では「このチェーンの構成要素の正しい並び順はどれか」「StrOutputParser の役割は何か」といった形で問われます。順序は必ずプロンプト → モデル → 出力パーサーです。「パーサーが先」「モデルが先」といった選択肢は誤りです。また、各要素の役割の対応(テンプレート=入力の整形、モデル=生成、パーサー=出力の変換)を即答できるようにしておきましょう。

3. Databricks のモデルをチェーンで使う:ChatDatabricks

Databricks 上では、Foundation Model API やモデルサービングエンドポイントで提供されるモデルを、databricks-langchain パッケージの ChatDatabricks クラスから呼び出せます。エンドポイント名を指定するだけで、LangChain の ChatModel としてそのまま使えます。

from databricks_langchain import ChatDatabricks

llm = ChatDatabricks(
    endpoint="databricks-meta-llama-3-3-70b-instruct",
    temperature=0.1,
)

💡 具体例:シンプルな「質問→丁寧な回答」チェーン

from databricks_langchain import ChatDatabricks
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# ① プロンプトテンプレート
prompt = ChatPromptTemplate.from_messages([
    ("system", "あなたは丁寧な日本語で回答するアシスタントです。"),
    ("user", "{question}"),
])

# ② モデル
llm = ChatDatabricks(endpoint="databricks-meta-llama-3-3-70b-instruct")

# ③ 出力パーサー
parser = StrOutputParser()

# LCEL でつなぐ
chain = prompt | llm | parser

answer = chain.invoke({"question": "Unity Catalog の役割を教えてください。"})
print(answer)  # → 文字列として回答が返る

StrOutputParser を通すことで、モデルが返す AIMessage オブジェクトから content(本文)だけを文字列として取り出せます。パーサーを省くと戻り値はメッセージオブジェクトになるため、後続処理やUI表示には文字列化が定石です。

4. 複数入力と RunnablePassthrough:RAG チェーンへの拡張

テンプレートの変数は複数置けます。その場合、invoke() には対応するキーをすべて含む辞書を渡します。

prompt = ChatPromptTemplate.from_template(
    "以下のコンテキストに基づいて質問に答えてください。\n"
    "コンテキスト: {context}\n質問: {question}"
)
chain.invoke({"context": "...", "question": "..."})

ここで「context は検索(retriever)で自動的に埋めたい、question はユーザー入力をそのまま通したい」という場面が出てきます。これを実現するのが RunnablePassthrough です。入力をそのまま素通しするコンポーネントで、辞書を組み立てる段に置きます。

from langchain_core.runnables import RunnablePassthrough

rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
    | parser
)
rag_chain.invoke("Databricks AI Search(旧 Vector Search) の同期モードは?")

この形では、ユーザーの質問文字列が retriever(検索)と RunnablePassthrough(素通し)の両方に渡され、その結果が {"context": 検索結果, "question": 元の質問} という辞書に組み立てられてプロンプトへ流れます。これが RAG チェーンの骨格であり、retriever の作り方は 3-3、プロンプトへの組み込み方は 3-4 で詳しく扱います。

📝 試験のポイント

RunnablePassthrough は「ユーザー入力を変更せずに後段へ渡す」ための部品です。試験では RAG チェーンのコードの穴埋めで「question キーに何を割り当てるか」という形で問われることがあります。「検索結果を整形する」「出力をパースする」といった説明は誤りです。

5. チェーン設計の考え方:小さく作って、つなぐ

LCEL の利点は、部品を差し替えやすいことです。モデルを変えたければ llm の定義だけを、出力を JSON にしたければパーサーだけを差し替えれば、チェーンの他の部分は変わりません。第1章で行った「タスク分解」の各ステージを、そのまま1本ずつのチェーン(またはチェーンの一段)として実装していくのが基本方針です。

やりたいこと差し替える・追加する部品
モデルを変更するChatDatabricksendpoint を変える(他は不変)
出力を文字列で受け取るStrOutputParser を末尾に置く
外部知識を注入する(RAG)retriever + RunnablePassthrough の辞書段を先頭に置く(3-4)
システム指示を固定するChatPromptTemplate の system メッセージに書く

✅ この節のまとめ

練習問題

問1. LCEL で「ユーザーの質問を受け取り、テンプレートに差し込んでモデルに渡し、結果を文字列で得る」チェーンを構築したい。正しい構成はどれか。

  1. chain = llm | prompt | StrOutputParser()
  2. chain = prompt | llm | StrOutputParser()
  3. chain = StrOutputParser() | prompt | llm
  4. chain = prompt | StrOutputParser() | llm
解答と解説を見る

正解:B

データは左から右へ流れるため、まずテンプレートが入力変数を差し込んでプロンプトを組み立て(prompt)、次にモデルが生成し(llm)、最後にパーサーが出力を文字列化します。Aはプロンプトが完成する前にモデルを呼んでおり、CとDはモデル出力が存在しない段階でパーサーを適用しているため、いずれも誤りです。

問2. チェーン prompt | llm | StrOutputParser() における StrOutputParser の役割として正しいものはどれか。

  1. ユーザー入力を検証し、不正なプロンプトを拒否する
  2. プロンプトテンプレートの変数にユーザー入力を差し込む
  3. モデルが返すメッセージオブジェクトから本文を取り出し、文字列に変換する
  4. モデルの出力を要約して短くする
解答と解説を見る

正解:C

出力パーサーはチェーンの最終段で、モデルの出力を後続処理で扱いやすい形式に変換する部品です。入力の検証(A)はガードレール(3-6)の役割、変数の差し込み(B)はプロンプトテンプレートの役割です。Dのような内容の加工(要約)は行いません。

問3. Databricks の Foundation Model API のエンドポイントを LangChain のチェーンから利用したい。最も適切な方法はどれか。

  1. ChatDatabricks(endpoint="databricks-meta-llama-3-3-70b-instruct") のようにエンドポイント名を指定してモデルとして組み込む
  2. モデルの重みファイルをダウンロードし、ノートブック上でロードする
  3. LangChain は Databricks のモデルに対応していないため、REST API を直接呼ぶしかない
  4. AI Search インデックスを作成し、その中でモデルを指定する
解答と解説を見る

正解:A

databricks-langchain パッケージの ChatDatabricks は、サービングエンドポイント名を指定するだけで LangChain の ChatModel として利用できます。Bの重みのダウンロードは不要かつ非現実的で、CはLangChain統合が存在するため誤りです。DのAI Searchインデックスは検索用のオブジェクトであり、LLMの呼び出し先ではありません。

問4. RAG チェーン {"context": retriever, "question": RunnablePassthrough()} | prompt | llm | parser における RunnablePassthrough() の役割はどれか。

  1. 検索結果を関連度順に並べ替える
  2. ユーザーの入力(質問)を変更せずにそのまま prompt の {question} 変数へ渡す
  3. コンテキストが空の場合にエラーを発生させる
  4. モデルの出力を検証し、有害な内容をブロックする
解答と解説を見る

正解:B

RunnablePassthrough は入力を無加工で通過させる部品です。この構成では、ユーザーの質問が retriever(検索実行)と RunnablePassthrough(素通し)の両方へ渡り、検索結果と元の質問がそれぞれ {context} と {question} に割り当てられます。並べ替え(A)はリランカー(3-5)、出力の検証(D)はガードレール(3-6)の役割です。