Azure AI Searchに使用するためにシノニムマップを作成する

# はじめに
こんにちは。masa-asaです。本日はAuzre AI Searchでシノニムマップを使うまでについて記事にしたいと思います。

# Azure AI Search
Azure AI Searchとは、RAGを行うために利用できるAzureの検索サービスの1つです。
AI Searchではユーザーが用意したドキュメントに対してベクトルインデックスを作成しLLMと連携することが可能です。

# シノニムマップ
シノニムマップとは、類義語の辞書のようなものです。json形式の一定のフォーマットに従って記述されます。
Azure AI Searchでは、solr形式で記述されたシノニムマップを用います。以下がシノニムマップの例です。
Azureのドキュメントから引用しています。


例えば、「USA」という単語の検索は類義語として「United States」や「United States of America」にも拡張される、ということになります。
```json
{
"format": "solr",
"synonyms": "
    USA, United States, United States of America\n
    dog, puppy, canine\n
    coffee, latte, cup of joe, java\n"
}
```

# シノニムマップをAzureで用いる
2024年12月現在、シノニムマップをAzure PortalのAI Search画面で操作して適用することはできません。
azure cliや作成したコードを用いる必要があります。

### シノニムマップをAzure上にアップロードする
まず、作成したシノニムマップをAzure上にアップロードする必要があります。
コードについては、巨人の肩の上に立ちGitHubで公開されているサンプルのコードを用います。
シノニムマップはAzure AI Searchに関連付けられます。そのため、シノニムマップAzureにアップロードするためにはAzure AI SearchのエンドポイントURLとAPI Keyが必要になります。

※Azure AI Searchの設定でAPI keyでのアクセスを許可している場合です。ロールベースでアクセスの許可をしている場合は、ユーザーのIDやシステム割り当てマネージドIDでのアクセスが必要になります。

今回で重要な部分は`create_synonym_map`関数です。サンプルではコード内で`solr_format_synonyms`変数にべた書きされていますが、実際には作成したルールセットを格納することになるかと思います。
以下例のように、シノニムマップ名、シノニムマップのルールセットを引数にとり`SearchIndexClient`の`create_synonym_map`メソッドに作成したシノニムマップを引数として渡して実行してあげることでAzure AI Search上にシノニムマップをアップロードすることができます。
`synonym_map = SynonymMap(name="test-syn-map", synonyms=solr_format_synonyms)` 

```python

import os

service_endpoint = os.getenv("AZURE_SEARCH_SERVICE_ENDPOINT")
key = os.getenv("AZURE_SEARCH_API_KEY")

from azure.core.credentials import AzureKeyCredential
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents.indexes.models import SynonymMap

client = SearchIndexClient(service_endpoint, AzureKeyCredential(key))

def create_synonym_map():
    # [START create_synonym_map]
    solr_format_synonyms = "\n".join([
        "USA, United States, United States of America",
        "Washington, Wash. => WA",
    ])
    synonym_map = SynonymMap(name="test-syn-map", synonyms=solr_format_synonyms)
    result = client.create_synonym_map(synonym_map)
    print("Create new Synonym Map 'test-syn-map succeeded")
    # [END create_synonym_map]

～～～～～～～～～～～～～中略～～～～～～～～～～～～～

if __name__ == '__main__':
    create_synonym_map()
```

実行結果を見るため、Postmanでシノニムマップをgetする以下のAPIを実行してみます。
apiを実行するには、headerに「api-key」というキーでAI SearchのAPI Keyを指定します。
`https://{AI Searchの名前}.search.windows.net/synonymmaps?api-version=2024-07-01`
実行結果を見ると「sample-map」という名前のシノニムマップが見つかりました。
これでシノニムマップの登録ができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942439/6d44930a-15c5-75c0-f119-87073954631d.png)


### シノニムマップをインデックスに適用する
Azureにアップロードしたシノニムマップを実際に用いるためには、シノニムマップをAI Searchのインデックスに適用する必要があります。
そのために必要な作業は、インデックスの定義フォーマット（json）を編集して、シノニムマップに関する情報を追加することです。

こちらも、コードがGitHubで公開されているサンプルを用います。
今回は、既存のインデックスに対してシノニムマップに関する部分だけを更新する場合を想定するため、
コードを一部修正しています。

`get_index()`関数で、既存の更新したいインデックスの構造を取得してきます。
その後、`update_index()`関数に取得したインデックスのオブジェクトと適用したいシノニムマップ名を
指定して実行しています。
シノニムマップはインデックスのフィールドに対して指定するので、ターゲットとなるフィールドを指定し、そのフィールドに対してシノニムマップをアタッチするというイメージです。
なお、`searchable`属性が`true`のフィールドにしかシノニムマップは適用できませんので、ご注意ください。

```python

～～～～～～～～～～～～～中略～～～～～～～～～～～～～

def get_index(index_name: str) -> SearchIndex:
    # [START get_index]
    name = index_name
    return client.get_index(name)
    # [END get_index]


def update_index(index_df: SearchIndex, synonym_map_name: str) -> None:
    # [START update_index]
    index = index_df

    for field in index.fields:
        if field.name == "chunk":
            field.synonym_map_names = [synonym_map_name]

    client.create_or_update_index(index=index)

    # [END update_index]


～～～～～～～～～～～～～中略～～～～～～～～～～～～～

if __name__ == "__main__":
    synonym_map_name = sys.argv[1]
    index_name = sys.argv[2]

    index_df = get_index(index_name)
    update_index(index_df, synonym_map_name, operation)

```

正しくインデックスの更新ができると、以下のように`synonymMaps`のフィールドに指定したシノニムマップが追加されていることがわかります。また、`synonymMaps`は配列形式になっていますが、フィールドに含めることのできるシノニムマップは現状1つのみであることにご注意ください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942439/7bfb1cd9-89e5-d991-1fbe-ae616de3e1f4.png)

# まとめ
- 現状シノニムマップはGUIから操作できず、CLIで操作する
- AI Searchの検索でシノニムマップを使うためには、Azure AI Searchへのシノニムマップのアップロードとインデックスへのアタッチという2ステップが必要
- `searchable`が`true`のフィールドにしかシノニムマップはアタッチできない
- フィールドに含めることのできるシノニムマップは現状1つのみ
- 1つのシノニムマップに含むことのできる規則は最大で20,000個
- 1つの規則で関連付けられる類義語は最大20個

ありがとうございました！

# おまけ
RAGの際に、専門用語や社内用語に強くするための手法として、「Golden-Retriever」という手法があります。
簡単に説明すると、専門用語のDBを事前に構築しておき、質問文に専門用語があればそれを抽出し、専門用語DBからその意味を検索し明確にしたうえで質問文（専門用語あり）の文脈をLLMに分析してRAGを行う方法です。

シノニムマップに比べて複雑ですが、専門用語の意味や質問文の文脈も理解したうえでRAGが可能なため、高い精度が期待できるという利点が考えられます。

https://arxiv.org/html/2408.00798v1


https://zenn.dev/knowledgesense/articles/90ac35eedf8b7c

# 参考

https://learn.microsoft.com/ja-jp/azure/search/search-what-is-azure-search

https://learn.microsoft.com/ja-jp/azure/search/search-synonyms?tabs=rest%2Crest-assign

https://learn.microsoft.com/ja-jp/azure/search/search-limits-quotas-capacity#synonym-limits

https://github.com/Azure/azure-sdk-for-python/blob/7cd31ac01fed9c790cec71de438af9c45cb45821/sdk/search/azure-search-documents/samples/sample_index_crud_operations.py

https://github.com/Azure/azure-sdk-for-python/blob/7cd31ac01fed9c790cec71de438af9c45cb45821/sdk/search/azure-search-documents/samples/sample_synonym_map_operations.py