【LangChain】SQL AgentでRAGに集計・統計機能を追加する方法

はじめに

PS-SLの佐々木です。 アドベントカレンダー14日目になります。 今回はRAGシステムを構築している際にデータの一覧や統計データの取得、集計をしたい場合のTipsを紹介します

セマンティック検索が苦手な質問

RAG(Retrieval-Augmented Generation)システムを構築したことがある方なら、こんな経験はないでしょうか。

ユーザー: 「完了率を教えてください」
RAG: 「完了に関する情報が見つかりました。タスクAは完了しています。タスクBも完了しています...」

ユーザー: 「いや、パーセンテージで知りたいんだけど...」

セマンティック検索(ベクトル検索)は「意味的に関連する情報を取得する」ことは得意ですが、「集計」「統計」「条件フィルタリング」は苦手です。

本記事では、この限界を突破するためにLangChainのSQL Agentを組み合わせるアプローチを紹介します。


セマンティック検索の得意・不得意

得意なこと

クエリ例 なぜ得意か
「冷却システムの問題点は?」 意味的に関連するドキュメントを取得
「バッテリーに関する懸念事項」 「バッテリー」「電池」「蓄電池」など類似概念も取得
「納期遅延のリスクについて」 文脈を理解して関連情報を取得

不得意なこと

クエリ例 なぜ不得意か
「完了率は何%?」 集計計算ができない
「担当者が田中のタスク一覧」 完全一致フィルタリングが苦手
「期限が来週までのものは何件?」 日付比較・カウントができない
「担当者ごとの件数は?」 GROUP BY相当の処理ができない

セマンティック検索は「類似度」で検索するため、「田中」で検索すると「田中」だけでなく「山田」「中田」など”なんとなく似ている”ものも返してしまう可能性があります。


解決策:SQL Agentとのハイブリッドアーキテクチャ

全体像

┌─────────────────────────────────────────────────────────────┐
│                      ユーザーの質問                          │
│  「冷却システムの問題点は?」「完了率は?」「田中の担当分は?」  │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    LLM(ルーター)                           │
│         質問の意図を分析し、適切なツールを選択                 │
└─────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ セマンティック検索 │ │   SQL Agent    │ │   その他ツール   │
│  (Azure AI Search │ │   (SQLite)     │ │                 │
│   / Pinecone等)   │ │                │ │                 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
        │                    │
        ▼                    ▼
  意味的に関連する      正確なフィルタリング
  ドキュメント取得      集計・統計計算

データの二重管理

同じデータを2つの形式で保持します:

  1. ベクトルDB: セマンティック検索用(埋め込みベクトル + メタデータ)
  2. RDB(SQLite等): 構造化クエリ用(正規化されたテーブル)
元データ(JSON/Excel等)
        │
        ├──→ ベクトルDB(Azure AI Search / Pinecone)
        │      - content: テキスト全文
        │      - embedding: ベクトル
        │      - metadata: 付随情報
        │
        └──→ SQLite
               - record_no: INT
               - assignee: TEXT
               - status: TEXT
               - deadline: DATE
               - ...

実装:LangChainのSQL Agent

LangChainには公式のSQLDatabaseToolkitcreate_sql_agentが用意されており、簡単にSQL Agentを構築できます。

公式のSQL Agentを使う方法

from langchain_community.utilities import SQLDatabase
from langchain_community.agent_toolkits import SQLDatabaseToolkit
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

# 1. データベース接続
db = SQLDatabase.from_uri("sqlite:///mydata.db")
print(f"利用可能なテーブル: {db.get_usable_table_names()}")

# 2. LLMの準備
llm = ChatOpenAI(model="gpt-4", temperature=0)

# 3. SQLToolkitの作成
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
tools = toolkit.get_tools()

# 4. エージェントの作成
system_prompt = """あなたはSQLデータベースと対話するエージェントです。
質問に対して、正しいSQLクエリを作成し、結果を確認して回答してください。
"""

agent = create_agent(llm, tools, system_prompt=system_prompt)

# 5. 実行
result = agent.invoke({"messages": [{"role": "user", "content": "完了率を教えて"}]})

SQLDatabaseToolkitが提供する4つのツール

ツール名 用途
sql_db_query SQLクエリを実行し、結果またはエラーを返す
sql_db_schema 指定テーブルのスキーマとサンプル行を取得
sql_db_list_tables 利用可能なテーブル一覧を表示
sql_db_query_checker 実行前にクエリの構文をチェック

エージェントの動作フロー:

  1. sql_db_list_tables でテーブル一覧を確認
  2. sql_db_schema で関連テーブルの構造を確認
  3. sql_db_query_checker でクエリを検証
  4. sql_db_query で実行

エラーが発生した場合、エラーメッセージがLLMに返され、自動的にクエリを修正して再実行します。

セキュリティ対策(重要)

⚠️ 警告: SQL Q&Aシステムの構築には、モデルが生成したSQLクエリを実行する必要があります。これには固有のリスクがあります。

公式ドキュメントの推奨事項:

  1. 権限を最小限に: Read-Only接続を使用
  2. テーブルを制限: 必要なテーブルのみアクセス許可
  3. Human-in-the-Loop: 重要なクエリは人間が承認
# Read-Only接続の例(SQLite)
uri = "sqlite:///file:mydata.db?mode=ro&uri=true"
db = SQLDatabase.from_uri(
    uri,
    include_tables=["allowed_table"],  # 許可テーブルを制限
    sample_rows_in_table_info=3,
)

Human-in-the-Loop(人間による承認)

LangChain 2025では、クエリ実行前に人間の承認を求める機能が組み込まれています。

from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(
    llm, tools, system_prompt=system_prompt,
    middleware=[HumanInTheLoopMiddleware(
        interrupt_on={"sql_db_query": True}  # クエリ実行時に一時停止
    )],
    checkpointer=InMemorySaver()
)

SQL生成用プロンプトの例

prompt = ChatPromptTemplate.from_messages([
    ("system", """あなたはSQLの専門家です。

## ルール
1. SELECT文のみ生成
2. SQLのみ返答(説明不要)

## テーブル情報
{table_info}

## クエリ例

完了率:
SELECT
  COUNT(*) as total,
  SUM(CASE WHEN status = '完了' THEN 1 ELSE 0 END) as completed,
  ROUND(SUM(CASE WHEN status = '完了' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) as rate
FROM records

担当者別件数:
SELECT assignee, COUNT(*) as count
FROM records GROUP BY assignee ORDER BY count DESC
"""),
    ("human", "{question}")
])

---

## LLMによるツール選択の実装
### ツールの定義

```python
from langchain_core.tools import tool

@tool
async def semantic_search(query: str, top_k: int = 5) -> str:
    """意味的に関連するドキュメントを検索します。

    「〜について」「〜に関する」「〜の問題点」などの質問に使用。
    """
    # ベクトル検索の実装
    results = await vector_store.search(query, top_k)
    return format_results(results)

@tool
async def sql_query(question: str) -> str:
    """データの集計・フィルタリング・統計を取得します。

    「何件?」「完了率は?」「担当者が〜」「一覧」などの質問に使用。
    """
    result = await sql_agent.execute(question)
    return json.dumps(result, ensure_ascii=False)

システムプロンプトで使い分けを指示

ポイント:LLMが適切なツールを選べるよう、判断基準を明示します。

## ツール選択の判断基準

| ユーザーの意図 | 使うツール | キーワード例 |
|--------------|-----------|-------------|
| 意味的に関連する情報 | semantic_search | 「〜について」「〜に関する」 |
| 件数・統計 | sql_query | 「何件」「完了率」「平均」 |
| 条件フィルタ | sql_query | 「担当者が〜」「ステータスが〜」 |
| 一覧取得 | sql_query | 「〜の一覧」「すべての〜」 |

### 具体例

✅ semantic_search を使う:
- 「冷却システムの問題点は?」→ 意味的に関連する情報を取得

✅ sql_query を使う:
- 「担当者が田中のタスク」→ WHERE assignee = '田中'
- 「完了率は?」→ COUNT + CASE WHEN
- 「来週期限のものは?」→ WHERE deadline <= '2025-01-17'

実際のクエリ例と結果

例1: セマンティック検索が適切なケース

ユーザー: 「バッテリー関連のリスクについて教えて」

→ semantic_search("バッテリー リスク")

結果:
- バッテリー劣化による航続距離低下のリスク
- 充電インフラ不足による利便性低下
- 電池廃棄時の環境負荷
(意味的に関連する情報を幅広く取得)

例2: SQLが適切なケース

ユーザー: 「担当者ごとの未完了件数を教えて」

→ sql_query("担当者ごとの未完了件数")

生成されたSQL:
SELECT assignee, COUNT(*) as count
FROM records
WHERE status != '完了'
GROUP BY assignee
ORDER BY count DESC

結果:
| assignee | count |
|----------|-------|
| 田中     | 12    |
| 佐藤     | 8     |
| 鈴木     | 5     |

例3: 組み合わせが必要なケース

ユーザー: 「田中さんの担当分の詳細を教えて」

→ 1. sql_query("担当者が田中のrecord_no一覧")
   → [1, 5, 12, 23, ...]

→ 2. semantic_search("record_no:1 OR record_no:5 OR ...")
   → 各レコードの詳細情報

メリットと考慮点

メリット

項目 セマンティック検索のみ + SQL Agent
意味検索
完全一致フィルタ △(不正確)
集計・統計
日付範囲検索
GROUP BY

考慮点

  1. データの二重管理: ベクトルDBとRDB両方にデータを投入・同期する必要がある
  2. スキーマ設計: SQLで検索したい項目は正規化してテーブル設計する
  3. セキュリティ: SQL Injection対策、Read-Only接続、クエリバリデーション

まとめ

セマンティック検索は強力ですが、万能ではありません。

  • 「〜について教えて」→ セマンティック検索
  • 「何件?」「完了率は?」「担当者が〜」→ SQL

LangChainのSQL Agentを組み合わせることで、RAGシステムの回答可能な範囲が大幅に広がります。

特に業務システムでは「統計」「集計」「正確なフィルタリング」の要求が多いため、この組み合わせは非常に効果的です。

ぜひ試してみてください。


参考

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です