Azure AI Searchを使ってベクトル検索を実行してみた（Python）

## はじめに
この記事は株式会社ナレッジコミュニケーションが運営する [クラウド AI by ナレコム Advent Calendar 2023](https://qiita.com/advent-calendar/2023/knowcom-cloudai) の6日目にあたる記事になります！

Azure AI Searchを使って、Pythonでベクトル検索を実行してみました。Azure AI Searchを使ったベクトル検索を試したい方におすすめです。

## 開発環境
- Azure サブスクリプション
- Azure AI Search
- Python 3.11

## 実装
### Azure AI Searchリソースの作成
[Azure Portal](https://portal.azure.com/)から「AI Search」のページに移動し、「作成」を選択します。

入力項目を埋め、「確認および作成」を選択します。

![記事投稿用 (11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/041ef491-dd40-5b63-977b-1dd9727cafdd.png)
　　
検証が完了したら、「作成」を選択します。

![記事投稿用 (12).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/f728de4c-ad08-33cb-0215-12f6594564f6.png)

　　
デプロイが完了したら、「リソースに移動」を選択します。

![記事投稿用 (13).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/5970588c-4d01-d453-415f-5422a04f42d7.png)

　　
「設定」の「キー」タブを選択し、プライマリー管理者キーをメモします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/beaa00f9-89c6-f4b3-7571-a9da57d6fbd2.png)

　　
### ライブラリのインポート、環境変数、リクエストヘッダーの設定
ここからはPythonファイルに記述していきます。まず、必要なライブラリをインポートし、環境変数、リクエストヘッダーを設定します。サンプルデータを使用するため、index_nameは「hotels-vector-quickstart」に指定してください。

```test.py
import requests
import json
service_name = "<AI Searchのサービス名>"
index_name = "hotels-vector-quickstart"
api_key = "<AI Searchのプライマリー管理者APIキー>"

headers = {
    "Content-Type": "application/json",
    "api-key": api_key
}
```
　　
### インデックスの作成
[インデックスを作成する](https://learn.microsoft.com/ja-jp/azure/search/search-get-started-vector#create-an-index)を参考に、Pythonに書き換えた以下のコードでインデックスを作成します。
```test.py
url = f"https://{service_name}.search.windows.net/indexes/{index_name}?api-version=2023-11-01"

body = {
    "name": "hotels-vector-quickstart",
    "fields": [
        {
            "name": "HotelId", 
            "type": "Edm.String",
            "searchable": False, 
            "filterable": True, 
            "retrievable": True, 
            "sortable": False, 
            "facetable": False,
            "key": True
        },
        {
            "name": "HotelName", 
            "type": "Edm.String",
            "searchable": True, 
            "filterable": False, 
            "retrievable": True, 
            "sortable": True, 
            "facetable": False
        },
        {
            "name": "HotelNameVector",
            "type": "Collection(Edm.Single)",
            "searchable": True,
            "retrievable": True,
            "dimensions": 1536,
            "vectorSearchProfile": "my-vector-profile"
        },
        {
            "name": "Description", 
            "type": "Edm.String",
            "searchable": True, 
            "filterable": False, 
            "retrievable": True, 
            "sortable": False, 
            "facetable": False
        },
        {
            "name": "DescriptionVector",
            "type": "Collection(Edm.Single)",
            "searchable": True,
            "retrievable": True,
            "dimensions": 1536,
            "vectorSearchProfile": "my-vector-profile"
        },
        {
            "name": "Category", "type": "Edm.String",
            "searchable": True, 
            "filterable": True, 
            "retrievable": True, 
            "sortable": True, 
            "facetable": True
        },
        {
            "name": "Address", 
            "type": "Edm.ComplexType",
            "fields": [
                {
                    "name": "City", "type": "Edm.String",
                    "searchable": True, "filterable": True, "retrievable": True, "sortable": True, "facetable": True
                },
                {
                    "name": "StateProvince", "type": "Edm.String",
                    "searchable": True, "filterable": True, "retrievable": True, "sortable": True, "facetable": True
                }
            ]
        },
        {
            "name": "Location",
            "type": "Edm.GeographyPoint",
            "searchable": False, 
            "filterable": True, 
            "retrievable": True, 
            "sortable": True, 
            "facetable": False
        }
    ],
    "vectorSearch": {
        "algorithms": [
            {
                "name": "my-hnsw-vector-config-1",
                "kind": "hnsw",
                "hnswParameters": 
                {
                    "m": 4,
                    "efConstruction": 400,
                    "efSearch": 500,
                    "metric": "cosine"
                }
            },
            {
                "name": "my-hnsw-vector-config-2",
                "kind": "hnsw",
                "hnswParameters": 
                {
                    "m": 4,
                    "metric": "euclidean"
                }
            },
            {
                "name": "my-eknn-vector-config",
                "kind": "exhaustiveKnn",
                "exhaustiveKnnParameters": 
                {
                    "metric": "cosine"
                }
            }
        ],
        "profiles": [      
            {
                "name": "my-vector-profile",
                "algorithm": "my-hnsw-vector-config-1"
            }
        ]
    }
}

response = requests.put(url, headers=headers, json=body)

print(response.status_code)
print(response.text)
```
　　
ここまで終えたら、Pythonファイルを実行してください。
ステータスコード201が返され、以下のようにhotels-vector-quickstartが作成されれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/faf0dc92-a62c-61a9-2195-9a9515096a53.png)

次のステップへ進む前に、コードが重複して実行されるのを防ぐため、url、body、responseの各変数を削除するか、コメントアウトしてください。

　　
### ドキュメントのアップロード
[ドキュメントのアップロード](https://learn.microsoft.com/ja-jp/azure/search/search-get-started-vector#upload-documents)を参考に、Pythonに書き換えた以下のコードでドキュメントをアップロードします。

```test.py
url = f"https://{service_name}.search.windows.net/indexes/{index_name}/docs/index?api-version=2023-11-01"
body = {
    "value": [
        {
            "@search.action": "mergeOrUpload",
            "HotelId": "1",
            "HotelName": "Secret Point Motel",
            "HotelNameVector": [],
            "Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York.",
            "DescriptionVector": [],
            "Category": "Boutique"
        },
        {
            "@search.action": "mergeOrUpload",
            "HotelId": "2",
            "HotelName": "Twin Dome Hotel",
            "HotelNameVector": [],
            "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.",
            "DescriptionVector": [],
            "Category": "Boutique"
        },
        {
            "@search.action": "mergeOrUpload",
            "HotelId": "3",
            "HotelName": "Triple Landscape Hotel",
            "HotelNameVector": [],
            "Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
            "DescriptionVector": [],
            "Category": "Resort and Spa"
        },
        {
            "@search.action": "mergeOrUpload",
            "HotelId": "4",
            "HotelName": "Sublime Cliff Hotel",
            "HotelNameVector": [],
            "Description": "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.",
            "DescriptionVector": [],
            "Category": "Boutique"
        },
        {
            "@search.action": "mergeOrUpload",
            "HotelId": "13",
            "HotelName": "Historic Lion Resort",
            "HotelNameVector": [],
            "Description": "Unmatched Luxury. Visit our downtown hotel to indulge in luxury accommodations. Moments from the stadium, we feature the best in comfort",
            "DescriptionVector": [], 
            "Category": "Resort and Spa"
        },
        {
            "@search.action": "mergeOrUpload",
            "HotelId": "48",
            "HotelName": "Nordick's Hotel",
            "HotelNameVector": [],
            "Description": "Only 90 miles (about 2 hours) from the nation's capital and nearby most everything the historic valley has to offer. Hiking? Wine Tasting? Exploring the caverns? It's all nearby and we have specially priced packages to help make our B&B your home base for fun while visiting the valley.",
            "DescriptionVector": [],
            "Category": "Boutique"
        },
        {
            "@search.action": "mergeOrUpload",
            "HotelId": "49",
            "HotelName": "Old Carrabelle Hotel",
            "HotelNameVector": [],
            "Description": "Spacious rooms, glamorous suites and residences, rooftop pool, walking access to shopping, dining, entertainment and the city center.",
            "DescriptionVector": [],
            "Category": "Luxury"
        }
    ]
}

response = requests.post(url, headers=headers, json=body)

print(response.status_code)
print(response.text)
```
　　
ここまで終えたら、Pythonファイルを実行してください。
ステータスコード200が返され、以下のようにドキュメント数が増えていれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/77af75ee-5fa4-3bdf-d8be-a2c86cdaf178.png)

先ほどと同様、次のステップへ進む前に、コードが重複して実行されるのを防ぐため、url、body、responseの各変数を削除するか、コメントアウトしてください。

　　
### クエリの実行
[クエリを実行する](https://learn.microsoft.com/ja-jp/azure/search/search-get-started-vector#run-queries)を参考に、Pythonに書き換えた以下のコードでクエリを実行します。

```test.py
url = f"https://{service_name}.search.windows.net/indexes/{index_name}/docs/search?api-version=2023-11-01"

body = {
    "count": True,
    "search": "historic hotel walk to restaurants and shopping",
    "select": "HotelName, Description",
    "top": 3
}

response = requests.post(url, headers=headers, json=body)

print(response.status_code)
print(response.text)
```
`"count": True`は、検索結果の総数を取得するかどうかを指定します。
`"search": "historic hotel walk to restaurants and shopping"`は、検索クエリです。この例では、「歴史的なホテルで、レストランやショッピングエリアに徒歩で行ける」という条件を指定しています。
`"select": "HotelName, Description"`は、取得したいフィールドを指定します。ここではホテルの名前と説明を取得します。
`"top": 3`は、最上位の3つの結果のみを取得することを指定します。

　　
ここまで終えたら、Pythonファイルを実行してください。
ステータスコード200が返され、以下のように条件との関連度を示す@search.scoreの順に3つのホテルが検索結果として返ってくれば成功です。

>value":[{"@search.score":2.1902227,"HotelName":"Nordick's Hotel","Description":"Only 90 miles (about 2 hours) from the nation's capital and nearby most everything the historic valley has to offer. Hiking? Wine Tasting? Exploring the caverns? It's all nearby and we have specially priced packages to help make our B&B your home base for fun while visiting the valley."},{"@search.score":2.0014017,"HotelName":"Historic Lion Resort","Description":"Unmatched Luxury. Visit our downtown hotel to indulge in luxury accommodations. Moments from the stadium, we feature the best in comfort"},{"@search.score":1.3935266,"HotelName":"Sublime Cliff Hotel","Description":"Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace."}]


## おわりに
Azure AI Searchを使って、Pythonでベクトル検索を実行してみました。Azure AI Searchはクエリベースの検索ニーズに対して、柔軟かつ効率的な解決策を提供します。ぜひ、自分のプロジェクトで試してみてください。私ももっと深めていきたいと思います。

最後までお読みいただき、ありがとうございました！ 記事に関する質問等ございましたら、コメントもしくは以下のDMにてよろしくお願いします。

https://twitter.com/t___tatsuki

　　
===
ナレッジコミュニケーションでは「[Musubite](https://musubite-job.com/)」というエンジニア同士のカジュアルトークサービスを利用しています。
生成 AI 技術を使ったプロジェクトに携わるメンバーと直接話せるサービスですので興味がある方は是非利用を検討してください！

https://recruiting.musubite-job.com/features/company/knowledge-communication
 
今後もクラウド AI の最新情報を皆さんに届けていきたいと思います。お楽しみに。

## 参考文献
https://learn.microsoft.com/ja-jp/azure/search/search-get-started-vector