【Agno】 Agentic RAGで強化するF1データ分析  【Agno SQL F1 sample】

本記事では、**F1（Formula 1）データを対象にした「テキスト→SQL変換エージェント」**の動作概要や、エージェントが参照する**ナレッジベース（Knowledge Base）**の詳細を解説します。さらに、実際のStreamlitアプリでの実行結果ログも交えて、エージェントがどのようにSQLを生成・実行し、どのような回答を返すのかを示します。

<blockquote class="twitter-tweet" data-media-max-width="800"><p lang="ja" dir="ltr">📢 マルチモーダルなAIエージェントを構築するための軽量フレームワーク「Agno」で遊んでみる②<br>SQLエージェントを動かしてみた！！！<br>これはマジで面白い！！！<br>やはりエージェント系はシミュレーションゲーム！！ <a href="https://t.co/8U8j9EZRCd">https://t.co/8U8j9EZRCd</a> <a href="https://t.co/9A3qzGdct2">pic.twitter.com/9A3qzGdct2</a></p>&mdash; Maki@Sunwood AI Labs. (@hAru_mAki_ch) <a href="https://twitter.com/hAru_mAki_ch/status/1887169470118134064?ref_src=twsrc%5Etfw">February 5, 2025</a></blockquote> 

## プロジェクト概要

このサンプルプロジェクトの目的は、**自然言語での質問を自動的にSQLクエリへ変換し、F1データ（1950年～2020年）に対して実行→結果を返す**というエージェントを構築することです。  
具体的には以下のような特徴があります。

1. **F1の歴史的データ**  
   1950年～2020年までのレース結果やドライバー情報、チーム（コンストラクターズ）情報、最速ラップなどを一括管理。

2. **Agentic RAG**  
   エージェントが**自身でナレッジベースに問い合わせ**、テーブルのメタデータやサンプルクエリを参照する仕組み。より正確なSQLクエリを生成できるようになります。

3. **Streamlit + Docker Compose**  
   - StreamlitによるUI（チャット形式のインターフェース）。  
   - Docker Composeで`pgvector`(PostgreSQL拡張)とアプリが同時起動。  
   - 簡単にローカル環境で試せます。

## ディレクトリ構成と主なファイル

リポジトリのディレクトリ構成はざっくり以下の通りです。

```
├── agents.py                <-- エージェントの定義・設定
├── app.py                   <-- Streamlitアプリ（UI部）
├── docker-compose.yml       <-- DBとアプリの起動設定
├── Dockerfile               <-- コンテナのビルド設定
├── load_f1_data.py          <-- CSVをPostgreSQLにロード
├── load_knowledge.py        <-- ナレッジベースをベクトルDBにロード
├── knowledge/               <-- テーブルメタデータやサンプルSQL, JSONファイル
└── utils.py                 <-- Streamlit用ユーティリティ等
```

### 2.1 `agents.py`
- Agnoの `Agent` クラスを継承し、「SQLエージェント」を実装。
- LLM（OpenAI GPT-4など）を選択し、**SQLツール**・**ファイル操作ツール**を追加。
- ナレッジベース（後述）やセッション管理(PostgresAgentStorage)を設定し、エージェントの「振る舞いのルール」(instructions)を詳細に記述。

### 2.2 `app.py`
- Streamlitでチャット形式のUIを作成。
- `agents.py` のエージェントを呼び出し、ユーザーからの質問を受け取り、エージェントが生成する回答やSQLをリアルタイム表示。

### 2.3 `load_f1_data.py` / `load_knowledge.py`
- `load_f1_data.py`: F1データ(CSV形式)をS3からダウンロード → pandas で読み込み → PostgreSQLにテーブルとして保存。
- `load_knowledge.py`: `knowledge/` ディレクトリのファイル（テーブルJSONやサンプルSQL）をベクトルDB(pgvector)に取り込み、エージェントの**検索用ナレッジベース**を構築。

### 2.4 `knowledge/`
- テーブル構造を記したJSONファイル (例: `drivers_championship.json`、`race_wins.json` など)
- サンプルクエリが書かれたSQLファイル (例: `sample_queries.sql`)  
- このフォルダに入っている情報が**エージェントの問い合わせ元**となる。

## ナレッジベースの詳細

本プロジェクトが特に特徴的なのは、「Agentic RAGを活用し、エージェントがナレッジベースを**随時検索**して適切なSQLを組み立てる」という点です。

### 3.1 ナレッジベースの構成

1. **テーブルのメタデータ (JSON)**  
   - 各テーブルのカラム名・型・意味を記載。  
   - 例: `race_wins.json`  
     ```json
     {
       "table_name": "race_wins",
       "table_description": "...",
       "table_columns": [
         { "name": "index", "type": "int", ... },
         { "name": "venue", "type": "text", ... },
         ...
       ]
     }
     ```

2. **サンプルSQL (`sample_queries.sql`)**  
   - よくある分析例（「チャンピオンが何回優勝したか」「コンストラクターの順位とレース勝利数の比較」など）をクエリ例として記載。

3. **補足情報（テキストベース）**  
   - たとえば「`date` 列は `DD Mon YYYY` フォーマットなので `TO_DATE` 関数を使うと良い」といった注意事項。

### 3.2 読み込みと活用フロー

1. **CombinedKnowledgeBase**  
   `TextKnowledgeBase` と `JSONKnowledgeBase` をまとめた**複合KB**で、ファイル内容を全て取り込みます。  

2. **PgVector**  
   取り込んだテキストをベクトルとして変換し、**pgvector**テーブルに格納。  
   - ユーザー質問の意図に合うテーブルやサンプルクエリをベクトル検索で素早く見つけ出す。

3. **エージェントのツール呼び出し**  
   - **`search_knowledge_base(table_name)`**: エージェントが、自分でこのツールを呼び出し、対象テーブルやルールを検索。  
   - **`describe_table(table_name)`**: データベース側の実テーブル定義を確認。

4. **サンプルクエリを参考にSQL組み立て**  
   - 例: `date` 列から年を抽出する場合 → `EXTRACT(YEAR FROM TO_DATE(date, 'DD Mon YYYY'))` を流用。

## 実行手順

### 4.1 環境変数の設定
`.env` ファイル内にOpenAIのAPIキーなどを記載します（例: `OPENAI_API_KEY="sk-..."`）。

### 4.2 Docker Composeで起動
```bash
docker compose up -d
```
- 初回起動時、`load_f1_data.py` や `load_knowledge.py` が自動実行され、F1データとナレッジベースがDBに取り込まれます。
- 数分かかる場合があるので完了を待ちます。

### 4.3 アプリにアクセス
ブラウザで `http://localhost:8509` を開くと、StreamlitチャットUIが表示されます。  
自然言語で質問すると、エージェントが**最適なSQLを組み立てて実行→結果を返す**フローを体験できます。

### 4.4 停止
```bash
docker compose down
```
永続化ボリュームを含め完全削除したい場合は `docker compose down -v` を使います。

## 実際の処理ログ（一例）

以下は、Streamlitアプリ上で行われたユーザーとのやりとりの抜粋です。  
（ログには「どのテーブルをdescribeしたか」「実行したSQL」「結果」が表示されています。）

### 5.1 テーブル一覧を問い合わせる

```
利用可能なテーブルを教えてください
```

**エージェントの回答例**:
```
以下のテーブルが利用可能です：
constructors_championship ...
drivers_championship ...
fastest_laps ...
race_results ...
race_wins ...
```

### 5.2 各テーブルのカラム詳細を取得

```
これらのテーブルについて詳しく説明してください
```

エージェントは `search_knowledge_base` および `describe_table` を呼び出し、メタデータをまとめて表示します。  
ログのツール呼び出し例:
```
🛠️ Describe Table
Arguments:
{
  "table_name":"drivers_championship"
}
Results:
[ "index: BIGINT()", "year: BIGINT()", "position: TEXT()", ... ]
```

### 5.3 「最も多くレースに勝ったドライバーは？」

```
最も多くのレースに優勝したドライバーは誰ですか？
```

エージェントが自動生成したSQLの例（ログより）:

```sql
SELECT name, COUNT(*) AS win_count
FROM race_wins
GROUP BY name
ORDER BY win_count DESC
LIMIT 1
```

**結果**:  
「ルイス・ハミルトン」 95回の優勝

### 5.4 「年間のレース数の推移を表示して」

`race_wins` テーブルには「year」列がないため、date列から年を抽出する必要があると判断。  
エージェントはサンプルSQLを検索して `TO_DATE(date, 'DD Mon YYYY')` を活用し、年ごとのレース数をカウント。  
結果として、1950年に7レース、2019年に21レースなど、年ごとの増減が一覧表示されます。

### 5.5 「2010年以降、最も多くのレースに勝利したドライバーとチームの順位は？」

以下のように、**driversチャンピオンシップ**と**race_wins**を結合しながら、さらに**constructors_championship**の順位を取得する複雑なクエリを作成。ログにはツール呼び出し(knowledge base検索やSQL実行)が順次記録されています。

**最終表示例**:

| 年   | ドライバー         | チーム                         | 勝利数 | チーム順位 |
|:---:|:-------------------|:-------------------------------|:-----:|:---------:|
| 2010 | Fernando Alonso   | Ferrari                       | 5      | 2         |
| 2011 | Sebastian Vettel | Red Bull Racing Renault       | 11     | 1         |
| 2012 | Sebastian Vettel | Red Bull Racing Renault       | 5      | 1         |
| 2013 | Sebastian Vettel | Red Bull Racing Renault       | 13     | 1         |
| 2014 | Lewis Hamilton   | Mercedes                      | 11     | 1         |
| 2015 | Lewis Hamilton   | Mercedes                      | 10     | 1         |
| 2016 | Lewis Hamilton   | Mercedes                      | 10     | 2         |
| 2017 | Lewis Hamilton   | Mercedes                      | 9      | 1         |
| 2018 | Lewis Hamilton   | Mercedes                      | 11     | 1         |
| 2019 | Lewis Hamilton   | Mercedes                      | 11     | 1         |
| 2020 | Lewis Hamilton   | Mercedes                      | 11     | 1         |

## まとめと応用

1. **ナレッジベース**  
   - テーブルのメタデータ・サンプルSQL・日付フォーマット注意点などを格納し、エージェントは質問に応じて必要な情報を検索・取得。  
   - これにより、ユーザーが「年ごとの推移が見たい」と要望したとき、自動的に適切なクエリを組み立てられます。

2. **エージェントの流れ**  
   1. **ユーザー質問を受け取り**、  
   2. **ナレッジベースからテーブル定義・ルールを参照**、  
   3. **クエリを生成しSQLを実行**、  
   4. **結果を整形して返す**。  

3. **拡張・応用**  
   - F1以外のデータも、**同じ形式**（JSONでテーブル情報、サンプルSQL）でナレッジベースを整備すれば、同じ仕組みで動かせます。  
   - 質問例やサンプルクエリが豊富になるほど、エージェントの回答精度は高まります。

## おわりに

本記事では、Docker Composeで動く**SQLエージェント**が**Agentic RAG**を通じてナレッジベースから情報を取得し、F1データを自由に分析できるしくみをまとめました。  
実際のログにある通り、ユーザーは自然言語で「最も多く優勝したドライバーは？」「2010年以降で一番勝ったドライバーは？」などと尋ねるだけで、エージェントが適切なクエリを組み立てて結果を返します。

もし本プロジェクトを試してみたい場合は、手元でリポジトリを**`git clone`**し、`.env`に自分のAPIキーをセット → `docker compose up -d` すればすぐにF1データの分析を始められます。ぜひご活用ください。

## リポジトリ

https://github.com/Sunwood-ai-labs/agno-sql-f1-sample

<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>