Cosmos DBとStreamlitで作成するRAG FAQボット

## はじめに

FAQボットを作成のため、Cosmos DB をベクトルデータベースとして使用したRAGシステムを構築しました。備忘録としてその手順を説明します。

一般的に、Azure でRAGシステムを構築する際には、Azure AI Search をデータベースとして使用するケースが多いです。しかし、いくつかの使いづらい点があるため、今回はベクトル検索に対応したCosmos DB をRAGのデータベースとして使用する方法を紹介します。

### Cosmos DB を使用する動機

- **データの変更が容易**: Azure AI Search ではデータの変更が難しいですが、Cosmos DB ならGUIから直接ドキュメントを操作できるため、後から変更を加えやすいです。
- **検索精度の向上**: クエリ時にファイル名やカテゴリなどでフィルタリングすることで、明らかに違うドキュメントを返さないようにできます。AI Search でもフィルタリングは可能ですが、後からフィールドを追加する柔軟性に欠けます。

### 行うこと、行わないこと

- 行うこと
    - Cosmos DB のリソース作成~コンテナ作成まで
    - アプリからサンプルデータの登録
    - アプリから検索実行
- 行わないこと
    - Cosmos DB 以外のAzure リソース作成手順説明
    - コードの解説

## 必要なツールとサービス

| カテゴリ | ツール/サービス | 用途 |
| --- | --- | --- |
| Azure | Azure OpenAI | **GPT-4o-mini**や**text-embedding-3-large**などの高度な言語モデルを利用して、自然言語処理を行います。 |
| Azure | Cosmos DB | ベクトルデータベースとして使用し、データの保存と検索を行います。 |
| Python | Streamlit | アプリケーションのフロントエンドを簡単に作成します。 |
| Python | openai | OpenAIのAPIをPythonから利用するためのライブラリです。 |
| Python | python-dotenv | 環境変数を管理し、セキュアな設定を行います。 |
| Python | azure-cosmos | Azure Cosmos DBを操作し、データの管理とクエリを実行します。 |
| Python | tiktoken | ベクトル化する前のデータのトークン数を確認するために使用します。 |
- GPT-4o-mini と text-embedding-3-large を使用するので、事前に作成しておく必要があります。

## 手順

- Cosmos DB 作成
    - Azure CLI で作成と設定
- データの登録
    - フォームから登録
    - CSVから登録
- データ検索
    - アプリから検索

### Cosmos DB 作成

リソースの作成は、Azure CLI を使用します。
以下をすべて実行して、リソース作成まで5分程度で完了します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561386/cac1c1a6-e861-446c-a298-f783850148b4.png)

- Azure Portal の上部から、Cloud Shell を開きます。

変数の値を書き換え、下記コマンドを実行します。
```powershell
$resource_group_name = 'faq_rg'  # リソースグループの名前
$location = 'japaneast'  # リソースの場所（リージョン）
$account_name = 'faqcosmosdb'  # Cosmos DBアカウントの名前（小文字で44文字未満にする必要があります）

$database_name = 'faq_db'  # データベースの名前
$container_name = 'faq_container'  # コンテナの名前

```

リソース作成とベクトル検索有効化のコマンドを実行します。

```powershell
# リソースグループを作成
az group create `
--name $resource_group_name `
--location $location

# Cosmos DB アカウントを作成
az cosmosdb create `
--name $account_name `
--resource-group $resource_group_name `
--default-consistency-level Session `
--locations regionName='japaneast' failoverPriority=0 isZoneRedundant=False `
--capabilities EnableServerless

# NoSQL Vector Search 機能を有効にする
az cosmosdb update `
--resource-group $resource_group_name `
--name $account_name `
--capabilities EnableNoSQLVectorSearch
    
# データベースを作成
az cosmosdb sql database create `
--account-name $account_name `
--resource-group $resource_group_name `
--name $database_name
    
```

ベクトルとインデックス化のポリシーを設定したコンテナ作成のコマンドを実行します。

```powershell
$vector_embedding_policy = @"
{
    "vectorEmbeddings": [
        {
            "path": "/vector_content",
            "dataType": "float32",
            "distanceFunction": "cosine",
            "dimensions": 1536
        }
    ]
}
"@

$json_indexing_policy = @"
{
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [
        {
            "path": "/*"
        }
    ],
    "excludedPaths": [
        {
            "path": "/\"_etag\"/?"
        },
        {
            "path": "/vector_content/*"
        }
    ],
    "fullTextIndexes": [],
    "vectorIndexes": [
        {
            "path": "/vector_content",
            "type": "diskANN",
            "quantizationByteSize": 96,
            "indexingSearchListSize": 100
        }
    ]
}
"@

# コンテナ作成
az cosmosdb sql container create `
--account-name $account_name `
--resource-group $resource_group_name `
--database-name $database_name `
--name $container_name `
--partition-key-path '/id' `
--vector-embeddings $vector_embedding_policy `
--idx $json_indexing_policy
   
```

### アプリの実行

使用するコードは下記に置いてあります。

https://github.com/stabkey/cosmos_faq_rag

環境変数の設定します。

`.env` ファイルを作成し、下記を貼り付けます。

```bash
AZURE_OPENAI_ENDPOINT=https://your-openai-endpoint.azure.com/
AZURE_OPENAI_API_KEY=your-openai-api-key

COSMOSDB_ENDPOINT=https://your-cosmosdb-endpoint.documents.azure.com:443/
COSMOSDB_KEY=your-cosmosdb-key
COSMOSDB_DATABASE_NAME=your-database-name
```

各値を書き換える必要があります。詳細は以下から確認します。

<details>
<summary>Azure OpenAI 関連</summary>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561386/48d88427-5230-474f-b15b-c987923d43e2.png)

1. 対象のAzure OpenAI のリソースに移動します。  
2. 左のナビゲーションから「キーとエンドポイント」を選択します。  
3. `AZURE_OPENAI_API_KEY` はキーで、 `AZURE_OPENAI_ENDPOINT` はエンドポイントから取得します。   

</details>

<details>
<summary>Cosmos DB 関連</summary>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561386/3b79e9ce-305d-4e16-bdc9-53f2bc478566.png)

1. 作成したCosmos DB のリソースに移動します。  
2. 左のナビゲーションから「キー」を選択します。  
3. `COSMOSDB_ENDPOINT` は「URI」の値になります。  
4. `COSMOSDB_KEY` は「PRIMARY KEY」または、「SECONDARY KEY」の値になります。  
5. `COSMOSDB_DATABASE_NAME` は、作成したデータベースの名前になります。  
        - Cosmos DB のリソース名ではなく、データベースの名前です。  
</details>

Python の仮想環境の作成と有効化

```bash
python -m venv .vnenv
.venv\Scripts\activate
```

必要なライブラリインストール

```bash
python -m pip install --upgrade pip
pip install -r requiremnts.txt
```

アプリの実行

```bash
streamlit run app.py
```

実行するとブラウザから👇️のような画面が開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561386/6c03b437-a435-4fe2-a61c-baf1db8d0a3e.png)


### データの登録

2つの方法でCosmos DB にデータを登録します。

1. フォームから登録
2. CSVで登録

フォームに登録したい内容を入力することで、その内容をベクトル化してCosmos DB に保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561386/d10e6768-9af2-417f-8227-0588d1f9018e.png)

試しに以下の架空のデータを登録します。

```
質問:ドンピアバラ共和国の首都はどこですか？
回答:ドンピアバラ共和国の首都はバラシティです。
カテゴリ:地理
```

まとめてデータを登録したい場合は、CSVを作成して登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561386/b9d96579-0fde-4ffe-9b9d-daede8c28221.png)

試しに以下の架空のデータを登録します。

<details>
<summary>登録する架空のデータ</summary>

```
question,answer,category
下関瓦そばペンギンズのキャプテンは誰ですか？,下関瓦そばペンギンズのキャプテンはタケシ・イシカワです。,チーム情報
下関瓦そばペンギンズのホームスタジアムはどこですか？,下関瓦そばペンギンズのホームスタジアムはペンギンフィールドです。,チーム情報
下関瓦そばペンギンズのマスコットキャラクターは何ですか？,下関瓦そばペンギンズのマスコットキャラクターはペンちゃんです。,チーム情報
下関瓦そばペンギンズの設立年はいつですか？,下関瓦そばペンギンズの設立年は2005年です。,チーム情報
下関瓦そばペンギンズのチームカラーは何ですか？,下関瓦そばペンギンズのチームカラーは緑と白です。,チーム情報
下関瓦そばペンギンズのライバルチームはどこですか？,下関瓦そばペンギンズのライバルチームは広島カープスです。,チーム情報
下関瓦そばペンギンズの監督は誰ですか？,下関瓦そばペンギンズの監督はケイタ・ナカムラです。,チーム情報
下関瓦そばペンギンズの応援歌は何ですか？,下関瓦そばペンギンズの応援歌は「飛べ！ペンギンズ」です。,チーム情報
下関瓦そばペンギンズの年間試合数はどれくらいですか？,下関瓦そばペンギンズの年間試合数は約115試合です。,チーム情報
下関瓦そばペンギンズのファンクラブ名は何ですか？,下関瓦そばペンギンズのファンクラブ名は「ペンクラブ」です。,チーム情報
```
</details>

<details>
<summary>架空のデータ作成の際に使用したプロンプト</summary>

```markdown
## タスク

- 以下のサンプルを参考にして「下関瓦そばペンギンズ」という野球チームの架空のデータを作成してください。
- データは10件作成します。
- 実在する名称を使用するのは避けてください。
- カンマ区切りのCSVで、コードブロック内に出力してください。
    
## サンプル
    
|question|answer|category|
|---|---|---|
|<質問>|<回答>|<カテゴリ>|
|日本の首都はどこですか？|日本の首都は東京です。|地理|
```
</details>

登録したデータは、「データ表示」から見ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561386/6aa31a02-24dc-4a99-a47f-506993ac3caa.png)

### 検索する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561386/6a2448ae-60d1-4a9d-80ef-ab682cb01b06.png)

- 左のナビゲーションから「DBと会話」を選択します。
- 登録したデータに関する質問をするとちゃんと回答してくれます。
- 「検索結果」に参照したCosmos DB 内のデータを最大5件表示してくれます。

## 所感

- Cosmos DBの場合、AI Search と違いDBの利用が従量課金になるのが良いと感じた。
    - AI Search の場合、リソースを消さない限り課金される。
    - Cosmos DB はサーバーレスが選択でき、要求された数で料金が決まる。
    - Cosmos DBなら、個人や3~5人で使用する場合は、2000円/月も行かないはず。
- 後からフィールドを追加したい場合、Cosmos DB なら融通が効く
    - tagsやauthorなど追加したい場合、スキーマを変更することなく追加できる。
    - Cosmos DBにはスキーマがないから。
- フィルタリングしやすい
    - カテゴリなどでフィルタリングできる。
    - データ全体から探すよりも絞った中から探すことが可能になる。
    - フィルタリング自体は、AI Search でもできると思うが、個人的にCosmos DB のほうが楽。
- キーワードについて
    - 入力されたデータからキーワード作成する処理があるが、作成したキーワードは使用していない。今後のフィルタリングで使うかもしれないのでキーワードを作成している。
- データ変更した後に再度ベクトル化する仕組みが必要
    - 現状登録の時しかベクトル化されない。
    - これを、後から変更したりした時再度ベクトル化される仕組みが必要かもしれない。
    - そうすることで最新状態を維持できる。

## 参考

- [統合されたベクトル ストア - Azure Cosmos DB for NoSQL | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/cosmos-db/nosql/vector-search)
- [az cosmosdb sql container | Microsoft Learn](https://learn.microsoft.com/en-us/cli/azure/cosmosdb/sql/container?view=azure-cli-latest#az-cosmosdb-sql-container-update)