🧠

LangChainとLangGraphで始めるRAG・AIエージェント開発入門

に公開

はじめに

近年、大規模言語モデル(LLM)の進化により、AI技術の活用が急速に広がっています。以前、Difyを使ってワークフローを組んで、なんとなくAIエージェントのようなものを作った経験はあるのですが、根底にある基礎知識をちゃんと理解していないと回答の精度が出せなさそうだという感覚はありました。そこで今回は、「LangChainとLangGraphによるRAG・AIエージェント実践入門」の読書を通して、LLMを活用したシステム開発の基礎知識から実践的な手法を学習して、体系的に解説します。

本記事で学べること

  • プロンプトエンジニアリングの基礎と実践
  • LangChainを使ったLLMアプリケーション開発
  • LangGraphによる複雑なワークフロー構築
  • AIエージェントの設計パターン

プロンプトエンジニアリング:LLMとの効果的な対話

プロンプトエンジニアリングとは

プロンプトエンジニアリングは、言語モデル(LM)を効率的に使用するためのプロンプトを開発し、最適化する比較的新しい分野です。適切なプロンプト設計により、LLMから期待する回答を得やすくなります。

プロンプトの構成要素

効果的なプロンプトは以下の要素から構成されます:

  1. 命令(Instruction): LLMに実行してほしいタスク
  2. 入力データ(Input Data): ユーザーの入力データ
  3. 文脈(Context): 前提条件や外部情報
  4. 出力形式の指定(Output Format): JSONやMarkdownなどの形式指定

プロンプトエンジニアリングの手法

1. Zero-shotプロンプティング

回答例を与えずに指示するシンプルな手法です。

from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate

llm = OpenAI(temperature=0)

# Zero-shotプロンプトの例
zero_shot_prompt = PromptTemplate(
    input_variables=["product"],
    template="""
以下の商品について、マーケティング用のキャッチコピーを作成してください。

商品: {product}

キャッチコピー:
"""
)

# 実行例
prompt = zero_shot_prompt.format(product="ワイヤレスイヤホン")
response = llm(prompt)
print(response)

2. Few-shotプロンプティング

回答例をいくつか示してから指示する手法です。

few_shot_prompt = PromptTemplate(
    input_variables=["product"],
    template="""
以下の例を参考に、商品のキャッチコピーを作成してください。

例1:
商品: スマートフォン
キャッチコピー: あなたの生活を変える、次世代スマートフォン

例2:
商品: コーヒー豆
キャッチコピー: 毎朝の一杯から始まる、特別な一日

商品: {product}
キャッチコピー:
"""
)

3. Zero-shot Chain of Thoughtプロンプティング

「ステップバイステップで考えてください」と指示することで、段階的な思考プロセスを促す手法です。回答例を与えずに、LLMに論理的な推論過程を明示させることで、より正確な応答を得やすくなります。

zero_shot_cot_prompt = PromptTemplate(
    input_variables=["problem"],
    template="""
以下の問題について、ステップバイステップで考えて解答してください。

問題: {problem}

ステップバイステップで考えてみましょう:
"""
)

LangChain:LLMアプリケーション開発フレームワーク

LangChainとは

LangChainは、LLMアプリケーション開発を簡素化するPythonフレームワークです。入力からプロンプト整形、LLM実行、回答のパースまでの一連の処理を効率的に実装できます。

LangChainの主要機能

  • 各種LLMとの通信の抽象化
  • 容易なプロンプト操作
  • LLMの回答のパース
  • LCEL(LangChain Expression Language)による処理の連鎖
  • RAGの簡単な構築

LangChainの基本的な使用例

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

# LLMの初期化
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)

# プロンプトテンプレートの作成
prompt = ChatPromptTemplate.from_template(
    "以下のトピックについて簡潔に説明してください:{topic}"
)

# 出力パーサーの作成
output_parser = StrOutputParser()

# LCEL構文でチェーンを作成
chain = prompt | llm | output_parser

# 実行
result = chain.invoke({"topic": "量子コンピューティング"})
print(result)

LangGraph:複雑なワークフローの構築

LangGraphとは

LangGraphは、LLMを活用した複雑なワークフローを開発するためのPythonライブラリです。LangChainの処理を一つのノードとして、複数のノードを連携させることで、高度なAIエージェントの開発が可能になります。

LangGraphの基本概念

  • ノード(Node): 個々の処理単位
  • エッジ(Edge): ノード間の接続
  • ステート(State): ワークフロー全体で共有されるデータ
  • グラフ(Graph): ノードとエッジで構成されるワークフロー

LangGraphの実装例

1. シンプルなエージェントワークフロー

from typing import TypedDict, Annotated
import operator
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI

# LLMの初期化
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

# ステートの定義
class AgentState(TypedDict):
    messages: Annotated[list, operator.add]
    current_step: str
    result: str

# ノード関数の定義
def analyze_request(state: AgentState) -> dict:
    """ユーザーリクエストを分析"""
    messages = state["messages"]
    last_message = messages[-1] if messages else "初回実行"
    
    # 分析ロジック
    analysis_prompt = f"以下のリクエストを分析してください:{last_message}"
    analysis = llm.invoke(analysis_prompt).content
    
    return {
        "messages": [f"分析結果: {analysis}"],
        "current_step": "planning",
        "result": ""
    }

def create_plan(state: AgentState) -> dict:
    """実行計画を作成"""
    messages = state["messages"]
    
    plan_prompt = "分析結果を基に実行計画を作成してください"
    plan = llm.invoke(plan_prompt).content
    
    return {
        "messages": [f"実行計画: {plan}"],
        "current_step": "execution",
        "result": ""
    }

def execute_plan(state: AgentState) -> dict:
    """計画を実行"""
    messages = state["messages"]
    
    execute_prompt = "計画を実行し、結果を生成してください"
    result = llm.invoke(execute_prompt).content
    
    return {
        "messages": [f"実行結果: {result}"],
        "current_step": "completed",
        "result": result
    }

# グラフの構築
workflow = StateGraph(AgentState)

# ノードの追加
workflow.add_node("analyze", analyze_request)
workflow.add_node("plan", create_plan)
workflow.add_node("execute", execute_plan)

# エッジの追加
workflow.add_edge(START, "analyze")
workflow.add_edge("analyze", "plan")
workflow.add_edge("plan", "execute")
workflow.add_edge("execute", END)

# グラフのコンパイル
app = workflow.compile()

# 実行
result = app.invoke({
    "messages": ["新しいプロダクトの企画を立ててください"],
    "current_step": "start",
    "result": ""
})

print("最終結果:", result)

AIエージェントの設計パターン

AIエージェントとは

AIエージェントは、複雑な目標を自律的に遂行できるAIシステムです。与えられた目標を達成するために必要な行動を自ら決定し、実行することができます。

エージェントデザインパターンの分類

目標設定と計画生成

  • パッシブゴールクリエイター: ユーザーの入力や要求から具体的な目標を抽出し、それらの目標を達成するための計画を生成する
  • プロアクティブゴールクリエイター: 環境や状況から能動的に目標を生成するパターン
  • マルチパスプランジェネレーター: 同じ目標に対して複数の計画案を生成し、最適なものを選択する
  • シングルパスプランジェネレーター: 単一の計画案を一度で生成する
  • ワンショットモデルクエリ: 一回のモデル呼び出しで完結する処理
  • インクリメンタルモデルクエリ: 段階的にモデルを呼び出して結果を積み上げる処理

推論の確実性向上

  • 検索拡張生成(RAG): 外部知識ベースから情報を検索し、回答の精度を向上させる
  • セルフリフレクション: AIエージェントが自身の回答を客観的に評価し、改善する
  • クロスリフレクション: 複数のエージェントが互いの回答を評価し合う
  • ヒューマンリフレクション: 人間のフィードバックを取り入れて回答を改善する
  • エージェント評価器: エージェントの性能や品質を評価する専用コンポーネント

エージェント間の協調

  • 投票ベースの協調: 複数のエージェントが提案を出し、投票で最終決定を行う
  • 役割ベースの協調: 各エージェントが特定の役割を担い、協力してタスクを実行する
  • 議論ベースの協調: エージェント間で議論を交わし、合意形成を図る

入出力制御

  • マルチモーダルガードレール: 様々な入力形式に対する安全性制御
  • ツール/エージェントレジストリ: 利用可能なツールやエージェントを管理する仕組み
  • エージェントアダプター: 異なるシステム間でエージェントを連携させるインターフェース

デザインパターン実装例

プロアクティブゴールクリエイター

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

class ProactiveGoalCreator:
    def __init__(self, llm):
        self.llm = llm
        self.parser = StrOutputParser()
        self.prompt = ChatPromptTemplate.from_template("""
以下の文脈から、達成すべき目標を積極的に提案してください:

文脈: {context}

提案する目標(3つ):
1. 
2. 
3. 
""")
    
    def create_goals(self, context: str) -> list:
        chain = self.prompt | self.llm | self.parser
        response = chain.invoke({"context": context})
        goals = self.parse_goals(response)
        return goals
    
    def parse_goals(self, response: str) -> list:
        # レスポンスから目標をパース
        goals = []
        lines = response.split('\n')
        for line in lines:
            line = line.strip()
            if line and any(line.startswith(f"{i}.") for i in range(1, 4)):
                goals.append(line)
        return goals

# 使用例
llm = ChatOpenAI(model="gpt-3.5-turbo")
goal_creator = ProactiveGoalCreator(llm)
goals = goal_creator.create_goals("新しいECサイトを立ち上げたい")
print("提案された目標:", goals)

マルチパスプランジェネレーター

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

class MultiPassPlanGenerator:
    def __init__(self, llm):
        self.llm = llm
        self.parser = StrOutputParser()
        
        self.plan_prompt = ChatPromptTemplate.from_template("""
目標: {goal}

これまでの計画案: {previous_plans}

より良い実行計画を提案してください(第{iteration}回目):
""")
        
        self.selection_prompt = ChatPromptTemplate.from_template("""
目標: {goal}

以下の計画案から最も効果的なものを選択してください:
{all_plans}

選択理由とともに最適な計画を回答してください。
""")
    
    def generate_plan(self, goal: str, iterations: int = 3) -> dict:
        plans = []
        
        for i in range(iterations):
            plan_chain = self.plan_prompt | self.llm | self.parser
            plan = plan_chain.invoke({
                "goal": goal,
                "previous_plans": "\n".join([f"計画{j+1}: {p}" for j, p in enumerate(plans)]),
                "iteration": i + 1
            })
            plans.append(plan)
        
        # 最適な計画を選択
        selection_chain = self.selection_prompt | self.llm | self.parser
        final_plan = selection_chain.invoke({
            "goal": goal,
            "all_plans": "\n".join([f"計画{i+1}: {plan}" for i, plan in enumerate(plans)])
        })
        
        return {
            "final_plan": final_plan,
            "all_plans": plans,
            "iterations": iterations
        }

# 使用例
plan_generator = MultiPassPlanGenerator(llm)
result = plan_generator.generate_plan("AIチャットボットの開発")
print("最終計画:", result["final_plan"])

セルフリフレクション

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

class SelfReflectionAgent:
    def __init__(self, llm):
        self.llm = llm
        self.parser = StrOutputParser()
        
        self.reflection_prompt = ChatPromptTemplate.from_template("""
質問: {question}
初回回答: {initial_response}

この回答を客観的に評価し、改善点を特定してください:
1. 回答の正確性
2. 完全性
3. 明確性

改善点:
""")
        
        self.improvement_prompt = ChatPromptTemplate.from_template("""
質問: {question}
初回回答: {initial_response}
改善点: {reflection}

改善点を反映した最終回答を生成してください:
""")
    
    def reflect_and_improve(self, initial_response: str, question: str) -> str:
        # リフレクション実行
        reflection_chain = self.reflection_prompt | self.llm | self.parser
        reflection = reflection_chain.invoke({
            "question": question,
            "initial_response": initial_response
        })
        
        # 改善実行
        improvement_chain = self.improvement_prompt | self.llm | self.parser
        improved_response = improvement_chain.invoke({
            "question": question,
            "initial_response": initial_response,
            "reflection": reflection
        })
        
        return improved_response

# 使用例
reflection_agent = SelfReflectionAgent(llm)
question = "量子コンピューティングの実用性について説明してください"
initial_answer = "量子コンピューティングは速いです。"
improved_answer = reflection_agent.reflect_and_improve(initial_answer, question)
print("改善された回答:", improved_answer)

役割ベースの協調

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

class RoleBasedCollaboration:
    def __init__(self, llm):
        self.llm = llm
        self.parser = StrOutputParser()
        self.agents = {
            "researcher": self.create_researcher(),
            "analyst": self.create_analyst(),
            "writer": self.create_writer()
        }
    
    def create_researcher(self):
        prompt = ChatPromptTemplate.from_template(
            "研究者として以下について詳細に調査してください: {query}"
        )
        return prompt | self.llm | self.parser
    
    def create_analyst(self):
        prompt = ChatPromptTemplate.from_template(
            "アナリストとして以下のデータを分析し、重要なポイントを抽出してください: {data}"
        )
        return prompt | self.llm | self.parser
    
    def create_writer(self):
        prompt = ChatPromptTemplate.from_template(
            "ライターとして以下の分析結果を読みやすい記事にまとめてください: {analysis}"
        )
        return prompt | self.llm | self.parser
    
    def collaborate(self, initial_query: str) -> str:
        # 研究フェーズ
        research_result = self.agents["researcher"].invoke({"query": initial_query})
        print("研究結果:", research_result[:100] + "...")
        
        # 分析フェーズ
        analysis_result = self.agents["analyst"].invoke({"data": research_result})
        print("分析結果:", analysis_result[:100] + "...")
        
        # 執筆フェーズ
        final_result = self.agents["writer"].invoke({"analysis": analysis_result})
        
        return final_result

# 使用例
collaboration = RoleBasedCollaboration(llm)
result = collaboration.collaborate("人工知能の最新動向")
print("最終結果:", result)

まとめ

「LangChainとLangGraphによるRAG・AIエージェント実践入門」を読んで、LLMとAIエージェントの基礎知識を吸収することができました。
RAGの精度改善などの部分についてちゃんと理解できていないので、これからも勉強していきたいと思います。
個人的にはPythonより慣れているTypeScriptベースで開発していきたいので、実際に利用するのはLangGraphじゃなくてMastraになると思います。
AIエージェント開発は急速に進化している分野です。基礎をしっかりと身につけ、実践を通して経験を積んでいきたいと思います。

Discussion