コンテンツにスキップ

8.3.3 LangChain の基礎

  • LangChain のようなチェーン型の抽象化が、なぜ自然に登場するのかを理解する
  • Prompt、モデル、パーサー、検索器がチェーンの中でどこにあるかを理解する
  • 最小例を通して、「前のステップの出力を次のステップへ渡す」感覚をつかむ
  • それがなぜプロトタイプや線形ワークフローに向いているのかを理解する

この節では、初心者にとって一番わかりやすい順番は「先にフレームワーク API を学ぶ」ことではなく、まず次をはっきり見ることです。

flowchart LR
A["実際のアプリは 1 回だけモデルを呼ばない"] --> B["複数の小さなステップに分ける"]
B --> C["各ステップに明確な入出力がある"]
C --> D["それを順番につなぐ"]

つまり、この節が本当に解決したいのは次の点です。

  • なぜチェーン型の抽象化が自然に生まれるのか
  • それは一体、何を整理してくれるのか

一、なぜ「チェーン」という抽象化が必要なのか?

Section titled “一、なぜ「チェーン」という抽象化が必要なのか?”

実際のアプリは、たいていモデルを 1 回呼ぶだけではないから

Section titled “実際のアプリは、たいていモデルを 1 回呼ぶだけではないから”

たとえば小さな QA システムを作るだけでも、次のようなステップがあります。

  1. ユーザーの クエリ を整える
  2. 文書を検索する
  3. prompt を組み立てる
  4. モデルを呼ぶ
  5. 出力を整形する

これをすべて 1 つの関数に手書きしても動かせますが、すぐに次のような状態になりがちです。

  • 見通しが悪い
  • 再利用しにくい
  • デバッグしにくい

チェーン型の抽象化は何をしているのか?

Section titled “チェーン型の抽象化は何をしているのか?”

それはつまり、

各ステップを責務がはっきりした小さなコンポーネントにして、順番につなぐ

ということです。

これが LangChain の最も重要な感覚です。


class SimpleChain:
def __init__(self, steps):
self.steps = steps
def run(self, value):
for step in self.steps:
value = step(value)
return value
def normalize_query(text):
return text.strip().lower()
def retrieve_docs(query):
if "返金" in query:
return {"query": query, "docs": ["コース購入後 7 日以内なら返金できます。"]}
return {"query": query, "docs": []}
def format_answer(payload):
if payload["docs"]:
return f"資料によると:{payload['docs'][0]}"
return "関連する資料が見つかりませんでした。"
chain = SimpleChain([
normalize_query,
retrieve_docs,
format_answer
])
print(chain.run(" 返金ポリシーは何ですか? "))

期待される出力:

Terminal window
資料によると:コース購入後 7 日以内なら返金できます。

このコードは何を教えているのか?

Section titled “このコードは何を教えているのか?”

ここで教えているのは、LangChain の最も核心的な考え方です。

各ステップは自分の入出力だけに集中し、システム全体はそれをつないでタスクを完成させる。

これがチェーン型アプリケーションの一番重要な価値です。


三、Prompt はチェーンの中でどんな役割をするのか?

Section titled “三、Prompt はチェーンの中でどんな役割をするのか?”

Prompt は「付属文」ではなく、1 つのコンポーネント

Section titled “Prompt は「付属文」ではなく、1 つのコンポーネント”

多くのワークフローでは、Prompt 自体が途中の 1 ステップになります。

  • クエリ を入力する
  • もっとわかりやすい prompt テンプレートを作る
def build_prompt(payload):
docs = payload["docs"]
query = payload["query"]
return f"次の資料をもとに質問に答えてください:資料={docs},質問={query}"
payload = {"query": "返金ポリシーは何ですか", "docs": ["コース購入後 7 日以内なら返金できます。"]}
print(build_prompt(payload))

期待される出力:

Terminal window
次の資料をもとに質問に答えてください:資料=['コース購入後 7 日以内なら返金できます。'],質問=返金ポリシーは何ですか

この例が伝えているのは、

Prompt もチェーンの中の中間変換ノードとして見られる

ということです。


四、さらに「モデル」のステップを加える

Section titled “四、さらに「モデル」のステップを加える”

オフラインでも動くように、ここでも mock model を使います。

def mock_llm(prompt):
return f"モデル出力:{prompt}"
chain = SimpleChain([
normalize_query,
retrieve_docs,
build_prompt,
mock_llm
])
print(chain.run("返金ポリシーは何ですか?"))

期待される出力:

Terminal window
モデル出力:次の資料をもとに質問に答えてください:資料=['コース購入後 7 日以内なら返金できます。'],質問=返金ポリシーは何ですか?

このステップでいちばん大事な気づき

Section titled “このステップでいちばん大事な気づき”

次のものが見えてきます。

  • 検索器
  • prompt builder
  • model

これらは実は、チェーン上の別々のノードにすぎません。

LangChain が「コンポーネントを組み立てるフレームワーク」に見えるのは、このためです。

LangChain コンポーネントのパイプライン図


五、なぜ出力パーサーも重要なのか?

Section titled “五、なぜ出力パーサーも重要なのか?”

多くの人は入力 prompt とモデル出力だけに注目しますが、次のことを忘れがちです。

モデルが出力したあとも、システムはしばしば構造化処理を続ける必要がある。

たとえば:

  • 一部のフィールドだけ取り出す
  • JSON に変換する
  • フロントエンド表示用の形式にマッピングする
def output_parser(text):
return {
"answer": text.replace("モデル出力:", ""),
"ok": True
}
chain = SimpleChain([
normalize_query,
retrieve_docs,
build_prompt,
mock_llm,
output_parser
])
print(chain.run("返金ポリシーは何ですか?"))

期待される出力:

Terminal window
{'answer': "次の資料をもとに質問に答えてください:資料=['コース購入後 7 日以内なら返金できます。'],質問=返金ポリシーは何ですか?", 'ok': True}

これで次のことが、よりはっきりわかるようになります。

LangChain の本当の価値は、「異なるコンポーネントの境界をきれいに分けること」にあることが多い。


六、なぜプロトタイプに特に向いているのか?

Section titled “六、なぜプロトタイプに特に向いているのか?”

それは、初期の LLM アプリは多くの場合、次のような形だからです。

  • 比較的線形なフロー
  • 複数のコンポーネントが順番に実行される

たとえば:

  • クエリ を整える
  • 検索する
  • prompt を組み立てる
  • モデルを呼ぶ
  • 結果を解析する

これはまさに、チェーン型の抽象化が最も気持ちよくハマる場面です。


七、いつから苦しくなるのか?

Section titled “七、いつから苦しくなるのか?”

フローが次のようになってくると、だんだん直線的なチェーンではつらくなります。

  • 検索に失敗したら クエリ を書き換えてもう一度検索する
  • 答えが十分に安定していなければ レビュー担当 に再チェックさせる
  • あるリクエストはツールを使い、別のリクエストは使わない

このような場合、「1 本の直線チェーン」だけではだんだん無理が出てきます。

つまり、

システムに明確な状態分岐やループが出てきたら、チェーン型の抽象化だけでは足りないことがある。

これが、後で LangGraph のような、よりグラフ指向のフレームワークが必要になる理由です。


八、とても重要な実装上の注意

Section titled “八、とても重要な実装上の注意”

LangChain を学ぶときに、最もよくある失敗は次のようなものです。

  • 最初からたくさんのクラス名やインターフェースを覚えようとする

でも、より安定した学び方はたいてい次の順番です。

  1. まずチェーン型の抽象化が何を解決するのかを理解する
  2. そのあとで具体的な API を見る

そうしないと、次のようになりやすいです。

  • フレームワークのコードは書ける
  • でも、なぜそのように組むのかがわからない

初心者が最初に LangChain を使うときの安定した方法

Section titled “初心者が最初に LangChain を使うときの安定した方法”

一般に、次の順番がいちばん安全です。

  1. まずは 1 本の線形チェーンだけ作る
  2. 各ノードの入出力をまずはっきり表示する
  3. そのあとで検索、パーサー、より複雑なコンポーネントを追加する
  4. 最後に、より複雑なグラフ型ワークフローを考える

このページを終えたら、この証拠カードを残します。

要求
入力、状態、tools/context、期待される出力の契約
検証済み出力
パーサー/スキーマ、または業務ルール確認の結果
追跡記録
モデル呼び出し、ツール/関数呼び出し、文書解析、または対話状態
失敗確認
フォーマット不正、必須フィールド不足、古い状態、または誤ったツール
次の行動
prompt、schema、state、API、または parsing の改善

この節でいちばん大事なのは、特定のクラス名を覚えることではなく、次を理解することです。

LangChain の核心的な価値は、「prompt、検索、モデル、解析」という頻出コンポーネントを、よりわかりやすい線形ワークフローとして整理することにある。

このチェーン思考が身につけば、あとで実際のフレームワークの API を見るときに、かなり理解しやすくなります。

  • LangChain はモデルを置き換えるものではなく、多段アプリケーションを整理するもの
  • まずチェーンを理解してからフレームワークを学ぶほうが、API をいきなり覚えるより安定する
  • プロトタイプや線形ワークフローには特に向いているが、あらゆる複雑なシステムの最終形ではない

  1. この SimpleChain に、クエリ を検索しやすい形に書き換えるステップを 1 つ追加してみてください。
  2. 自分の言葉で説明してください。なぜ Prompt もチェーンの中の 1 つのコンポーネントとして見られるのでしょうか?
  3. 考えてみてください。フローに複雑な分岐が入り始めると、なぜチェーン型の抽象化はつらくなるのでしょうか?
  4. 自分の言葉で説明してください。LangChain はどのような形の問題に最も向いていますか?
参考実装と解説
  1. クエリ書き換えステップでは、同義語をそろえ、元の意図を保ったまま検索向きの表現に変えます。
  2. Prompt は入力を指示に変換し、出力の形を制約するので、チェーン内の1つの component と見なせます。
  3. 分岐、retry、状態に基づく判断、tool loop が増えると、線形の chain は扱いづらくなります。
  4. LangChain は、Prompt、retriever、tool、parser などを組み合わせた、比較的予測しやすい LLM workflow に向いています。