「RAGを導入したのに、なんだか精度がイマイチ…」「LLMアプリの品質をどうやって担保すればいいのかわからない」
多くの開発者が直面するこの課題は、従来のRAGが持つ限界と、LLM評価の難しさに起因します。特に複雑なクエリや複数の情報源を扱う場合、単一パスの検索では満足のいく結果が得られにくいものです。
この記事では、従来のRAGの課題を解決し、高精度なLLMアプリを構築するための「Agentic RAG」というアプローチを、LangGraph、LlamaIndex、CrewAIといった主要フレームワークの比較と具体的な実装例を交えて解説します。さらに、品質を継続的に向上させるための「評価駆動開発(Eval Driven Development - EDD)」の重要性と実践方法についても深掘りします。
この記事を読むことで、あなたは従来のRAGの限界を突破し、プロダクションで通用する信頼性の高いLLMアプリケーションをゼロから構築するための具体的な知識とノウハウを得られるでしょう。
なぜ今、Agentic RAGと評価駆動開発が必要なのか?従来のRAGの課題
従来のRAG (Retrieval-Augmented Generation) は、LLMが外部知識を利用してハルシネーション(幻覚)を減らし、より正確な回答を生成するための強力な手法です。しかし、そのシンプルさゆえに、以下のような課題を抱えています。
- 単一パスの検索: 多くのRAGシステムは、一度の検索で取得した情報を基に回答を生成します。複雑なクエリや多段階の推論が必要な場合、この単一パスでは十分な情報が得られず、精度が低下します。
- コンテキストの限界: 取得された情報がLLMのコンテキストウィンドウに収まらない場合、重要な情報が欠落する可能性があります。
- 固定された検索戦略: 事前に定義された検索戦略(例: ベクトル検索)しか利用できないため、クエリの内容に応じて最適な検索方法を動的に選択できません。
- 評価の難しさ: LLMの生成結果の品質を客観的に評価する標準的な方法が確立されておらず、改善サイクルを回しにくいという問題があります。
これらの課題を解決し、より堅牢で高精度なLLMアプリケーションを構築するために、「Agentic RAG」と「評価駆動開発(EDD)」という2つのアプローチが注目されています。
Agentic RAGとは?精度向上へのアプローチ
Agentic RAGは、従来のRAGに自律的なAIエージェントの推論能力を組み込むことで、これらの課題を克服する新しいパラダイムです。エージェントは、検索戦略の計画、複数回の検索実行、中間結果の検証、最終回答生成前の反復といった一連のプロセスを自律的に行います。
具体的には、Agentic RAGは以下の要素を通じて精度を向上させます。
- ツール利用: エージェントは、Web検索、データベースクエリ、API呼び出しなど、様々なツールを必要に応じて動的に選択し、利用できます。
- メモリ: 過去の対話や検索結果を記憶し、それらを次の行動計画に反映させることができます。
- Reason-Act-Observe (ReAct) ループ: 思考(Reason)、行動(Act)、観察(Observe)を繰り返すことで、自己修正能力を持ち、複雑な問題解決を可能にします。
RAGをエージェント内の呼び出し可能なツールとして扱うことで、LLMが「いつ、何を、どのように検索するか」を制御できるようになり、従来のRAGでは難しかった多段階の推論やクロスソースの統合が可能になります。
LLM評価駆動開発(EDD)の重要性
どんなに優れたアーキテクチャを採用しても、その品質を客観的に測定し、改善するプロセスがなければ、プロダクションレベルのアプリケーションは構築できません。そこで重要になるのが「LLM評価駆動開発 (Eval Driven Development - EDD)」です。
EDDは、評価(evals)を使用してLLMアプリケーションやAIエージェントを反復的に改善するプロセスであり、以下の3つのステップで構成されます。
- ゴールデンデータセットの準備: 期待される出力を含む適切な数のテストケース(約100個が目安)を準備します。
- メトリクスの定義: AIエージェントのパフォーマンスと相関性の高い3〜5個のメトリクスを定義します(例: 忠実性、回答の関連性、コンテキストの精度、コンテキストの再現率など)。
- 反復と改善: すべてのメトリクスがテストケースをパスするまで、プロンプト、モデル、ツール、RAGパイプラインなどを反復的に改善します。
OpenAI Evalsプラットフォームは非推奨化されていますが、RAGAS、TruLens、DeepEval、LangSmithなどの評価フレームワークが代替として広く利用されています。EDDを導入することで、LLMアプリケーションの品質を客観的に測定し、継続的に向上させることが可能になります。
Agentic RAGを実装する主要フレームワーク
Agentic RAGの実装には、エージェントのオーケストレーションやRAGパイプラインの構築を支援する様々なフレームワークが利用されます。ここでは、主要な3つのフレームワークであるLlamaIndex、LangGraph、CrewAIについて解説します。
LlamaIndex: RAGに特化したデータフレームワーク
LlamaIndexは、RAGを強化するデータフレームワークであり、データ取り込み、チャンク化、インデックス作成、クエリのための高レベルAPIを提供します。Agentic RAGにおいては、エージェントが利用する「知識源」としてのRAGパイプラインを構築する基盤となります。
LlamaIndexの基本的なRAGワークフロー
LlamaIndexを使ったRAGの基本的な流れは以下の通りです。
- 環境設定: APIキーや使用するLLM、埋め込みモデルを設定します。
-
ドキュメント読み込み:
SimpleDirectoryReaderなどを使って、ローカルファイルやクラウドストレージからドキュメントを読み込みます。 -
インデックス作成: 読み込んだドキュメントから
VectorStoreIndexなどのインデックスを作成します。この際、ドキュメントは指定されたchunk_sizeとchunk_overlapに基づいてチャンクに分割され、埋め込みが生成されます。 - クエリ実行: 作成したインデックスに対してクエリを実行し、関連する情報を取得してLLMに渡します。
以下に、LlamaIndexの基本的なRAGワークフローのコード例を示します。
# LlamaIndexのインストール
# pip install llama-index-core llama-index-embeddings-openai llama-index-llms-openai python-dotenv
# または Google Gemini を使用する場合:
# pip install llama-index-core llama-index-embeddings-google-genai llama-index-llms-google-genai python-dotenv
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, Settings
from llama_index.llms.openai import OpenAI # または llama_index.llms.google import Gemini など
from llama_index.embeddings.openai import OpenAIEmbedding # または llama_index.embeddings.google import GeminiEmbedding など
import os
# 環境変数の設定 (例: OpenAI APIキー)
# .env ファイルに OPENAI_API_KEY="YOUR_OPENAI_API_KEY" を記述し、以下で読み込む
from dotenv import load_dotenv
load_dotenv()
# グローバル設定
# LLMとEmbeddingモデルは明示的に設定することを推奨します。
# デフォルトのチャンクサイズは1024、チャンクオーバーラップは20ですが、
# アプリケーションに合わせて調整することが重要です。
Settings.llm = OpenAI(model="gpt-3.5-turbo", temperature=0.1)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-ada-002") # または "text-embedding-3-small" など
Settings.chunk_size = 512
Settings.chunk_overlap = 20
# ドキュメントの読み込み
# 例: './data/documents.txt' にテキストファイルがあるとする
# ディレクトリが存在しない場合は作成し、ダミーファイルを作成
if not os.path.exists('./data'):
os.makedirs('./data')
with open('./data/documents.txt', 'w') as f:
f.write("LlamaIndexは、LLMアプリケーションを構築するためのデータフレームワークです。RAGパイプラインの構築を容易にします。")
f.write("Agentic RAGは、従来のRAGにエージェントの推論能力を組み合わせることで、より複雑なクエリに対応できます。")
reader = SimpleDirectoryReader(input_files=["./data/documents.txt"])
documents = reader.load_data()
# インデックスの作成
index = VectorStoreIndex.from_documents(documents)
# クエリエンジンの作成
query_engine = index.as_query_engine()
# クエリの実行
response = query_engine.query("LlamaIndexは何をするためのフレームワークですか?")
print(f"応答: {response}")
# 取得されたソースノードの確認(デバッグに役立つ)
print("ソースノード:")
for node in response.source_nodes:
print(f"- テキスト: {node.text[:100]}...") # 最初の100文字を表示
print(f" メタデータ: {node.metadata}")
LlamaIndexのプロダクションでのベストプラクティス
- チャンクサイズとオーバーラップの最適化: 特定のデータタイプやクエリの種類に合わせて、チャンクサイズとオーバーラップをテストし、最適化することが重要です。これにより、関連情報の取得精度が大きく変わります。
- 埋め込みモデルの一貫性: ドキュメント取り込み時とクエリ実行時で同じ埋め込みモデルを使用するようにします。異なるモデルを使用すると、検索結果の関連性が低下する可能性があります。
- ハイブリッド検索の検討: セマンティック検索(埋め込みベース)とキーワード検索(BM25など)を組み合わせたハイブリッド検索は、単一の検索方法よりも堅牢な結果をもたらすことが多いです。
- プロンプトのカスタマイズ: LLMの出力品質に問題がある場合、プロンプトの調整が最も効果的な改善策となることがよくあります。
- メタデータフィルタリングの活用: メタデータをノードにアタッチし、クエリ時にフィルタリングを行うことで、検索範囲を絞り込み、精度を向上させることができます。
LangGraph: エージェントワークフローを構築する強力なグラフフレームワーク
LangGraphは、LangChainを基盤とし、状態、条件分岐、ループ、リトライ、共有状態、人間による承認ポイントが必要な複雑なエージェントシステムに適したフレームワークです。Agentic RAGにおけるエージェントの「思考プロセス」や「行動のオーケストレーション」を柔軟に定義できます。
LangGraphの主要な概念
-
状態 (State): エージェントの現在の状況を保持するデータ構造です。
TypedDictなどで定義し、各ノードで更新されます。 - ノード (Node): 特定の処理(LLM呼び出し、ツール実行、データ処理など)を実行する関数やロジックの単位です。
- エッジ (Edge): ノード間の遷移を定義します。条件付きエッジを使うことで、状態に基づいて次のノードを動的に選択できます。
- グラフ (Graph): ノードとエッジを組み合わせて、エージェントのワークフロー全体を表現します。
LangGraphでのエラーハンドリングの例
LangGraphでは、ノードレベルでのエラー管理が可能です。特にToolNodeは、ツール実行時のエラーを自動的にキャプチャし、LLMに報告する機能を持っています。これにより、LLMがエラーメッセージを解釈し、自己修正を試みるようなプロンプト設計が可能になります。
以下は、ツール呼び出し時のエラーハンドリングを示すLangGraphの例です。
# LangGraphのインストール
# pip install langgraph langchain_openai python-dotenv
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from typing import TypedDict, Annotated, List
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
import operator
from dotenv import load_dotenv
load_dotenv()
# 環境変数の設定 (例: OpenAI APIキー)
# os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"
# グラフの状態を定義
# messages: 会話履歴を累積
# error: 最新のエラーメッセージを格納(デモンストレーション用)
class AgentState(TypedDict):
messages: Annotated[List[BaseMessage], operator.add]
error: Annotated[str, operator.setitem] # 最新のエラーメッセージを上書き
@tool
def get_weather(location: str):
"""指定された場所の現在の天気を取得するツール。
有効な場所は「San Francisco」のみです。
"""
if location.lower() != "san francisco": # 小文字の "san francisco" でもエラーにする
raise ValueError("無効な入力形式です。'San Francisco'を使用してください。")
elif location == "San Francisco":
return "気温は60度で霧がかかっています。"
else:
# このパスは通常到達しないが、網羅のために残す
raise ValueError(f"サポートされていない場所です: {location}")
# LLMとツールノードの初期化
tools = [get_weather]
model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
tool_node = ToolNode(tools)
# LLMを呼び出すノード
def call_model(state: AgentState):
messages = state["messages"]
response = model.invoke(messages)
return {"messages": [response]}
# ツール呼び出しの有無を判断する条件付きエッジ
def should_continue(state: AgentState):
last_message = state["messages"][-1]
if last_message.tool_calls:
return "tools"
return "end"
# エラーハンドリングを含むツールノードのラッパー
# LangGraphのToolNodeはデフォルトでエラーをキャプチャし、LLMに報告する(ToolMessageとして返す)
# ここでは、その動作をより明示的に示すためにラッパー関数を定義しています。
# 実際にはToolNodeを直接使用し、LLMがエラーを解釈するプロンプト設計が一般的です。
def tool_executor_with_error_handling(state: AgentState):
try:
tool_output_messages = tool_node.invoke(state)["messages"]
# ToolNodeはエラー時にもToolMessageを返すため、
# ここではエラーメッセージを別途状態に格納するロジックは省略し、
# 通常のToolNodeの出力をそのまま返します。
return {"messages": tool_output_messages}
except Exception as e:
# ここに到達することは稀ですが、予期せぬエラーの場合のフォールバック
error_message = f"ツール実行中に予期せぬエラーが発生しました: {e}"
print(f"エラー捕捉: {error_message}")
return {"messages": [ToolMessage(content=error_message, tool_call_id="error_tool_call")], "error": error_message}
# グラフの定義
workflow = StateGraph(AgentState)
workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_executor_with_error_handling) # エラーハンドリングを組み込んだノードを使用
workflow.set_entry_point("agent")
workflow.add_conditional_edges(
"agent",
should_continue,
{"tools": "tools", "end": END}
)
workflow.add_edge("tools", "agent") # ツール実行後にエージェントに戻る
app = workflow.compile()
# エラーを発生させるクエリの例
print("\n--- エラーを発生させるクエリの実行 ---")
# 小文字の "san francisco" はツールでエラーになるよう設定
final_state = app.invoke({"messages": [HumanMessage(content="san franciscoの天気は?")]})
print(f"最終状態: {final_state}")
# LangGraphのToolNodeはエラーをToolMessageとして返し、LLMがそれを処理しようとするため、
# グラフ自体はENDに到達せずにループする可能性があります。
# 上記の例では、LLMがToolMessageを見て再度ツールを呼び出そうとする可能性があります。
# 実際のデバッグでは、`app.stream()` を使用してステップごとの出力を確認するのが効果的です。
# 正しいクエリの例
print("\n--- 正しいクエリの実行 ---")
final_state_correct = app.invoke({"messages": [HumanMessage(content="San Franciscoの天気は?")]})
print(f"最終状態 (正しいクエリ): {final_state_correct}")
LangGraphのベストプラクティス:
-
最小限で型付けされた状態: 状態は
TypedDictなどで明確に型付けし、必要最小限の情報を保持するようにします。 -
Reducerの活用: リストなどの累積される値を扱う場合は、
Annotated[List[...], operator.add]のようにReducerを適切に定義し、状態が意図せず上書きされるのを防ぎます。 - テストの実施: 関数だけでなく、グラフ全体をテストすることで、複雑なエージェントの挙動を検証します。
-
プロダクションチェックポインター:
Postgresなどのプロダクションチェックポインターを使用し、エージェントの状態を永続化することで、中断されたエージェントの実行を再開できるようにします。
CrewAI: 協調的なAIエージェントチームを構築
CrewAIは、プロダクション対応の協調的なAIエージェントチームを構築するためのオープンソースのPythonフレームワークです。Agentic RAGにおいて、複数のエージェントが連携して複雑なタスクを解決するシナリオで特に強みを発揮します。
CrewAIの主要コンポーネント
- エージェント (Agent): 特定の役割、ゴール、バックストーリーを持ち、ツールを使用できる自律的なエンティティです。
- タスク (Task): エージェントに割り当てられる具体的な目標と期待される出力を定義します。
- ツール (Tool): エージェントがタスクを実行するために利用する機能(Web検索、ファイル読み書きなど)です。
-
クルー (Crew): 複数のエージェントとタスクを組織し、協調して目標を達成するための全体的なオーケストレーションを管理します。
Process.sequential(直列処理)やProcess.hierarchical(階層処理)などのプロセスを定義できます。
CrewAIでのエージェントとタスクの定義例
以下に、CrewAIを使って市場調査員とコンテンツライターのエージェントが連携してブログ記事を作成するコード例を示します。
# CrewAIのインストール
# pip install 'crewai[tools]' duckduckgo-search python-dotenv
from crewai import Agent, Task, Crew, Process
from crewai_tools import DuckDuckGoSearchRun # Web検索ツール
import os
from dotenv import load_dotenv
load_dotenv()
# 環境変数の設定 (例: OpenAI APIキー)
# os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"
# または OPENAI_API_BASE, OPENAI_MODEL_NAME なども設定可能
# カスタムツールの定義 (例: Web検索ツール)
search_tool = DuckDuckGoSearchRun()
# エージェントの定義
# 各エージェントには明確な役割、ゴール、バックストーリーを与えます。
researcher = Agent(
role='市場調査員',
goal='特定のトピックの最新市場トレンドを特定する',
backstory='あなたは市場トレンドの特定に長けた経験豊富な市場調査員です。',
tools=[search_tool], # 必要なツールを割り当てる
verbose=True, # 実行の詳細を出力
allow_delegation=False # 他のエージェントにタスクを委譲しない
)
writer = Agent(
role='コンテンツライター',
goal='調査結果に基づいて魅力的なブログ記事を作成する',
backstory='あなたは複雑な情報を簡潔で魅力的なブログ記事にまとめる才能のあるライターです。',
verbose=True,
allow_delegation=True # 他のエージェントにタスクを委譲する可能性がある
)
# タスクの定義
# 各タスクには明確な説明と期待される出力、担当エージェントを指定します。
research_task = Task(
description='AIの最新トレンドについて包括的な調査を実施し、主要な進歩をまとめる。特にAgentic RAGとLLM評価駆動開発に焦点を当てる。',
expected_output='AIの最新トレンドに関する詳細なサマリーレポート。',
agent=researcher
)
write_blog_task = Task(
description='市場調査員からのサマリーレポートを基に、技術ブログ記事を作成する。タイトルは「ゼロから構築!Agentic RAGの高精度LLMアプリと評価駆動開発」とし、読者を引きつける、SEOに最適化されたブログ記事を作成する。',
expected_output='読者を引きつける、SEOに最適化されたブログ記事。',
agent=writer
)
# クルーの作成と実行
# エージェントとタスクをクルーにまとめ、実行プロセスを定義します。
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_blog_task],
process=Process.sequential, # SequentialまたはHierarchical
verbose=True,
# LLM設定を明示的に指定することも可能
# llm=OpenAI(model="gpt-4o", temperature=0.7)
)
print("CrewAIの実行を開始します...")
result = crew.kickoff()
print("\n--- CrewAI 実行結果 ---")
print(result)
CrewAIのベストプラクティス:
- 小規模から始める: 最初は2〜3エージェントのシンプルなクルーから始め、徐々に複雑さを増していきます。
- 明確な役割定義: エージェントごとに明確な役割と責任を定義し、役割の重複を避けることで、効率的なコラボレーションを促します。
- タスク設計に注力: エージェント定義よりも、タスクの設計に80%の労力を費やすべきです。各タスクは明確で具体的な目的を持ち、単一の出力を持つようにします。
- メモリ機能の統合: 長期的なタスクや複数の対話にわたるタスクでは、メモリ機能を統合することでエージェントのパフォーマンスを向上させます。
Agentic RAGにおける設計上のトレードオフと選択基準
LlamaIndex、LangGraph、CrewAIはそれぞれ異なる強みを持つため、プロジェクトの要件に応じて適切なフレームワークを選択することが重要です。
| 特徴 | LlamaIndex | LangGraph | CrewAI |
|---|---|---|---|
| 主な用途 | RAGパイプラインの構築、データフレームワーク | 複雑なエージェントのオーケストレーション、状態管理、条件分岐、ループ | 役割ベースのマルチエージェントコラボレーション、タスクの委譲 |
| 得意なシナリオ | 知識ベースの構築、効率的な情報検索 | 動的な意思決定、自己修正、エラーハンドリング、人間による承認 | 構造化されたチームワーク、専門エージェントの連携 |
| 複雑度 | 中 | 高 | 中〜高 |
| 学習コスト | 中 | 高 | 中 |
| Agentic RAGとの関係 | RAGの基盤を提供し、エージェントのツールとして利用される | エージェントの思考プロセスと行動を制御するコアフレームワーク | 複数のエージェントがRAGを含むツールを連携して使用する |
Agentic RAGの採用判断
- 従来のRAGで十分な場合: クエリが単一ホップで、データが単一ソースにあり、レイテンシーが深度よりも重要な場合は、従来のRAGが効率的です。
- Agentic RAGが必要な場合: クエリが多段階の推論、クロスソースの統合、自己修正を必要とし、高レイテンシーとコストを許容できる場合は、Agentic RAGを検討します。まずは従来のRAGを構築し、それがユーザーの実際のクエリで失敗しているという証拠がある場合にのみ、エージェントオーケストレーションを追加するのが賢明なアプローチです。
LangGraphとCrewAIの選択
- CrewAIの選択: 作業が明確な役割とタスクの引き渡しに分解でき、構造化されたコラボレーションが求められる場合に適しています。
- LangGraphの選択: システムが分岐ロジック、ループ、リトライ、共有状態、人間による承認ポイントを必要とし、実行が状態に応じて適応する必要がある場合に強力です。
これら2つのフレームワークは排他的ではなく、組み合わせて使用することも可能です。例えば、LangGraphで全体のエージェントオーケストレーションを定義し、その中の特定のタスクをCrewAIのサブクルーに委譲するといったアーキテクチャも考えられます。
よくあるエラー・ハマりどころと回避策
LLMアプリケーション開発、特にAgentic RAGのような複雑なシステムでは、様々なハマりどころが存在します。
-
LlamaIndex: ドキュメントの読み込み失敗、空のコーパス
-
ハマりどころ:
SimpleDirectoryReaderがPDFなどからテキストを抽出できず、空のコーパスが作成されることがあります。これにより、LLMが自身の学習データに基づいて誤った回答を生成してしまいます。 -
回避策:
- 読み込み後、ドキュメントのカウントと、最初の数個のドキュメントの数百文字を印刷して、テキストが空でないか、破損していないかを確認します。
- OCRやレイアウト処理をサポートするパーサー(例: LlamaParse)を介してファイルをルーティングすることを検討します。
-
SimpleDirectoryReaderがサポートするファイル形式(.txt,.md,.jsonなど)を事前に確認し、必要に応じて前処理を行います。
-
ハマりどころ:
-
LlamaIndex: メタデータがアタッチされず、フィルターが機能しない
- ハマりどころ: フィルタリングに利用したいメタデータ(ソース、セクション、ドキュメントタイプ、日付など)がノードに正しくアタッチされていないため、フィルタークエリが期待通りに機能しません。
-
回避策:
- 取り込み時にメタデータをアタッチし、取得したノードの
node.metadataを印刷して、メタデータが正しく存在することを確認します。 -
MetadataExtractorやカスタムのノードパーサーを使用して、メタデータを自動的に抽出・付与する仕組みを導入します。 - メタデータフィルタリングは、埋め込みの改善よりも効果的な場合があるため、積極的に活用を検討しましょう。
- 取り込み時にメタデータをアタッチし、取得したノードの
-
LangGraph: ツール呼び出しエラー
- ハマりどころ: LLMが誤ったツール名を呼び出したり、期待されるスキーマに準拠しない引数を渡したり、以前のエージェントの状態と矛盾する入力を提供したりすることがあります。
-
回避策:
- ツールスキーマ、説明を改善し、ツールオプションを制限することで、LLMがツールを正しく理解できるよう、プロンプトエンジニアリングを適用します。
-
ToolNodeはツールエラーを自動的にキャプチャし、モデルに報告するため、LLMがエラーメッセージを解釈し、自己修正を試みるようにプロンプトを設計します。 -
RetryPolicyを使用して、指数関数的バックオフを含むリトライをノードにアタッチし、一時的なエラーに対応します。
-
LangGraph: 状態管理のミス
- ハマりどころ: Reducerを定義し忘れると、リストなどの累積されるべき値が新しいデータで上書きされたり、型定義内で可変デフォルトに依存すると、永続的な状態として機能しなかったりします。
-
回避策:
- 累積される値を持つリストを扱う場合は、
Annotated[List[...], operator.add]のように適切なReducerを定義します。 -
invoke呼び出し時に実際の状態を明示的に初期化します。 - 状態は最小限に、明示的に、
TypedDict、Pydantic、dataclassesのいずれかで型付けされたものにし、コードベース全体で一貫して使用します。
- 累積される値を持つリストを扱う場合は、
-
Agentic RAG: エージェントのループ、根拠のない生成
- ハマりどころ: エージェントがループに陥り、タスクの範囲を超えてしまうことがあります。また、生成された回答が根拠のないもの(ハルシネーション)になる問題も発生します。
-
回避策:
- 停止または次のステップに進むための明確な境界線を定義することで、適切な目標定義と段階的な計画を立てます。プロンプトで終了条件を明確に指示します。
- RAG評価メトリクス(忠実性、回答の関連性、コンテキストの精度、コンテキストの再現率など)を使用して、根拠のない生成を特定し、改善します。評価駆動開発を導入し、継続的に品質を測定・改善します。
- エージェントの思考プロセス(thought process)をログに出力し、デバッグを容易にします。
まとめと次の一歩
この記事では、従来のRAGの限界をAgentic RAGと評価駆動開発によってどのように克服し、高精度なLLMアプリケーションを構築するかについて解説しました。LlamaIndexによる堅牢なRAGパイプラインの構築、LangGraphによる複雑なエージェントのオーケストレーション、CrewAIによる協調的なマルチエージェントシステムの実現、そしてこれらすべての品質を保証するための評価駆動開発の重要性を理解していただけたかと思います。
- Agentic RAG: 従来のRAGの単一パス検索の限界を突破し、エージェントの推論能力とツール利用を組み合わせることで、複雑なクエリや多段階の推論に対応できる高精度なLLMアプリを構築します。
- LlamaIndex: RAGパイプラインの基盤として、データ取り込みからインデックス作成、クエリ実行までを効率的に行い、エージェントの知識源を強化します。
- LangGraph: エージェントの動的な思考プロセス、条件分岐、ループ、エラーハンドリングを柔軟に定義し、自己修正可能な複雑なエージェントワークフローを実現します。
- CrewAI: 役割ベースのマルチエージェントシステムを構築し、複数のエージェントが協調して複雑なタスクを解決するシナリオで特に威力を発揮します。
- 評価駆動開発 (EDD): LLMアプリの品質を客観的に測定し、改善サイクルを回すための不可欠なプロセスです。RAGAS, TruLens, DeepEval, LangSmithなどの評価フレームワークを活用し、CI/CDに組み込むことで、継続的な品質向上を目指します。
これらの技術は、単独ではなく組み合わせて使用することで最大の効果を発揮します。まずはご自身のプロジェクトの要件と複雑度を評価し、最適なフレームワークの組み合わせと開発戦略を検討してみてください。
次の一歩として、各フレームワークの公式ドキュメントを深く読み込み、実際に手を動かしてプロトタイプを構築することをお勧めします。特に、評価駆動開発は早期に導入することで、開発後期での大きな手戻りを防ぎ、最終的なアプリケーションの品質を飛躍的に向上させることができます。