LangChainをマスターする:堅牢なLLMアプリケーション構築のための開発者ガイド

AI tutorial - IT technology blog
AI tutorial - IT technology blog

クイックスタート:午前2時にページャーが鳴り、LLMソリューションが必要なとき

午前2時です。本番環境がダウンし、複雑で構造化されていないデータから即座に知的な応答が求められています。

このような状況で私が最初に考えるのは常に、「LLMがこの問題をどれだけ早く解決してくれるか?」ということです。まさにここでLangChainは非常に貴重なツールとなります。これは単なるライブラリではなく、大規模言語モデル(LLM)の作業の複雑さを簡素化し、基本的なプロンプト応答を超えた複雑なタスクを実行するアプリケーションを構築できるように設計されたフレームワークです。

それでは、基本から始めましょう。一秒を争う状況では、明確で簡潔なガイダンスが必要です。

インストール:LangChainをあなたのマシンに導入する

まず、必要なツールが必要です。まだLangChainをインストールしていない場合は、インストールしてください。私は通常、クリーンなセットアップを維持するために仮想環境内でこれを行います。


pip install langchain_community langchain_openai

なぜlangchain_communitylangchain_openaiの両方をインストールするのか疑問に思うかもしれません。LangChainは最近、コードベースをモジュール式のパッケージに再構築しました。langchain_communityパッケージは、様々なLLM、ドキュメントローダー、ベクトルストアなど、幅広い一般的な統合を提供します。一方、OpenAIの機能を処理します。異なるLLMプロバイダーと作業する場合、例えばGoogle Geminiの場合はlangchain_google_genaiのように、専用のパッケージをインストールします。

最初のLLM呼び出し:AIの「Hello World」

次に、LLMにタスクを実行させましょう。OpenAI APIキーが必要です。常に安全に、理想的には環境変数として保存してください。迅速なテストには直接エクスポートで十分かもしれませんが、本格的なアプリケーションでは常に適切なシークレット管理システムを使用してください。


import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

# OpenAI APIキーが環境変数として設定されていることを確認してください
# os.environ["OPENAI_API_KEY"] = "あなたの実際のOpenAI APIキー"

# LLMを初期化します
llm = ChatOpenAI(model="gpt-3.5-turbo")

# シンプルなプロンプトテンプレートを定義します
prompt = ChatPromptTemplate.from_messages([
    ("system", "あなたは役立つAIアシスタントです。"),
    ("user", "{input}")
])

# シンプルなチェーンを作成します:prompt | llm

chain = prompt | llm

# 入力でチェーンを呼び出します
response = chain.invoke({"input": "オペレーティングシステムにおける「デッドロック」の概念を1文で説明してください。"})

print(response.content)

このPythonのコードスニペットは、OpenAIのチャットモデルを初期化し、簡単なプロンプトを定義し、そして「チェーン」(一連の操作)を作成して呼び出します。デッドロックの簡潔な説明を受け取れるはずです。わずか5分ほどで、最小限の労力で大規模言語モデルを活用できました。この迅速なプロトタイピング能力こそが、私がプレッシャーの下で作業する際に最も重視する点です。

詳細解説:LangChainの内部動作を理解する

差し迫った危機が回避されたら、LangChainがこのプロセスをどのように促進しているかを理解する時です。本番環境で不透明なシステムに依存することは、しばしば深夜のさらなるアラートにつながります。

LangChainは単なるラッパーではありません。強力なコンポーネントを洗練されたアプリケーションに組み合わせるための構造化されたアプローチを提供します。私のプロフェッショナルな経験では、これらの基本的な構成要素を真に理解することが不可欠なスキルです。これにより、例を実行するだけでなく、堅牢なAI駆動型ソリューションを実際に設計できるようになります。

コアコンポーネント:あなたの必須AIツールキット

LangChainは、いくつかの重要な抽象化を中心に構築されています。

言語モデル (LLM)

その核となるのは、LangChainがLLMを必要とすることです。OpenAIのGPT、GoogleのGemini、AnthropicのClaude、あるいはOllamaのようなツールを介したLlama 2のようなローカルのオープンソースモデルを含む、多様なモデルに対する統一されたインターフェースを提供します。この抽象化により、最小限のコード調整でモデルを簡単に交換できます。これは、コスト、パフォーマンス、または特定の機能のために最適化する場合に特に大きな利点です。


from langchain_openai import ChatOpenAI
from langchain_google_genai import ChatGoogleGenerativeAI

# OpenAIを使用
openai_llm = ChatOpenAI(model="gpt-4o")

# Google Geminiを使用 (GOOGLE_API_KEYが設定されていることを確認してください)
gemini_llm = ChatGoogleGenerativeAI(model="gemini-pro")

print(openai_llm.invoke("こんにちは").content)
print(gemini_llm.invoke("やあ").content)

プロンプトテンプレート:一貫したLLM入力の確保

生のテキストプロンプトは単発のタスクには機能しますが、アプリケーションは一貫性を要求します。プロンプトテンプレートを使用すると、繰り返し可能な構造を定義し、変数を動的に挿入できます。これは、プロンプトインジェクションの脆弱性を防ぎ、LLMが一貫して適切にフォーマットされた入力を確実に受け取るために不可欠です。


from langchain_core.prompts import ChatPromptTemplate

issue_template = ChatPromptTemplate.from_messages([
    ("system", "あなたは本番環境でのインシデント分析を担当するシニアDevOpsエンジニアです。"),
    ("user", "以下の本番環境インシデントレポートを分析してください:\nインシデントID: {incident_id}\nエラーログ: {error_logs}\nユーザーへの影響: {user_impact}\n
根本原因と初期の軽減策の要約を提供してください。")
])

formatted_prompt = issue_template.format_messages(
    incident_id="P-2026-03-22-001",
    error_logs="{'service_a': 'データベースへの接続が拒否されました', 'service_b': '500 内部サーバーエラー'}",
    user_impact="すべてのユーザーが機能にアクセスできません。"
)

# これでformatted_promptをLLMに渡すことができます
# print(formatted_prompt)

チェーン:LLM操作のオーケストレーション

LangChainの真の強みは、コンポーネントを連携させることで明らかになります。「チェーン」とは、単に一連の操作が定義されたものであり、あるステップの出力が次のステップの入力にシームレスに結合されます。LangChain Expression Language (LCEL) の一部である`|`演算子により、このプロセスは信じられないほど直感的で読みやすくなります。

これは、LLM操作のために特別に設計されたUnixパイプラインと考えることができます。プロンプトテンプレートをLLMと組み合わせたり、複数のLLM呼び出しを連結してより複雑な推論を実現したりできます。


from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

llm = ChatOpenAI(model="gpt-3.5-turbo")
output_parser = StrOutputParser()

analysis_prompt = ChatPromptTemplate.from_template(
    "以下のエラーログが与えられます:{log_message}\n最も可能性の高い原因は何ですか?簡潔に答えてください。"
)

# ログメッセージを受け取り、LLMにプロンプトを送り、出力を文字列として解析するチェーン
analysis_chain = analysis_prompt | llm | output_parser

log_message = "エラー403:ユーザーはリソース /admin/dashboard へのアクセスが許可されていません"
result = analysis_chain.invoke({"log_message": log_message})
print(f"可能性のある原因: {result}")

出力パーサー:アプリケーション向けにLLM応答を構造化する

LLMはテキストを生成しますが、アプリケーションはJSONやリストのような構造化されたデータを必要とすることがよくあります。出力パーサーは、生のLLMテキストを使用可能な形式に変換することで、このギャップを埋めます。この機能は、LLMの出力とプログラムで連携するために不可欠です。


from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field

# Pydanticを使用して希望する出力構造を定義します
class IncidentSummary(BaseModel):
    root_cause: str = Field(description="インシデントの簡潔な根本原因")
    severity: str = Field(description="重大度レベル (例: Critical, High, Medium, Low)")
    mitigation_steps: list[str] = Field(description="即時の軽減策のリスト")

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
parser = JsonOutputParser(pydantic_object=IncidentSummary)

prompt = ChatPromptTemplate.from_messages([
    ("system", "あなたは細心の注意を払うインシデントアナリストです。このスキーマに従ってJSON形式で応答してください: {format_instructions}"),
    ("user", "このインシデントを分析してください: データベースの接続プールが枯渇し、APIゲートウェイで503エラーが発生しています。")
]).partial(format_instructions=parser.get_format_instructions())

chain = prompt | llm | parser

incident_data = chain.invoke({})
print(incident_data)
print(f"根本原因: {incident_data['root_cause']}")

検索:外部知識源への接続

LLMは強力ですが、その知識はトレーニングデータに限定されています。リアルタイム、ドメイン固有、または独自の情報については、外部データを注入する方法が必要です。ここで検索が重要になります。通常、一連のステップが含まれます。

  1. PDF、ウェブページ、内部Wikiなどの様々なドキュメントを読み込む。
  2. これらのドキュメントをより小さく、管理しやすいチャンクに分割する。
  3. これらのチャンクをベクトル表現に埋め込む。
  4. これらの表現をベクトルデータベースに保存する
  5. ユーザーの入力に基づいて最も関連性の高いチャンクを取得するためにベクトルデータベースをクエリする。
  6. これらの取得されたチャンクをLLMにコンテキスト情報として渡す。

完全なRAG (Retrieval-Augmented Generation) アプリケーションの構築は専用の記事で扱われますが、検索を理解することは、その一般的な知識を超えて情報にアクセスする必要があるLLMアプリケーションにとって不可欠です。これにより、幻覚を抑制し、事実の正確性を確保できます。

高度な使用法:本番環境に対応したLLMシステムの設計

基本的な概念に慣れてしまえば、LangChainは洗練されたシステムを構築することを可能にします。ここでは、単純なスクリプトから、意思決定を行い、APIと対話し、状態を維持できるアプリケーションへと移行します。

エージェントとツール:LLMに自律性を与える

エージェントは真に革新的な機能を提供します。特定の目標を達成するためにどの「ツール」を使用するかをLLMが戦略的に決定できるようにします。

ツールは多岐にわたり、ウェブの検索、内部APIの呼び出し、データベースからのデータ取得、コードの実行などのアクションを含めることができます。エージェントは現在の状態を観察し、適切なアクションを選択し、ツールを使用して実行し、結果を観察し、目標が達成されるまでこのサイクルを繰り返します。このメカニズムはLLMを積極的な問題解決者に変えます。

以下は、検索ツールの使用を示す概念的な例です。


from langchain_openai import ChatOpenAI
from langchain import agents
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

# ツールを定義します
wikipedia = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())
tools = [wikipedia]

# ツールへのアクセス権を持つエージェントを初期化します
agent_executor = agents.create_react_agent(
    llm, tools, verbose=True
)

# エージェントはWikipediaを使って質問に答えられるようになりました
# response = agent_executor.invoke({"input": "Googleの現在のCEOは誰ですか?"})
# print(response["output"])

verbose=Trueの設定は、エージェントのデバッグにとって非常に重要です。どのツールを使用するか、その根拠を検討する際のLLMの内部思考プロセスを明らかにします。エージェントが期待通りに動作しない場合、その内部独白を観察することは、午前2時のインシデント中にシステムのログを理解するのと同じくらい重要な洞察を提供します。

メモリ:過去のやり取りを記憶する

会話型アプリケーションでは、LLMは以前のやり取りを「記憶」する必要があります。LangChainのメモリモジュールは、以前の対話を現在のプロンプトに注入することでこれを管理します。単純なバッファメモリから、より複雑な要約メモリまで、様々なタイプのメモリが存在し、それぞれ異なるユースケースと会話の複雑さに適しています。


from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

prompt = ChatPromptTemplate.from_messages([
    ("system", "あなたはフレンドリーなチャットボットです。"),
    MessagesPlaceholder(variable_name="history"),
    ("user", "{input}")
])

# メモリを初期化します
memory = ConversationBufferMemory(return_messages=True)

# 会話チェーンを作成します
conversation = ConversationChain(
    llm=llm,
    prompt=prompt,
    memory=memory,
    verbose=True
)

# 会話をシミュレートします
conversation.invoke({"input": "こんにちは!"})
conversation.invoke({"input": "私の名前はアレックスです。何ができますか?"})
response = conversation.invoke({"input": "私の名前を思い出させてくれますか?"})

print(response["response"])

コールバック:LLM操作に不可欠な可観測性

どんな複雑なソフトウェアと同様に、LLMアプリケーションにも堅牢な可観測性が必要です。LangChainのコールバックシステムは、チェーンやエージェントの実行の様々な段階にフックを注入することを可能にします。これは、ログ記録、デバッグ、トークン使用量の監視、遅延の分析に非常に貴重です。LLMアプリケーションが期待される動作から逸脱した場合、コールバックは不可欠なデバッグツールとなり、何がどのように発生したかを綿密に追跡するのに役立ちます。


from langchain.callbacks import StdOutCallbackHandler
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

handler = StdOutCallbackHandler()
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

prompt = ChatPromptTemplate.from_template("{{country}} の首都はどこですか?")
chain = prompt | llm

# コールバックハンドラーでチェーンを実行します
response = chain.invoke({"country": "フランス"}, config={"callbacks": [handler]})
print(response.content)

コールバックハンドラーが、消費されたトークンや応答時間など、LLM呼び出しに関する詳細情報を出力することに注目してください。このレベルの洞察は、本番環境でのパフォーマンス最適化や問題解決にとって絶対に不可欠です。

実践的なヒント:急速に進化するLLM開発の世界を航海する

LLMを使ったアプリケーション構築は、毎日新しいモデルや技術が登場するため、未踏の領域に足を踏み入れるような感覚になることがよくあります。ここでは、私が正気を保ち、効果的に仕事を進めるために採用してきたいくつかの戦略を紹介します。

デバッグ:あなたの不可欠な味方

LLMアプリケーションのデバッグは、その非決定的な性質のため、独特の課題を提示します。エージェントやチェーンでは常にverbose=Trueを有効にしてください。コールバックを活用し、中間ステップを diligent に出力してください。エージェントが道に迷った場合、その思考プロセスを調べて、どこで逸脱したかを正確に特定する必要があります。LLMの推論は、他の複雑なシステムと同様に扱います。各段階でその状態を検査してください。

効率的なコスト管理

トークンの使用量は急速に蓄積する可能性があります。特にメモリを組み込む場合は、プロンプトの長さに細心の注意を払ってください。会話履歴が過度に長くなった場合は要約してください。

より単純なタスクや初期のフィルタリングには、GPT-3.5-turboのようなより経済的なモデルを使用してください。GPT-4oのようなより高価で強力なモデルにエスカレートするのは、絶対に必要な場合のみにしてください。LangChainのキャッシュメカニズム(例えば、SQLiteキャッシュを使用)は、同一入力に対するLLMの繰り返しの呼び出しを大幅に削減し、時間とコストの両方を節約できます。


from langchain_openai import ChatOpenAI
from langchain.globals import set_llm_cache
from langchain.cache import SQLiteCache

set_llm_cache(SQLiteCache(database_path=".langchain.db"))

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

# 最初の呼び出し - LLMにヒットします
response1 = llm.invoke("OOPにおけるポリモーフィズムの定義は何ですか?")
print(f"最初の呼び出し: {response1.content[:50]}...")

# 同じ入力での2回目の呼び出し - キャッシュにヒットします
response2 = llm.invoke("OOPにおけるポリモーフィズムの定義は何ですか?")
print(f"2回目の呼び出し (キャッシュ済み): {response2.content[:50]}...")

常に情報を収集しつつ、すべての新奇を追い求めない

LangChainフレームワークは、より広範なLLMの状況と同様に、急速なペースで進化しています。ドキュメントとリリースノートに注意を払ってください。しかし、新機能が導入されるたびにアプリケーションを完全に再構築しようとする衝動には抵抗してください。新機能は、真に差し迫った問題を解決するか、パフォーマンスまたはコスト効率に大きな改善をもたらす場合にのみ、戦略的に統合してください。

厳密なテストが鍵

従来の単体テストでは不十分なことが多いため、LLMアプリケーションのテストは本質的に困難です。代わりに、チェーンとエージェントの全体的な動作を検証する堅牢な統合テストに焦点を当ててください。LangChain独自のLangSmithや様々なオープンソースの代替品のような評価フレームワークを利用して、ゴールデンデータセットに対してパフォーマンスを正確に測定してください。LLMが応答を生成するだけでなく、それらの応答が常に*正しく*信頼できるものであることを確認する必要があります。

LangChainは単なるライブラリを超え、LLMとのインタラクションを構造化するための包括的な方法論を表しています。

これは、しばしば混沌とした開発空間に秩序をもたらし、過剰な定型コードに巻き込まれることなく、強力でインテリジェントなアプリケーションを構築できるようにします。シンプルなチャットボットを構築している場合でも、複雑な自律エージェントを構築している場合でも、このフレームワークを理解し活用することで、開発サイクルを大幅に加速し、AIソリューションの信頼性を向上させることができます。

Share: