脆いAI開発はやめよう:LangGraphによる自己修正型Agentic RAGの構築

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

「素朴なRAG」の壁

現代のAI開発における「Hello World」といえば、シンプルな検索拡張生成(RAG)パイプラインです。PDFをチャンク分割し、PineconeやChromaのようなベクトルデータベースに投入し、上位3つの結果を取得してLLMにプロンプトを送る。週末のプロジェクトとしては最適です。しかし、この線形的なアプローチは、本番環境ではしばしば壁に突き当たります。

標準的なRAG設定が失敗する原因として最も多いのは、クエリが少し曖昧な場合です。例えば、ユーザーが昨日リリースされたソフトウェアの新機能について質問したのに、ベクトルストアの最終更新が1ヶ月前だった場合、LLMは自信満々に間違った答え(ハルシネーション)を生成する可能性が高いでしょう。LLMには、内部ドキュメントが古いことに気づく「自己認識」が欠けているのです。私のテストでは、動的なドメインにおいて、素朴なRAGシステムは最大30%の確率で無関係なコンテキストを提供し、ユーザーの信頼を完全に損なう結果となりました。

Agentic RAG(エージェント型RAG)はこの状況を一変させます。一方通行の道(検索 -> 生成)ではなく、循環的なロジックゲートを構築します。取得したデータが役に立たない場合、システムは単に推測するのではなく、一旦停止し、検索クエリを書き換え、Tavilyを使用してライブウェブから最新データを取得します。これをカスタマーサポートボットに導入したところ、ハルシネーション率は即座に、かつ測定可能なほど低下しました。

自己修正型システムのアーキテクチャ

基本的な検索を超えて進化させるには、ロジックのループを処理するフローコントローラーと、人間ではなくマシンのために設計された検索ツールが必要です。

LangGraph:ループのあるロジック

標準的なLangChainは線形なシーケンスには適していますが、逆戻りが必要な処理には苦労します。Agentic RAGは本質的に非線形です。ドキュメントの関連性チェックに失敗した場合は、最初に戻ってループする必要があります。

LangGraphは、AIの推論を「ステートマシン(状態機械)」として扱います。ノード(アクション)とエッジ(それらの間の経路)を定義することで、エージェントが高い信頼性の解決策に達するまで問題を循環させることができます。これにより、AIの意思決定プロセスが予測可能になり、さらに重要なことに、デバッグが容易になります。

Tavily:LLMのための検索エンジン

GoogleやBingは、人間が広告やSEO重視の記事をクリックするように設計されています。AIエージェントがウェブを検索する際、2,000語のブログ記事は必要ありません。必要なのは、生の事実に基づいたスニペットです。Tavilyはノイズをフィルタリングします。広告も、肥大化したJavaScriptも、SEOのための水増し表現もありません。内部ナレッジベースが不足している場合の、高精度なセーフティネットとして機能します。

システムの構築

自分の作業をセルフチェックするフローを構築してみましょう。このシステムはドキュメントを取得して評価し、ローカルデータが不十分な場合にのみウェブ検索を実行します。

ステップ1:基盤の準備

まずはコアスタックをインストールします。進めるにはOpenAI의 キーとTavilyのAPIキーが必要です。

pip install langchain-openai langgraph tavily-python chromadb

ステップ2:状態(State)の定義

LangGraphにおいて、Stateオブジェクトは唯一の信頼できる情報源(Single Source of Truth)です。グラフ内を移動しながら、質問、見つかったドキュメント、およびウェブ検索が必要かどうかを追跡します。

from typing import List, TypedDict

class GraphState(TypedDict):
    question: str
    generation: str
    web_search: str # "Yes" または "No"
    documents: List[str]

ステップ3:評価ノード(Grader Node)

ここに「Agentic(エージェント的)」な振る舞いが集約されます。GPT-4o-miniのような高速なモデルを「裁判官」として使用します。取得したコンテキストがユーザーのプロンプトに直接答えていない場合、評価者はweb_searchスイッチを「Yes」に切り替えます。

from langchain_openai import ChatOpenAI

def grade_documents(state):
    print("---ドキュメントの関連性を評価中---")
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    
    question = state["question"]
    documents = state["documents"]
    
    # 実際のアプリでは、ここで構造化出力パーサーを使用してください
    if not documents or "irrelevant" in documents[0]: 
        return {"web_search": "Yes", "question": question}
    return {"web_search": "No", "question": question, "documents": documents}

ステップ4:Tavilyの統合

内部検索が失敗した場合は、ウェブにアクセスします。これにより、リアルタイムの出来事や、ローカルデータベースに含まれていないニッチなトピックについても、エージェントが回答できるようになります。

from langchain_community.tools.tavily_search import TavilySearchResults

def web_search(state):
    print("---ウェブ検索に切り替え中---")
    question = state["question"]
    tavily = TavilySearchResults(k=3)
    
    results = tavily.invoke({"query": question})
    content = "\n".join([r["content"] for r in results])
    
    return {"documents": [content], "question": question}

ステップ5:グラフの構築

次にロジックをマッピングします。web_searchフラグを見て次の動きを決定する条件付きエッジ(conditional edge)を定義します。

from langgraph.graph import END, StateGraph

workflow = StateGraph(GraphState)

workflow.add_node("retrieve", retrieve_docs)
workflow.add_node("grade_documents", grade_documents)
workflow.add_node("web_search_node", web_search)
workflow.add_node("generate", generate_answer)

workflow.set_entry_point("retrieve")
workflow.add_edge("retrieve", "grade_documents")

workflow.add_conditional_edges(
    "grade_documents",
    lambda x: x["web_search"],
    {
        "Yes": "web_search_node",
        "No": "generate",
    },
)
workflow.add_edge("web_search_node", "generate")
workflow.add_edge("generate", END)

app = workflow.compile()

本番環境で得られた教訓

これらのシステムを実際に運用してみた結果、ロジックは戦いの半分に過ぎないことが分かりました。Agentic RAGをスケールさせるための3つの実践的な教訓を紹介します。

  • 評価者(Grader)を微調整する: 評価者が厳しすぎると、エージェントがあらゆるクエリに対してウェブ検索をデフォルトにするため、APIコストが急増します。逆に甘すぎると、ハルシネーションに戻ってしまいます。評価者の「厳格さ」を調整するために、フューショットプロンプト(few-shot prompt)を使用してください。
  • コンテキストウィンドウに注意する: Tavilyからの生のウェブ検索結果は5,000トークンを超えることがあります。これらを直接プロンプトに投入してはいけません。「Map-Reduce」アプローチや簡単な要約ステップを使用して、関連する500トークンだけを抽出してください。これによりコストを節約し、LLMの集中力を高めることができます。
  • 「分かりません」のしきい値: ウェブでさえ答えを持っていないことがあります。私は常に最終チェックを含めています。ウェブ検索の結果も関連性が低い場合、エージェントは事実を捏造するのではなく、丁寧に人間にエスカレーションするか、厳選されたヘルプリンクを提供すべきです。

LangGraphはここで大きな利点をもたらします。それは「可観測性(Observability)」です。カスタマーが奇妙な回答を受け取った際、グラフの状態を確認して、ロジックのどこで道が逸れたかを正確に把握できます。本来「不適切」とされるべきドキュメントがなぜ「関連あり」と評価されたのかを特定でき、数分でプロンプトを修正することが可能です。

結論

RAGからAgentic RAGへのアップグレードは、受動的なドキュメント検索から能動的なリサーチアシスタントへの進化を意味します。システムに自らのデータを検証し、混乱したときには外部に目を向けるツールを与えることで、はるかに堅牢なものを構築できます。まずはシンプルな3ノードのグラフから始めてください。データの例外ケースが見つかるたびにループを追加していけば、その複雑さは、ユーザーが心から信頼できるAIという形で報われるはずです。

Share: