AIエージェントの賢さを支えるRAG戦略:計画と実行の仕組み
「AIエージェントが、なぜこんなに賢く、複雑なタスクをこなせるのか?」その疑問を抱いたことはありませんか?従来のRAG(Retrieval Augmented Generation)では「検索→生成」の固定パイプラインでしたが、これだけではAIエージェントの柔軟な意思決定や多段階の推論は実現できません。
本記事では、AIエージェントが複雑なタスクを効率的にこなすために、RAG(Retrieval Augmented Generation) がどのように機能し、計画(Planning)フェーズと実行(Acting)フェーズでそれぞれどのような情報を取得・活用しているのか、その内部動作と設計パターンを詳細に解説します。特に、LangChainとLangGraph を用いた具体的な実装例を交え、エージェントの精度と頑健性を高めるためのRAG活用術を深掘りします。この記事を読めば、あなたのAIエージェントはさらに賢く、実用的なものになるでしょう。
なぜAgentic RAGが重要なのか?従来のRAGの限界
このセクションでは、従来のRAGが持つ限界と、それを乗り越えるためにAgentic RAGがどのように進化してきたかを解説します。
従来のRAGは「ユーザーの質問に対して、事前に用意されたドキュメントから関連情報を検索し、その情報を基にLLMが回答を生成する」という強力なパラダイムを確立しました。これにより、LLMのハルシネーションを抑制し、最新情報や社内データに基づいた回答が可能になりました。しかし、そのプロセスは比較的シンプルで固定されています。
従来のRAGの限界:
- 固定されたパイプライン: 質問の種類にかかわらず、常に「検索→生成」という一連の流れで処理されます。例えば、簡単な質問でも必ず検索が走るため、非効率な場合があります。
- 単一ステップの推論: 複雑な質問や多段階の思考を要するタスク(例: 「AとBを比較し、Cの観点から最適なものを提案して」)には対応しにくい構造です。検索結果がそのまま回答の根拠となるため、深い分析や考察が苦手です。
- 検索戦略の柔軟性の欠如: ユーザーの質問やタスクのゴールに応じて、検索する情報源を変えたり、検索クエリを動的に修正したりする能力がありません。
これらの限界を打破し、AIエージェントに人間のような「考える力」を与えるのが、Agentic RAG のアプローチです。Agentic RAGでは、LLMが単なるテキスト生成器としてではなく、タスクの分解、検索戦略の決定、取得情報の評価、自己修正といった「推論」を中心とした役割を担います。これは、LangChainブログで「Agentic RAG: The Next Evolution of RAG」として紹介されています。
Agentic RAGの内部動作:計画と実行のメカニズム
このセクションでは、Agentic RAGがどのようにして賢い判断を下し、複雑なタスクをこなすのか、その内部メカニズムを「計画(Planning)」と「実行(Acting)」のフェーズに分けて詳しく見ていきます。
Agentic RAGの核となるのは、LLMが自律的に思考し、行動を決定する能力です。これは、Planning、Reasoning、Acting、Reflecting (PRAR) の4つの思考ステップに集約されます。
計画フェーズ (Planning & Reasoning) でのRAG活用
計画フェーズでは、AIエージェントが与えられたタスクを理解し、どのように解決すべきかの戦略を立てます。この段階でRAGが果たす役割は、タスクの適切な分解と、最適な行動計画の立案を支援することです。
-
タスク理解と分解:
- RAGの役割: エージェントはまず、ユーザーの質問やタスクを深く理解しようとします。この際、類似の過去のタスク解決履歴、ドメイン固有の知識、またはタスク分解に関するベストプラクティスが記載されたドキュメントをRAGで検索・参照することがあります。
- 例: 「新製品の市場調査レポートを作成して」というタスクに対して、エージェントは「市場調査レポートのテンプレート」「競合分析の手法」「データ収集源」といった情報を検索し、それらを基に「まず競合製品の情報を集める」「次に顧客アンケートを実施する」といった具体的なステップを計画します。
-
ツール選択と戦略立案:
- RAGの役割: エージェントは、タスクを解決するために利用可能なツール(Web検索、データベースクエリ、API呼び出しなど)の中から最適なものを選択する必要があります。この選択プロセスにおいて、各ツールの機能、利用条件、過去の成功事例などが記載されたドキュメントをRAGで検索し、参照します。
- 例: 「最新の株価情報を調べて」という質問に対し、エージェントは「株価検索APIの利用方法」というドキュメントを検索し、そのAPIを呼び出す計画を立てます。もし「LangChain Expression Language (LCEL) の最新情報」であれば、LangChainの公式ドキュメントを検索する計画を立てます。
-
LangChainでの実装: LangChainのエージェントは
toolsを与えられ、プロンプトを通じてどのツールを使うべきかを推論します。この「どのツールが何をするか」という情報も、RAGの延長線上でLLMに提供される知識と考えることができます。
このフェーズでは、RAGはエージェントが「何をすべきか」「どうすべきか」を判断するための「メタ知識」や「ノウハウ」を提供します。
実行フェーズ (Acting) でのRAG活用
実行フェーズでは、計画に基づいて具体的な行動を起こし、タスクを遂行します。この段階でのRAGは、行動の成功に必要な「具体的な情報」を提供します。
-
情報検索と取得:
- RAGの役割: エージェントがWeb検索ツールやベクトルストア検索ツールなどを利用して、タスクに必要な情報を取得する際、RAGは中心的な役割を果たします。エージェントは動的に検索クエリを生成し、関連性の高い情報を取得します。
- 例: 計画フェーズで「競合製品の情報を集める」と決めた場合、実行フェーズではWeb検索ツール(Tavilyなど)を使って具体的な競合製品名やスペック、価格などを検索します。この検索結果がRAGによってLLMに提示され、次のステップの入力となります。
-
LangChainでの実装:
retriever.invoke(query)のような形で、LLMが生成したクエリを使ってベクトルストアから情報を取得します。
-
情報の評価と反省 (Reflecting):
- RAGの役割: 取得した情報がタスクのゴールに対して適切か、信頼できるかを評価する際にもRAGが活用されます。エージェントは、評価基準やファクトチェックの手法に関するドキュメントを検索し、それらを基に取得情報の品質を判断します。不十分な場合は、さらに情報を検索したり、異なる戦略を試みたりする「自己修正(Self-Correction)」のプロセスに入ります。
- 例: Web検索で得られた情報が古かったり、信頼性に欠けると判断した場合、エージェントは「情報の信頼性評価ガイドライン」のようなドキュメントをRAGで参照し、別の情報源を当たるなどの行動を修正します。
このように、Agentic RAGは単に検索結果を生成に使うだけでなく、エージェントの思考プロセス全体にわたって情報検索と活用を組み込むことで、より高度な問題解決能力を実現しています。
LangChainとLangGraphによるAgentic RAGの実装
このセクションでは、LangChainとLangGraphを用いてAgentic RAGを具体的に実装する方法を解説します。特に、LangGraphによる状態管理と条件分岐が、エージェントの柔軟な動作をどのように支えるのかに焦点を当てます。
前提環境とライブラリのインストール
まず、必要なライブラリをインストールし、APIキーを設定します。
pip install -U langgraph langchain langchain-openai langchain-text-splitters beautifulsoup4 requests langchain_community
APIキーは環境変数として設定します。
import os
import getpass
def _set_env(key: str) -> None:
if key not in os.environ:
os.environ[key] = getpass.getpass(f"{key}:")
_set_env("OPENAI_API_KEY")
# LangSmithを使用する場合 (オプション: エージェントのデバッグ・可視化に必須)
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = getpass.getpass("LANGSMITH_API_KEY:")
os.environ["LANGCHAIN_PROJECT"] = "Agentic RAG Example" # プロジェクト名を指定
RAGの準備:ドキュメントの取得とインデックス化
Agentic RAGの基盤となるRAGシステムを構築します。ここではLangChainの公式ドキュメントを例に、URLからドキュメントをロードし、テキスト分割、埋め込み、ベクトルストアへの格納を行います。
from bs4 import BeautifulSoup as Soup
from langchain_community.document_loaders.recursive_url_loader import RecursiveUrlLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
# ドキュメントのロード
# LangChain v0.2.x のドキュメントURLに変更
url = "https://python.langchain.com/v0.2/docs/concepts/"
loader = RecursiveUrlLoader(
url=url, max_depth=2, extractor=lambda x: Soup(x, "html.parser").text
)
docs = loader.load()
# テキスト分割
# チャンクサイズとオーバーラップを調整することで、検索精度に大きく影響します。
# 適切なチャンキング戦略はRAGの品質を決定づける重要な要素です。
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
# ベクトルストアへの格納
# 本番環境では永続化可能なベクトルストア(Pinecone, Weaviateなど)を利用します。
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever()
LangGraphによるエージェントフローの構築
ここからがAgentic RAGの心臓部です。LangGraphを使って、エージェントの思考と行動のフローをグラフとして定義します。ユーザーの質問内容に応じて、検索を行うか、直接回答を生成するかをエージェント自身が判断するシンプルなAgentic RAGフローを構築します。
グラフの状態定義
LangGraphでは、グラフ全体の状態を TypedDict で定義します。この状態がノード間を伝播し、エージェントの記憶となります。
from typing import TypedDict, Annotated, List, Union
import operator
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
# グラフの状態定義
# 'messages' はエージェントの対話履歴を保持します。
# operator.add を使うことで、新しいメッセージがリストの末尾に追加されます。
class AgentState(TypedDict):
messages: Annotated[List[BaseMessage], operator.add]
# 必要に応じてRAGの検索結果やツールの実行結果などを追加することも可能です
# 例: retrieved_docs: List[Document]
LLMの初期化
エージェントの推論と生成を担当するLLMを初期化します。ここではOpenAIの gpt-4o を使用します。
llm = ChatOpenAI(model="gpt-4o", temperature=0)
ノードの定義
グラフの各ステップをノードとして定義します。ここでは「質問のルーティング」「情報検索」「回答生成」の3つのノードを作成します。
# ノードの定義
def route_question(state: AgentState) -> str:
"""
質問内容に基づいて、検索が必要か、直接回答できるかを判断するルーティングノード。
これは計画フェーズの一部であり、エージェントが最適な行動を決定する例です。
"""
print("---ROUTE QUESTION---")
last_message = state["messages"][-1].content
# LLMを使ってルーティングを判断させるプロンプト
# エージェントに「思考」を促すためのプロンプトエンジニアリングが重要です。
prompt = ChatPromptTemplate.from_messages([
("system", "あなたはユーザーの質問を分析し、情報検索が必要かどうかを判断するアシスタントです。"
"質問が特定の情報検索を必要とする場合は 'retrieve'、"
"そうでなく、一般的な知識で回答できる場合は 'generate' とだけ答えてください。"),
("human", "{question}")
])
chain = prompt | llm
response = chain.invoke({"question": last_message})
if "retrieve" in response.content.lower():
print("---DECISION: RETRIEVE---")
return "retrieve" # 検索ノードへ遷移
print("---DECISION: GENERATE---")
return "generate" # 生成ノードへ遷移
def retrieve(state: AgentState) -> dict:
"""
リトリーバーを使って情報を検索するノード(実行フェーズ)。
ここで実際にRAGによる情報取得が行われます。
"""
print("---RETRIEVE---")
question = state["messages"][-1].content
documents = retriever.invoke(question) # 準備したリトリーバーを実行
# 検索結果をToolMessageとして状態に追加
# LangGraphのメッセージ履歴にツール実行結果として含めるのが一般的です。
return {"messages": state["messages"] + [ToolMessage(content=str(documents), name="retriever_tool")]}
def generate(state: AgentState) -> dict:
"""
LLMで回答を生成するノード(実行フェーズ)。
検索結果があればそれをコンテキストとして利用し、より正確な回答を目指します。
"""
print("---GENERATE---")
messages = state["messages"]
# 検索結果がToolMessageとして存在するか確認し、コンテキストとして利用
context = ""
for msg in messages:
if isinstance(msg, ToolMessage) and msg.name == "retriever_tool":
context = msg.content # 検索結果をコンテキストとして利用
break
prompt_template = ChatPromptTemplate.from_messages([
("system", "あなたは役立つAIアシスタントです。以下の情報に基づいて質問に答えてください。"
"情報が提供されていない場合でも、一般的な知識で回答を試みてください。\n\n"
"--- 参照情報 ---\n{context}\n-----------------"),
("human", "{question}")
])
# 最後のHumanMessageがユーザーの質問
question = messages[-1].content if isinstance(messages[-1], HumanMessage) else ""
chain = prompt_template | llm
response = chain.invoke({"context": context, "question": question})
return {"messages": state["messages"] + [AIMessage(content=response.content)]}
グラフの構築とコンパイル
定義したノードと状態を使って、LangGraphでワークフローを構築します。条件分岐によって、エージェントの行動が動的に変化します。
# グラフの構築
workflow = StateGraph(AgentState)
# ノードの追加
workflow.add_node("retrieve", retrieve)
workflow.add_node("generate", generate)
# 開始ノードからルーティングノードへ
# route_question 関数の戻り値に基づいて、次のノードへ条件分岐します。
workflow.add_conditional_edge(
"__start__", # 開始ノード
route_question, # ルーティング関数
{"retrieve": "retrieve", "generate": "generate"} # 遷移先のマッピング
)
# 検索ノードから生成ノードへ
# 検索が完了したら、その結果を使って回答を生成します。
workflow.add_edge("retrieve", "generate")
# 生成ノードから終了へ
workflow.add_edge("generate", END)
# グラフのコンパイル
app = workflow.compile()
# グラフの実行例
print("\n--- 実行例1: 検索が必要な質問 ---")
inputs = {"messages": [HumanMessage(content="LangChain Expression Language (LCEL) について詳しく教えてください。")]}
for output in app.stream(inputs):
for key, value in output.items():
print(f"Output from node '{key}': {value}")
print(f"\n最終回答: {output['messages'][-1].content}")
print("\n--- 実行例2: 検索が不要な質問 ---")
inputs = {"messages": [HumanMessage(content="日本の首都はどこですか?")]}
for output in app.stream(inputs):
for key, value in output.items():
print(f"Output from node '{key}': {value}")
print(f"\n最終回答: {output['messages'][-1].content}")
この実装例では、route_question ノードがLLMの推論によって次に進むべきパス(retrieve または generate)を決定します。これがAgentic RAGの計画フェーズの一端を担っています。retrieve ノードはRAGを実行し、generate ノードは得られた情報(または一般的な知識)に基づいて回答を生成します。LangGraphはこのような複雑な状態遷移を直感的に記述することを可能にします。
Agentic RAGにおけるよくある課題と回避策
Agentic RAGは強力ですが、実装と運用には特有の課題が伴います。ここでは、よくあるエラーやハマりどころとその回避策を解説します。
1. ハルシネーション(Hallucination)の抑制
LLMが事実に基づかない情報を生成するハルシネーションは、RAGシステム全体にとって常に大きなリスクです。Agentic RAGでは、多段階の推論が介在するため、ハルシネーションのリスクが複雑化する可能性があります。
-
回避策:
- 自己検証機能 (Self-Correction/Self-Reflection): エージェントが生成した回答の信頼性を評価し、必要に応じて追加の検索や再生成を行うステップをLangGraphで組み込みます。例えば、生成された回答と参照ドキュメントの整合性をLLMに評価させるノードを追加できます。
- RAGASなどの評価フレームワークの活用: RAGASはRAGパイプラインの品質をリファレンスフリーで自動評価するデファクトスタンダードです。回答の正確性、文脈の関連性、誤情報の検出などを定量的に評価し、継続的に改善します。CI/CDに組み込み、モデル変更やチャンキング変更のたびに自動実行することで、品質を維持します。
- チャンキング戦略の最適化: LLMに与えるコンテキストの質を高めるため、ドキュメントの適切な粒度での分割が不可欠です。チャンクサイズ、オーバーラップ、メタデータ付与、セマンティックチャンキングなどを試行錯誤し、最適な戦略を見つけましょう。
- クエリの最適化: LLMで検索クエリをリライトしたり、複数のクエリを生成したりして、検索の精度を向上させることで、ハルシネーションの根源を断ちます。
2. 検索精度の不足
ユーザーの質問に対して関連性の低いドキュメントが取得されると、Agentic RAGの精度も低下します。
-
回避策:
- ハイブリッド検索: ベクトル検索だけでなく、キーワード検索(BM25など)も組み合わせることで、セマンティックな関連性とキーワードの一致度の両方を考慮し、検索漏れや関連性の低い結果を減らします。
- リランキング: 検索結果をさらにLLMなどで評価し、関連性の高い順に並べ替えることで、LLMに与えるコンテキストの質を向上させます。Cohere RerankやBGE Rerankerなどが利用可能です。
-
埋め込みモデルの選定: ドメインに特化した埋め込みモデルや、より高性能なモデル(例:
text-embedding-3-small)を使用することで、セマンティック検索の精度を高めます。 - ドキュメントのカバレッジ(網羅度)の改善: RAGは「既存のドキュメントから答えを見つける」技術であるため、そもそも情報源にない知識は生成できません。情報源の拡充を検討することは、最も根本的な解決策の一つです。
3. LangGraphの状態管理の複雑性
グラフが複雑になるにつれて、状態の定義やノード間のデータフローの管理が難しくなることがあります。特に、状態がシリアライズされることを考慮しないと、予期せぬエラーの原因となります。
-
回避策:
- シンプルなデータ型を使用: LangGraphの状態は内部でシリアライズ(JSON化)されることがあるため、カスタムオブジェクトではなく、参照ではなくIDで持つなど、シンプルなデータ型を使用するのが安全です。カスタムオブジェクトを状態に含める場合は、シリアライズ可能であることを確認するか、カスタムシリアライザを実装します。
- LangSmithの活用: LangSmithはLangChain/LangGraphアプリケーションのトレース、監視、評価を支援する強力なツールです。複雑なエージェントの内部動作を可視化し、各ノードの入力・出力、LLMの呼び出し、ツールの実行などを詳細に確認できるため、デバッグを大幅に容易にします。開発段階から積極的に導入しましょう。
-
モジュール化と抽象化: 各ノードの役割を明確にし、複雑なロジックは別の関数やクラスに切り出すことで、グラフ全体の可読性と保守性を高めます。状態の変更は
Annotated[List[BaseMessage], operator.add]のように、状態を破壊的に変更しないoperator.addを使うのがLangGraphのベストプラクティスです。
Agentic RAGの設計上のトレードオフとベストプラクティス
このセクションでは、Agentic RAGを設計・導入する上で考慮すべきトレードオフと、効果的なシステムを構築するためのベストプラクティスをまとめます。
設計上のトレードオフ
-
精度 vs レイテンシー vs コスト:
- Agentic RAGは従来のRAGよりも高精度な回答を期待できますが、複数の推論ステップやツール呼び出しを伴うため、レイテンシー(応答時間)が増加し、LLMの利用コストも高くなる傾向があります。
- 即効性や低コストを重視する場合は、チャンキング最適化やハイブリッド検索などのBasic/Advanced RAGから導入し、徐々にAgentic RAGへ移行することを検討しましょう。
-
汎用性 vs 特化性:
- 汎用的なAIエージェントは幅広いタスクに対応できますが、特定のドメインや業務に特化したエージェントの方が高い精度や効率を発揮しやすいです。
- 特定のユースケースに特化する場合、ドメイン知識を反映したプロンプト設計、専用のツール開発、ファインチューニングなどを検討することで、より高いパフォーマンスを得られます。
-
自律性 vs 制御:
- AIエージェントの自律性を高めるほど、予期せぬ動作やハルシネーションのリスクも増大する可能性があります。
- 重要な業務ではHuman-in-the-Loop(人間の確認を挟むフロー)を導入し、エージェントの判断を人間がレビュー・承認する仕組みを組み込むことが重要です。LangGraphはHuman-in-the-Loopの実装をサポートしています。
ベストプラクティス
- 段階的な導入: まずはBasic RAGから始め、チャンキング最適化、ハイブリッド検索、リランキングなどのAdvanced RAGの手法で精度を向上させます。その後、複雑なタスクや多段階推論が必要な場合にAgentic RAGを検討する、という段階的なアプローチが推奨されます。
- 評価パイプラインの構築: RAGASなどの評価フレームワークを導入し、回答の正確性、関連性、完全性、誤情報の検出などを定量的に計測・改善する仕組みを構築します。CI/CDに組み込み、モデル変更やチャンキング変更のたびに自動実行することで、品質を維持できます。
- データガバナンスの徹底: RAGの回答品質は参照データの品質に大きく依存するため、データの収集、前処理、更新、セキュリティ対策を適切に行うことが不可欠です。
- LangSmithの積極的な活用: 開発段階からLangSmithを導入し、エージェントのトレース、デバッグ、パフォーマンス監視を行うことで、複雑なエージェントの動作を理解し、問題の特定と解決を効率化できます。
- プロンプトエンジニアリングの最適化: LLMの推論能力を最大限に引き出すために、ReActパターンなどの思考と言語行動を分離したプロンプト設計や、出力フォーマットの構造化を意識することが重要です。
- ツールと外部データソースの活用: ベクトルデータベースだけでなく、Web検索(Tavilyなど)、APIサービス、社内データベースなど、多様な情報源に動的にアクセスできる仕組みを構築することで、エージェントの能力を拡張します。LangChainのToolsはこれらの統合を容易にします。
- モジュール化された設計: LangGraphでエージェントを構築する際は、各ノードの役割を明確にし、再利用可能なコンポーネントとして設計することで、保守性と拡張性を高めます。
まとめ
本記事では、AIエージェントの賢さを支えるAgentic RAG戦略について、その内部動作からLangChain/LangGraphを用いた実装、そして開発・運用上の課題とベストプラクティスまでを詳細に解説しました。
重要なポイントは以下の通りです。
- Agentic RAG は、LLMが推論を通じて動的に検索戦略を決定し、多段階の思考と行動を組み合わせることで、従来のRAGの限界を超え、より複雑なタスクに対応します。
- 計画フェーズ では、タスク理解、分解、ツール選択においてRAGがメタ知識を提供し、実行フェーズ では、具体的な情報検索と自己検証にRAGが活用されます。
- LangChainとLangGraph は、Agentic RAGを構築するための強力なフレームワークであり、特にLangGraphは複雑なエージェントのワークフロー、状態管理、条件分岐を直感的に記述することを可能にします。
- ハルシネーション抑制、検索精度向上、状態管理の複雑性といった課題に対しては、自己検証機能、RAGASによる評価、LangSmithを活用したデバッグ、そしてチャンキング戦略の最適化が有効な回避策となります。
AIエージェントの進化はまだ始まったばかりです。本記事で紹介したAgentic RAGの概念と実装パターンが、皆様のより賢く、より実用的なAIエージェント開発の一助となれば幸いです。さらに深く学びたい方は、LangChainおよびLangGraphの公式ドキュメントを参照し、Agentic RAGのさらなる応用例や発展トピックを探求してみてください。