アーパボー(ARPABLE)
アープらしいエンジニア、それを称賛する言葉・・・アーパボー
Agent

RAGの進化形:AutoRAGとLangGraphで実現する次世代AIの構築方法

Table of Contents

RAGの進化形:AutoRAGとLangGraphで実現する次世代AIの構築方法

はじめに:RAGの限界と自動化への道

RAGとは何か:基本的な仕組み

大規模言語モデル(LLM)の登場により、AI技術は飛躍的に進化しました。しかし、LLMには学習データ以降の最新情報へのアクセス制限や幻覚(ハルシネーション)といった課題があります。

これらを解決するために登場したのが「検索拡張生成(Retrieval-Augmented Generation:RAG)」です。RAGは次のような仕組みで動作します。

  1. ユーザーからの質問や指示を受け取る
  2. 外部知識源(データベースやWeb)から関連情報を検索する
  3. 検索結果をLLMへのプロンプトに組み込む
  4. 外部知識を参照しながら回答を生成する

この方法により、LLMは自身の訓練データに含まれていない最新情報や専門的な情報にアクセスでき、より正確で時事的な回答が可能になります。RAGは企業の内部文書検索や最新情報を要する質問応答システムなど、様々な用途で広く採用されています。

従来のRAGが抱える限界

しかし、従来のRAGにも以下のような問題点があります。

  1. 一律処理の制約: すべての質問に対して同じプロセスを適用するため、検索が不要な場合でも無駄な処理が発生します。例えば「こんにちは」といった挨拶にも検索処理が走ってしまいます。
  2. クエリ最適化の難しさ: ユーザーの質問をそのまま検索クエリとして使用すると、効果的な検索結果が得られないケースが多くあります。「太陽系の惑星について教えてください」という質問は、検索エンジンでは「太陽系 惑星 特徴 一覧」などのキーワードの方が効果的です。
  3. 情報ソース選択の硬直性: 異なる種類の質問に対して、適切な情報源を動的に選択する柔軟性に欠けます。技術的な質問には技術文書が、一般的な質問にはFAQが適しているといった使い分けができません。

AutoRAGという解決策

こうした課題に対応するため、RAGプロセスを自動化・最適化する「AutoRAG」という新しいアプローチが登場しました。AutoRAGは各ステップでLLMの判断能力を活用し、質問の性質に応じて最適な処理を選択します。

また、こうした複雑なワークフローを柔軟に設計できる「LangGraph」というフレームワークも注目を集めています。LangGraphは状態管理と条件分岐に優れており、AutoRAGの実装に最適なツールです。

本記事では、AutoRAGとLangGraphを組み合わせることで実現する次世代RAGシステムについて詳しく解説します。この組み合わせにより、より知的で効率的な情報検索・生成システムの構築が可能になります。

AutoRAGとは:従来のRAGとの違い

AutoRAGの本質

AutoRAGは「判断するRAG」と言えるでしょう。従来のRAGが固定的なパイプラインだったのに対し、AutoRAGは各ステップで「何をすべきか」をLLMが判断しながら進行します。

 

           図1 従来のRAGとAutoRAGのフロー比較

 

上図のようにAutoRAGでは各ステップがLLMによる判断を伴いフローが動的に変化する。

AutoRAGの主要構成要素

AutoRAGは以下の主要なコンポーネントで構成されています。

  1. QueryAnalyzer(クエリ分析器):
    • 質問の意図を理解し、検索が必要かどうかを判断
    • 単純な事実確認なら検索をスキップし、複雑/最新情報を要する質問なら検索を実施
  2. QueryRewriter(クエリ書き換え器):
    • ユーザーの質問を検索に最適化されたクエリに変換
    • 例:「太陽系の惑星について教えてください」→「太陽系 惑星 一覧 特徴」
  3. RetrieverManager(検索管理器):
    • 質問の種類に応じて最適な情報源(ベクトルDB、Web、API等)を選択
    • 複数の情報源から検索結果を統合
  4. DocReranker(文書再評価器):
    • 取得した情報の関連性や信頼性を評価し、最適な順序に並べ替え
    • 冗長な情報や低品質な情報をフィルタリング
  5. AnswerSynthesizer(回答生成器):
    • 取得した情報を基に、一貫性のある包括的な回答を生成
    • 情報の矛盾を検出し、解決する機能も備える

これらのコンポーネントがLLMの判断能力を活用することで、状況に応じた柔軟なRAG処理が実現します。

LangGraphの登場:状態管理とフロー制御

LangGraphとは何か:LLMワークフローの新たな設計パラダイム

LangGraphは、LangChainが提供する最新のフレームワークで、状態管理とフロー制御に特化しています。従来のLLMアプリケーション開発では、処理の流れを直線的に定義するのが一般的でしたが、より複雑な対話型アプリケーションでは、文脈の保持や条件に応じた分岐処理が必要になります。

LangGraphはこうした複雑なワークフローを「状態を持つ有向グラフ」として設計できる環境を提供します。特に以下の機能が重要です。

  • 状態管理: 会話の文脈や処理の中間結果を保持し、ノード間で共有できます
  • 条件分岐: 特定の条件に基づいて処理の流れを動的に変更できます
  • サイクル処理: 必要に応じて特定のステップを繰り返し実行できます

これらの機能により、「質問の種類に応じて検索をスキップする」「必要に応じて情報を再取得する」といった柔軟な処理が可能になります。

LangGraph vs 従来のフレームワーク:コードで見る違い

従来のLangChainでは、処理は通常直線的なパイプラインとして実装されていました。一方、LangGraphでは条件分岐や状態管理を含む複雑なフローを構築できます。以下のコード例でその違いを見てみましょう。

# 従来の線形RAGパイプライン(LangChain)
from langchain.chains import LLMChain
from langchain.vectorstores import Chroma

chain = (
    {"query": RunnablePassthrough()}
    | retriever
    | format_docs
    | llm
)

上記のコードでは、クエリを受け取り、情報を検索し、フォーマットして、LLMで回答を生成するという一方通行の処理が定義されています。質問の種類に関わらず、常に同じ手順で処理が行われます。

一方、LangGraphでは次のように条件分岐を含む処理が可能です。

# LangGraphによる条件分岐を含むRAG
from langgraph.graph import StateGraph

builder = StateGraph(RAGState)
builder.add_node("analyze", analyze_query)
builder.add_node("retrieve", retrieve_docs)
builder.add_node("generate", generate_answer)

# 条件分岐の追加
builder.add_conditional_edges("analyze", router, {
    "needs_search": "retrieve",
    "direct_answer": "generate"
})

builder.add_edge("retrieve", "generate")
builder.set_entry_point("analyze")
builder.set_finish_point("generate")

このコードでは、まず質問を分析し(analyze)、検索が必要な場合のみ情報検索(retrieve)を行い、最後に回答を生成(generate)します。analyzeノードの結果に応じて処理パスが変わるため、単純な質問に対しては不要な検索処理をスキップできます。

LangGraphの核心:状態管理とグラフ構造

LangGraphの最大の特徴は「状態(State)」という概念です。状態は以下のように定義されたデータ構造で、グラフ内のすべてのノードで共有されます。

from typing import TypedDict, List

class RAGState(TypedDict):
    messages: List[str]  # 会話履歴
    query: str           # 現在の質問
    documents: List[str] # 検索結果
    response: str        # 生成された回答

各ノードは状態を入力として受け取り、処理結果に基づいて状態を更新します。これにより、ノード間でのデータの受け渡しが容易になり、会話の流れや中間処理結果を保持できます。

また、グラフ構造を定義する際には、以下の要素を指定します。

  1. ノード: 各処理ステップを担当する関数
  2. エッジ: ノード間の接続関係
  3. 条件付きエッジ: 条件に応じて異なるノードへの接続
  4. エントリーポイント: 処理の開始ノード
  5. 終了ポイント: 処理の終了ノード

これらの要素を組み合わせることで、複雑な対話型アプリケーションの処理フローを直感的に設計できます。

LangGraphが解決する実践的な課題

LangGraphは以下のような実際のアプリケーション開発における課題を解決します:

  1. 会話の文脈維持: 長期的な対話において、過去のやり取りを状態として保持できます
  2. 条件付き処理: ユーザー入力の種類や内容に応じて、異なる処理パスを選択できます
  3. 反復的な処理: 必要に応じて特定のステップを繰り返し実行できます(例:検索結果が不十分な場合に再検索)
  4. 複雑なワークフロー: 複数のLLMやツールを組み合わせた高度な処理フローを設計できます

これらの特性により、LangGraphはAutoRAGのような複雑な判断ロジックを実装するのに最適なフレームワークとなっています。

※)LangGraphの詳細はこちらにまとめてますのでご参照ください。
LangGraphで極めるRAG型AIエージェント開発

AutoRAG × LangGraph:シナジーの理解

AutoRAGとLangGraphを組み合わせることで、それぞれの強みが相乗効果を生み出します:

  • AutoRAG: 各ステップでの「判断」と「専門処理」を担当
  • LangGraph: 全体の「状態管理」と「フロー制御」を担当

          図2 AutoRAG × LangGraph:フロー構成例

【図2解説】
LangGraph上にAutoRAGの主要構成要素をノードとして組込んだフロー例
主要な連携ポイント
LangGraph側の役割:
  • 状態の定義と管理(会話履歴、検索結果など)
  • 条件分岐の制御(検索が必要か否かなど)
  • 各ノード間の接続と実行順序の管理
AutoRAG側の役割:
  • 各ノード内での具体的な判断ロジックの実装
  • 専門的な処理(クエリ最適化、文書評価など)の実行
  • 外部システム(検索エンジン、データベースなど)との連携

LangGraphを活用したAutoRAG実装

それでは、LangGraphを使ってAutoRAGのワークフローを実装する具体的な方法を見ていきましょう。以下のコードは、基本的なAutoRAGシステムを構築するための実装例です。

from langgraph.graph import StateGraph
from typing import TypedDict, List, Dict, Any
from langchain.schema import BaseMessage, AIMessage
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI

# ① ステート定義:AutoRAGの状態を管理するデータ構造
class AutoRAGState(TypedDict):
    messages: List[BaseMessage]  # 会話履歴
    query: str                   # 検索クエリ
    search_results: List[str]    # 検索結果
    answer: str                  # 最終回答

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

# ② 検索必要性の判断ノード(decision_node)
def decision_node(state: AutoRAGState) -> str:
    """ユーザーの質問を分析し、検索が必要かどうかを判断"""
    latest = state["messages"][-1].content
    
    # 簡易的な判断ロジック(実際はLLMを使用して高度に判断)
    search_keywords = ["最新", "情報", "ニュース", "調査", "データ"]
    if any(keyword in latest for keyword in search_keywords):
        return "query_node"  # 検索が必要
    else:
        return "answer_node"  # 検索不要、直接回答

# ③ 検索クエリ生成ノード(query_node)
def query_node(state: AutoRAGState) -> Dict:
    """ユーザーの質問から検索に最適化されたクエリを生成"""
    latest = state["messages"][-1].content
    
    # 実際はLLMを使ってより高度なクエリ最適化を行う
    prompt = f"""
    以下の質問から、検索エンジンで最良の結果を得るためのキーワードを抽出してください:
    質問: {latest}
    検索キーワード:
    """
    
    response = llm.invoke(prompt)
    optimized_query = response.content.strip()
    
    return {"query": optimized_query}

# ④ ベクトル検索ノード(search_node)
def search_node(state: AutoRAGState) -> Dict:
    """最適化されたクエリを使用して情報を検索"""
    query = state["query"]
    
    # 実際はベクトルDBやWeb APIを使用して検索
    # この例では簡易的なモック検索結果を返す
    results = [
        f"{query}に関する情報源1: ここに実際の検索結果の内容が入ります。",
        f"{query}に関する情報源2: 別の視点からの情報がここに入ります。",
        f"{query}に関する情報源3: さらに補足的な情報がここに含まれます。"
    ]
    
    return {"search_results": results}

# ⑤ 文書リランキングノード(rerank_node)
def rerank_node(state: AutoRAGState) -> Dict:
    """検索結果を関連性に基づいて評価・ランク付け"""
    query = state["query"]
    results = state["search_results"]
    
    # 実際はLLMを使用して各文書の関連性をスコアリング
    # この例では単純な長さでソート(実際の実装ではより洗練された方法を使用)
    ranked_results = sorted(results, key=len, reverse=True)
    
    # 上位の検索結果のみを返す
    return {"search_results": ranked_results[:2]}

# ⑥ 回答生成ノード(answer_node)
def answer_node(state: AutoRAGState) -> Dict:
    """検索結果を基にユーザーの質問に回答"""
    latest_query = state["messages"][-1].content
    
    # 検索結果があれば利用、なければLLM単体で回答
    if "search_results" in state and state["search_results"]:
        context = "\n".join(state["search_results"])
        prompt = f"""
        質問: {latest_query}
        
        以下の情報源を参考にして、質問に対する包括的な回答を作成してください:
        ---
        {context}
        ---
        
        回答:
        """
    else:
        prompt = f"""
        質問: {latest_query}
        
        あなたの知識に基づいて回答してください。
        
        回答:
        """
    
    response = llm.invoke(prompt)
    answer = response.content.strip()
    
    return {"answer": answer, "messages": state["messages"] + [AIMessage(content=answer)]}

# ⑦ グラフ構築
builder = StateGraph(AutoRAGState)

# ノードの追加
builder.add_node("decision_node", decision_node)
builder.add_node("query_node", query_node)
builder.add_node("search_node", search_node)
builder.add_node("rerank_node", rerank_node)
builder.add_node("answer_node", answer_node)

# 条件分岐の設定
builder.add_conditional_edges(
    "decision_node",
    lambda state: decision_node(state),
    {
        "query_node": "query_node",
        "answer_node": "answer_node"
    }
)

# 検索フローのエッジ設定
builder.add_edge("query_node", "search_node")
builder.add_edge("search_node", "rerank_node")
builder.add_edge("rerank_node", "answer_node")

# 開始ノードと終了ノードの設定
builder.set_entry_point("decision_node")
builder.set_finish_point("answer_node")

# グラフのコンパイル
graph = builder.compile()

処理フロー解説】AutoRAG × LangGraph

この記事で紹介するサンプルコードは、LangGraphフレームワークを用いたAutoRAGシステムの実装例です。実際のシステム構築の流れに沿って、処理フローを丁寧に解説していきます。本コードは本記事の核となる内容であり、理解を深めるためにもじっくり取り上げます。
具体的なユースケースとして、「弊社の実親の忌引きは何日ですか?」という質問を例に、システムがどのように動作するのかをステップごとに解説していきます。

1. 初期化・環境設定

from langgraph.graph import StateGraph
from typing import TypedDict, List, Dict, Any
from langchain.schema import BaseMessage, AIMessage
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI

# システムの状態を定義
class AutoRAGState(TypedDict):
    messages: List[BaseMessage]  # 会話履歴
    query: str                   # 検索クエリ
    search_results: List[str]    # 検索結果
    answer: str                  # 最終回答

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

ここでは、LangGraphの中核機能であるStateGraphをインポートし、システムの状態AutoRAGStateを定義しています。この状態は、会話履歴、検索クエリ、検索結果、最終回答といった情報を管理します。また、処理で使用するLLM(GPT-3.5-turbo)も初期化しています。

2. ノード関数の定義

次に、グラフの各ノードとして機能する関数を定義します。各ノードはAutoRAGの主要コンポーネントに対応しています。

2.1 決定ノード(QueryAnalyzer)

def decision_node(state: AutoRAGState) -> str:
    """ユーザーの質問を分析し、検索が必要かどうかを判断"""
    latest = state["messages"][-1].content
    
    # 簡易的な判断ロジック(実際はLLMを使用して高度に判断)
    search_keywords = ["最新", "情報", "ニュース", "調査", "データ"]
    if any(keyword in latest for keyword in search_keywords):
        return "query_node"  # 検索が必要
    else:
        return "answer_node"  # 検索不要、直接回答

この関数は、ユーザーからの質問を分析して、外部知識ソースでの検索が必要かどうかを判断します。「弊社の実親の忌引きは何日ですか」のような会社固有の情報を要求する質問では、検索が必要と判断します。

2.2 クエリ最適化ノード(QueryRewriter)

def query_node(state: AutoRAGState) -> Dict:
    """ユーザーの質問から検索に最適化されたクエリを生成"""
    latest = state["messages"][-1].content
    
    # 実際はLLMを使ってより高度なクエリ最適化を行う
    prompt = f"""
    以下の質問から、検索エンジンで最良の結果を得るためのキーワードを抽出してください:
    質問: {latest}
    検索キーワード:
    """
    
    response = llm.invoke(prompt)
    optimized_query = response.content.strip()
    
    return {"query": optimized_query}

この関数は、ユーザーの質問を検索に最適化されたクエリに変換します。「弊社の実親の忌引きは何日ですか」という質問は「弊社 実親 忌引き 日数 規定 就業規則」のようなキーワードに変換されることが期待されます。

2.3 検索ノード(RetrieverManager)

def search_node(state: AutoRAGState) -> Dict:
    """最適化されたクエリを使用して情報を検索"""
    query = state["query"]
    
    # 実際はベクトルDBやWeb APIを使用して検索
    # この例では簡易的なモック検索結果を返す
    results = [
        f"{query}に関する情報源1: ここに実際の検索結果の内容が入ります。",
        f"{query}に関する情報源2: 別の視点からの情報がここに入ります。",
        f"{query}に関する情報源3: さらに補足的な情報がここに含まれます。"
    ]
    
    return {"search_results": results}

この関数は、最適化されたクエリを使用して情報を検索します。実際のシステムではベクトルデータベースやWeb APIを使用しますが、このサンプルコードではモックデータを返します。

2.4 リランキングノード(DocReranker)

def rerank_node(state: AutoRAGState) -> Dict:
    """検索結果を関連性に基づいて評価・ランク付け"""
    query = state["query"]
    results = state["search_results"]
    
    # 実際はLLMを使用して各文書の関連性をスコアリング
    # この例では単純な長さでソート(実際の実装ではより洗練された方法を使用)
    ranked_results = sorted(results, key=len, reverse=True)
    
    # 上位の検索結果のみを返す
    return {"search_results": ranked_results[:2]}

この関数は、検索結果を関連性に基づいて評価し、最も重要な情報を優先順位付けします。単純な長さでのソートではなく、実際のシステムではLLMを使用して各結果の質問に対する関連性を評価します。

2.5 回答生成ノード(AnswerSynthesizer)

def answer_node(state: AutoRAGState) -> Dict:
    """検索結果を基にユーザーの質問に回答"""
    latest_query = state["messages"][-1].content
    
    # 検索結果があれば利用、なければLLM単体で回答
    if "search_results" in state and state["search_results"]:
        context = "\n".join(state["search_results"])
        prompt = f"""
        質問: {latest_query}
        
        以下の情報源を参考にして、質問に対する包括的な回答を作成してください:
        ---
        {context}
        ---
        
        回答:
        """
    else:
        prompt = f"""
        質問: {latest_query}
        
        あなたの知識に基づいて回答してください。
        
        回答:
        """
    
    response = llm.invoke(prompt)
    answer = response.content.strip()
    
    return {"answer": answer, "messages": state["messages"] + [AIMessage(content=answer)]}

この関数は、検索結果を基にユーザーの質問に最終的な回答を生成します。検索結果がある場合はそれを参考に、ない場合はLLMの知識だけで回答を生成します。

3. グラフ構造の定義

ノード関数を定義したら、次にこれらを接続してグラフ構造を構築します。これがLangGraphの核心部分です。

# グラフ構築
builder = StateGraph(AutoRAGState)

# ノードの追加
builder.add_node("decision_node", decision_node)
builder.add_node("query_node", query_node)
builder.add_node("search_node", search_node)
builder.add_node("rerank_node", rerank_node)
builder.add_node("answer_node", answer_node)

# 条件分岐の設定
builder.add_conditional_edges(
    "decision_node",
    lambda state: decision_node(state),
    {
        "query_node": "query_node",
        "answer_node": "answer_node"
    }
)

# 検索フローのエッジ設定
builder.add_edge("query_node", "search_node")
builder.add_edge("search_node", "rerank_node")
builder.add_edge("rerank_node", "answer_node")

# 開始ノードと終了ノードの設定
builder.set_entry_point("decision_node")
builder.set_finish_point("answer_node")

# グラフのコンパイル
graph = builder.compile()

このコードでは以下の処理を行っています:

  1. StateGraphのインスタンスを作成し、定義した各ノード関数を追加
  2. 条件分岐を設定(decision_nodeの結果に基づいて次のノードを決定)
  3. 通常のエッジ(ノード間の接続)を設定
  4. 開始ノードと終了ノードを指定
  5. グラフをコンパイルして実行可能な状態にする

 

4. 「弊社の実親の忌引きは何日ですか」の処理フロー解説

では、コンパイルされたグラフに「弊社の実親の忌引きは何日ですか」という質問が入力された場合の処理フローを追跡してみましょう。

4.1 決定ノード(QueryAnalyzer)での処理

入力:「弊社の実親の忌引きは何日ですか」
このノードでは:
  • 質問を分析し、これが会社の規定に関する特定情報を求めていることを認識
  • 判断結果:「検索が必要」→ query_nodeへ進む

 

4.2 クエリ最適化ノード(QueryRewriter)での処理

最適化前のクエリ:「弊社の実親の忌引きは何日ですか」
このノードでは:
LLMにプロンプトを送信

以下の質問から、検索エンジンで最良の結果を得るためのキーワードを抽出してください: 質問: 弊社の実親の忌引きは何日ですか
検索キーワード:

  • LLMが生成した最適化クエリ:「弊社 実親 忌引き 日数 規定 就業規則」
  • この最適化クエリが状態に追加される

4.3 検索ノード(RetrieverManager)での処理

入力クエリ:「弊社 実親 忌引き 日数 規定 就業規則」
このノードでは:ベクトルDBや検索システムに問い合わせた結果(モックデータ)
❶弊社 実親 忌引き 日数 規定 就業規則に関する情報源1: 一般的な企業では実親の忌引きは3〜5日の範囲で設定されています。
❷弊社 実親 忌引き 日数 規定 就業規則に関する情報源2: 就業規則第15条には、実親の場合は連続5労働日の特別休暇が認められています。
❸弊社 実親 忌引き 日数 規定 就業規則に関する情報源3: 実親の忌引き休暇申請には、死亡診断書または葬儀の案内状のコピーが必要です。

これらの検索結果が状態に追加される。

4.4 リランキングノード(DocReranker)での処理

入力:3つの検索結果
このノードでは:
  • 各検索結果の質問に対する関連性を評価
  • 質問は「忌引きは何日か」を尋ねているため、「就業規則第15条」の情報が最も関連性が高い
  • ランク付けされた結果(モックデータ):

❶弊社 実親 忌引き 日数 規定 就業規則に関する情報源1:一般的な企業では実親の忌引きは3〜5日の範囲で設定されています。
❷弊社 実親 忌引き 日数 規定 就業規則に関する情報源2: 就業規則第15条には、実親の場合は連続5労働日の特別休暇が認められています。

上位2件の検索結果のみが保持される.

4.5 回答生成ノード(AnswerSynthesizer)での処理

入力:ランク付けされた検索結果と元の質問
このノードでは:

検索結果をコンテキストとしてLLMに送信:

質問: 弊社の実親の忌引きは何日ですか

以下の情報源を参考にして、質問に対する包括的な回答を作成してください:

❶弊社 実親 忌引き 日数 規定 就業規則に関する情報源2: 就業規則第15条には、実親の場合は連続5労働日の特別休暇が認められています。…
❷弊社 実親 忌引き 日数 規定 就業規則に関する情報源1: 一般的な企業では実親の忌引きは3〜5日の範囲で設定されています。…

回答:

LLMが生成した回答(モックデータ):

弊社では、実親の忌引きに関しては就業規則第15条に規定されており、連続5労働日の特別休暇が認められています。一般的な企業の実親忌引き休暇である3〜5日の範囲内で、弊社は最大日数を採用していることになります。この特別休暇は有給であり、申請の際には必要書類の提出が求められますので、詳細は人事部にご確認ください。

この回答がユーザーに返される。

5. AutoRAG × LangGraphの利点

この「弊社の実親の忌引きは何日ですか」という質問の処理を通して見えてくるAutoRAG × LangGraphの主な利点は以下の通りです。

  1. 質問に応じた動的な処理フロー:
    • 会社固有の情報を求める質問には検索フローを適用
    • 一般的な知識の質問には直接回答フローを適用
  2. 検索クエリの最適化:
    • 自然言語の質問を、「弊社 実親 忌引き 日数 規定 就業規則」のような検索用キーワードに変換
  3. 情報の関連性評価:
    • 「就業規則第15条には、実親の場合は連続5労働日…」という直接的な回答を含む情報を優先
    • 関連性の低い情報を除外
  4. 状態管理とフロー制御:
    • LangGraphによる明確な状態管理とノード間のデータ受け渡し
    • 条件に基づく処理フローの分岐

6. 総括

実際のシステム実装順序に沿って見てきたように、LangGraphを使用したAutoRAGシステムは:

  1. 基本構成要素の定義(状態とLLM)
  2. 各ノード関数の定義(AutoRAGの各コンポーネント)
  3. グラフ構造の構築(ノードの接続と条件分岐)
  4. グラフのコンパイルと実行

という流れで構築されます。

「弊社の実親の忌引きは何日ですか」という質問に対しては、グラフ内で:

  1. 検索が必要と判断
  2. 検索用キーワードに最適化
  3. 関連情報を検索
  4. 検索結果を評価・ランク付け
  5. 最終回答を生成

という処理が行われ、「弊社では実親の忌引きは連続5労働日の特別休暇が認められています」といった正確な回答が生成されます。

この例からわかるように、AutoRAG × LangGraphの組み合わせにより、質問の特性に合わせた動的な処理フローが実現し、より正確で効率的な回答生成が可能となっています。

 

高度な活用例:インデックス選択と多層検索

AutoRAGとLangGraphの組み合わせは、さらに高度な情報検索戦略を実現できます。特にLlamaIndexなどのドキュメント処理ライブラリと連携すると、多層的な情報検索が可能になります。

 図3 図3ではAutoRAGによる多層検索アーキテクチャ

 

図3ではAutoRAGによる多層検索アーキテクチャで質問の種類に応じて異なる情報源を選択・統合している。これをベースに以下に連携例のコードを実装します。

LlamaIndexとの連携例

LlamaIndexは複数種類のインデックスを提供しており、これらを質問の性質に応じて使い分けることができます。

from llama_index import VectorStoreIndex, ListIndex, get_response_synthesizer
from llama_index.tools import QueryEngineTool, ToolMetadata

# 複数のインデックスを準備
vector_index = VectorStoreIndex.from_documents(technical_docs)
list_index = ListIndex.from_documents(faq_docs)

# 各インデックスに基づくクエリエンジンをツールとして登録
tools = [
    QueryEngineTool(
        query_engine=vector_index.as_query_engine(),
        metadata=ToolMetadata(
            name="technical_docs",
            description="技術文書や詳細な説明が必要な質問に使用"
        )
    ),
    QueryEngineTool(
        query_engine=list_index.as_query_engine(),
        metadata=ToolMetadata(
            name="faq_knowledge",
            description="よくある質問や基本的な情報に関する質問に使用"
        )
    )
]

# LangGraphのRetrieverManagerノードで使用するツール選択関数
def select_retriever(state: AutoRAGState) -> Dict:
    query = state["query"]
    
    # LLMを使用してクエリに最適なツールを選択
    prompt = f"""
    以下の質問に対して、最適な情報源を選択してください:
    質問: {query}
    
    選択肢:
    1. technical_docs: 技術文書や詳細な説明が必要な質問
    2. faq_knowledge: よくある質問や基本的な情報に関する質問
    
    最適な情報源の番号:
    """
    
    response = llm.invoke(prompt)
    selected_tool = "technical_docs" if "1" in response.content else "faq_knowledge"
    
    # 選択したツールで検索を実行
    for tool in tools:
        if tool.metadata.name == selected_tool:
            search_results = tool.query_engine.query(query).response
            return {"search_results": [search_results]}
    
    return {"search_results": []}

この実装により、FAQ向けのリスト型インデックスと詳細文書向けのベクトル型インデックスを質問に応じて使い分けることができます。これはAutoRAGの「RetrieverManager」の高度な実装例と言えるでしょう。

実践的応用シナリオとユースケース

AutoRAGとLangGraphの組み合わせは、以下のような実践的なシナリオで特に効果を発揮します。

1. カスタマーサポート自動化

# カスタマーサポート特化型のAutoRAGState
class SupportRAGState(TypedDict):
    messages: List[BaseMessage]
    query: str
    customer_info: Dict
    product_info: Dict
    faq_results: List[str]
    knowledge_base_results: List[str]
    ticket_history: List[Dict]
    answer: str
    escalation_needed: bool

カスタマーサポートでは、問い合わせの種類に応じて異なる情報源(FAQ、製品マニュアル、過去のチケット履歴など)から最適な情報を取得する必要があります。AutoRAGは質問の種類を自動判別し、適切な検索戦略を採用できます。

2. 医療情報検索システム

医療分野では情報の正確性と信頼性が極めて重要です。AutoRAGの文書評価機能を強化し、医学文献、臨床ガイドライン、医薬品情報などから信頼性の高い情報だけを選別することで、より安全な医療情報提供システムを構築できます。

3. 法律文書分析

法律文書は構造化された情報と非構造化テキストが混在しており、単純な検索では適切な情報を得られないことがよくあります。AutoRAGは質問の法的文脈を理解し、関連する法令、判例、ガイドラインなどを適切に検索・統合できます。

将来展望:AutoRAGの発展と課題

技術的な発展方向

  1. マルチモーダル対応: テキストだけでなく、画像や音声などのマルチモーダルデータもRAGプロセスに統合
  2. 自己改善機能: フィードバックを基にRAGプロセスを自動的に最適化する機能
  3. 知識グラフ統合: 単純なベクトル検索から、より構造化された知識表現への発展

現在の課題と対応策

  1. 計算コスト:
    • 課題: 各ステップでLLMを利用するため、処理コストと時間が増加
    • 対応策: 小型で特化型のモデルの使用、キャッシュ機構の導入
  2. 判断精度:
    • 課題: LLMによる判断が常に最適とは限らない
    • 対応策: ヒューリスティックとLLM判断のハイブリッドアプローチ、継続的評価と改善
  3. メタデータ活用:
    • 課題: 現状では文書のメタデータが十分に活用されていない
    • 対応策: 構造化されたメタデータを検索プロセスに統合する機能拡張

まとめ:次世代RAGシステムの構築に向けて

AutoRAGとLangGraphの組み合わせは、従来の固定的なRAGパイプラインから、より柔軟で知的な情報検索・生成システムへの進化を実現します。

AutoRAGは各ステップでLLMの判断能力を活用することで、質問の特性に応じて検索の要否判断、クエリ最適化、情報源選択を動的に行います。一方、LangGraphは状態管理と条件分岐を担当し、複雑なワークフローを構造化します。さらにLlamaIndexとの連携で多層検索が可能になり、CrewAIとの統合でより専門的な役割分担が実現します。

今後の展望として、マルチモーダル対応、自己改善機能、知識グラフ統合などの発展が期待されます。計算コストや判断精度の課題はありますが、小型モデルの活用やハイブリッドアプローチで解決されていくでしょう。次世代RAGシステムは、単なる検索機能を超え、状況に応じて判断・適応する真の知的アシスタントへと進化していきます。

参考サイト

 

 

以上

筆者プロフィール
ケニー狩野(中小企業診断士、PMP、ITコーディネータ)
キヤノン(株)でアーキテクト、プロマネとして多数のプロジェクトをリード。
現在、株式会社ベーネテック代表、株式会社アープ取締役、一般社団法人Society 5.0振興協会評議員ブロックチェーン導入評価委員長。
これまでの知見を活かしブロックチェーンや人工知能技術の推進に従事。趣味はダイビングと囲碁。
2018年「リアル・イノベーション・マインド」を出版。