Databricks Certified Generative AI Engineer Associate 教科書
第3章 アプリケーション開発(Application Development, 30%)
🎯 この節の学習目標
prompt | llm | parser でチェーンを組み立て、invoke() で実行できるChatDatabricks を使って Databricks のモデルサービングエンドポイントをチェーンに組み込める第1章で学んだとおり、実用的な生成AIアプリは「プロンプトを組み立てる → LLM を呼ぶ → 出力を整形する」という複数ステップの連なり(チェーン)で構成されます。LangChain は、この各ステップを部品(コンポーネント)として定義し、それらを宣言的につなぐためのオープンソースフレームワークです。試験の第3セクション「Application Development」は全体の30%を占める最重要領域で、その中核が LangChain によるチェーン構築です。
最小のチェーンは、次の3要素からなります。
| 要素 | 代表的なクラス | 役割 |
|---|---|---|
| ① プロンプトテンプレート | PromptTemplate / ChatPromptTemplate | ユーザー入力({変数})を差し込んで、モデルに渡すプロンプトを組み立てる |
| ② モデル | LLM / ChatModel(例:ChatDatabricks) | プロンプトを受け取り、テキスト(またはメッセージ)を生成する |
| ③ 出力パーサー | StrOutputParser など | モデルの出力(メッセージオブジェクト等)を、後続処理で使いやすい形式(文字列・JSON等)に変換する |
PromptTemplate は単一のテキストプロンプト用、ChatPromptTemplate は system / user などのロール付きメッセージ列を組み立てるチャットモデル用です。現在の主流はチャットモデルなので、ChatPromptTemplate + ChatModel の組み合わせを基本形として押さえましょう。
LCEL(LangChain Expression Language)は、コンポーネントを Unix のパイプのように | 演算子でつなぐ記法です。データは左から右へ流れます。
chain = prompt | llm | parser
result = chain.invoke({"question": "Delta Lakeとは何ですか?"})
invoke(入力) — チェーンを1回実行して結果を返す(基本の実行メソッド){変数名} と一致させるデータの流れ: {"question": "..."} →(prompt が変数を差し込む)→ 完成したプロンプト →(llm が生成)→ AIMessage オブジェクト →(parser が変換)→ 文字列
📝 試験のポイント
試験では「このチェーンの構成要素の正しい並び順はどれか」「StrOutputParser の役割は何か」といった形で問われます。順序は必ずプロンプト → モデル → 出力パーサーです。「パーサーが先」「モデルが先」といった選択肢は誤りです。また、各要素の役割の対応(テンプレート=入力の整形、モデル=生成、パーサー=出力の変換)を即答できるようにしておきましょう。
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,
)
endpoint にはサービングエンドポイント名を指定する(Foundation Model API の pay-per-token エンドポイントも、自分でデプロイしたカスタムエンドポイントも同じ書き方)temperature や max_tokens などの生成パラメータも指定できる(1-4参照)💡 具体例:シンプルな「質問→丁寧な回答」チェーン
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表示には文字列化が定石です。
テンプレートの変数は複数置けます。その場合、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 キーに何を割り当てるか」という形で問われることがあります。「検索結果を整形する」「出力をパースする」といった説明は誤りです。
LCEL の利点は、部品を差し替えやすいことです。モデルを変えたければ llm の定義だけを、出力を JSON にしたければパーサーだけを差し替えれば、チェーンの他の部分は変わりません。第1章で行った「タスク分解」の各ステージを、そのまま1本ずつのチェーン(またはチェーンの一段)として実装していくのが基本方針です。
| やりたいこと | 差し替える・追加する部品 |
|---|---|
| モデルを変更する | ChatDatabricks の endpoint を変える(他は不変) |
| 出力を文字列で受け取る | StrOutputParser を末尾に置く |
| 外部知識を注入する(RAG) | retriever + RunnablePassthrough の辞書段を先頭に置く(3-4) |
| システム指示を固定する | ChatPromptTemplate の system メッセージに書く |
✅ この節のまとめ
chain = prompt | llm | parser とパイプでつなぎ、invoke(辞書) で実行する。辞書のキーはテンプレートの {変数名} に対応する。ChatDatabricks(endpoint="...") でチェーンに組み込める。StrOutputParser はモデルのメッセージ出力を文字列に変換する。RunnablePassthrough は入力をそのまま素通しし、RAG チェーンで質問を後段へ渡すのに使う。問1. LCEL で「ユーザーの質問を受け取り、テンプレートに差し込んでモデルに渡し、結果を文字列で得る」チェーンを構築したい。正しい構成はどれか。
chain = llm | prompt | StrOutputParser()chain = prompt | llm | StrOutputParser()chain = StrOutputParser() | prompt | llmchain = prompt | StrOutputParser() | llm正解:B
データは左から右へ流れるため、まずテンプレートが入力変数を差し込んでプロンプトを組み立て(prompt)、次にモデルが生成し(llm)、最後にパーサーが出力を文字列化します。Aはプロンプトが完成する前にモデルを呼んでおり、CとDはモデル出力が存在しない段階でパーサーを適用しているため、いずれも誤りです。
問2. チェーン prompt | llm | StrOutputParser() における StrOutputParser の役割として正しいものはどれか。
正解:C
出力パーサーはチェーンの最終段で、モデルの出力を後続処理で扱いやすい形式に変換する部品です。入力の検証(A)はガードレール(3-6)の役割、変数の差し込み(B)はプロンプトテンプレートの役割です。Dのような内容の加工(要約)は行いません。
問3. Databricks の Foundation Model API のエンドポイントを LangChain のチェーンから利用したい。最も適切な方法はどれか。
ChatDatabricks(endpoint="databricks-meta-llama-3-3-70b-instruct") のようにエンドポイント名を指定してモデルとして組み込む正解:A
databricks-langchain パッケージの ChatDatabricks は、サービングエンドポイント名を指定するだけで LangChain の ChatModel として利用できます。Bの重みのダウンロードは不要かつ非現実的で、CはLangChain統合が存在するため誤りです。DのAI Searchインデックスは検索用のオブジェクトであり、LLMの呼び出し先ではありません。
問4. RAG チェーン {"context": retriever, "question": RunnablePassthrough()} | prompt | llm | parser における RunnablePassthrough() の役割はどれか。
正解:B
RunnablePassthrough は入力を無加工で通過させる部品です。この構成では、ユーザーの質問が retriever(検索実行)と RunnablePassthrough(素通し)の両方へ渡り、検索結果と元の質問がそれぞれ {context} と {question} に割り当てられます。並べ替え(A)はリランカー(3-5)、出力の検証(D)はガードレール(3-6)の役割です。