🌊

AzureAISearchRetriever とAzureSearch().as_retriever() どちらを使うべきなのか

2024/11/27に公開

Azure でRAG を実施する場合、(cosomos DB 等も使えるが)Azure AI Search に参考情報を格納するのが手っ取り早いだろう。

LangChain で、Azure AI Search に格納した参考情報を取り出す(retriever)実装例を検索してみると、

  1. AzureAISearchRetriever() を使う
  2. AzureSearch().as_retriever() を使う

の2パターンあり、LangChain の公式ドキュメントを読んでも両者の違いがよく分からなかったので、調査してみた。

結論

  • フルテキスト検索がしたいならAzureAISearchRetriever()。ただし、現状セマンティックランカーは利用不可。
  • ベクトル検索・ハイブリッド検索がしたい、セマンティックランカーを利用したいならAzureSearch().as_retriever()

Azure AI Search の仕様

Azure AI Search で可能なクエリ検索方法は、Azure AI Search のドキュメントに拠れば、以下の3種類。

  • フルテキスト検索
  • ベクトル検索
  • ハイブリッド検索

ここではLangChain は単なるラッパーなので、AzureAISearchRetriever()AzureSearch().as_retriever() のどちらも、上記の3種類のいずれかの検索方法となるはず。

内部コード調査

AzureAISearchRetriever()

こちらは単純。ソースコードを見れば見当がつく。
要は検索用のURL を作って、そこへクエリを検索するようなGET API を叩いているわけである。

def _search(self, query: str) -> List[dict]:
    search_url = self._build_search_url(query)
    response = requests.get(search_url, headers=self._headers)
    if response.status_code != 200:
        raise Exception(f"Error in search request: {response}")

    return json.loads(response.text)["value"]

ここで実行されているAPI についてAzure のドキュメントで調べてみると、該当するのはDocuments - Search Get だろう。

このドキュメントのsearch の欄に、

フルテキスト検索クエリ式。すべてのドキュメントに一致するには、"*" を使用するか、このパラメーターを省略します。

と記載されているので、これはフルテキスト検索を実施するためのAPI のようだ。このAPI をラッパーしているAzureAISearchRetriever()は必然的に、フルテキスト検索しか出来ないことになる。

またこのAPI 自体はセマンティックランカー(semanticConfiguration)に対応しているが、AzureAISearchRetriever() が作る検索用のURL はセマンティックランカーを想定していない(search$top$filter のみ設定)ので、セマンティックランカーも利用不可である。

AzureSearch().as_retriever()

ドキュメントはこちら。返却されるのはAzureSearchVectorStoreRetriever なので先程とは別物。

fetch_kfilter の他に、search_type を設定できる。AzureSearch のドキュメントに拠れば、"similarity" / "hybrid" / "semantic_hybrid" の3通りに対応している。

  • AzureSearchVectorStoreRetrieverドキュメントに拠れば、"similarity_score_threshold" / "hybrid_score_threshold" / "semantic_hybrid_score_threshold" も設定可能のはずで、なぜこれらを含めていないのかは謎。
  • search_type のデフォルト値は“similarity” と記載されているが、langchain-community v0.3.8 では特に処理が実装されていないため、勘違いでなければsearch_type を設定しない場合はAzureSearchVectorStoreRetriever 側のデフォルト値である"hybrid" になる。修正PR は送信済。

Azure AI Search の仕様と突き合わせると、"similarity" が「ベクトル検索」、"hybrid" が「ハイブリッド検索」に対応しているはずである。(一応裏が取りたくてコード追いかけたが、Azure SDK の領域で断念...)

おまけ

備忘録も兼ねて、検証に使ったプログラムを残しておく。

データベース(インデックス)定義

インデックスが存在しなくても(Azure のGUI で空インデックスを作成しなくても)、自動で作成される。

from langchain_openai import AzureOpenAIEmbeddings
from langchain_community.vectorstores import AzureSearch

index_name = "test-index"

embeddings = AzureOpenAIEmbeddings(
    azure_deployment=os.getenv("AZURE_OPENAI_EMBEDDING_MODEL"),
    openai_api_key=os.getenv("AZURE_OPENAI_API_KEY"),
    openai_api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
    azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT")
)

vector_store = AzureSearch(
    embedding_function=embeddings.embed_query,
    azure_search_endpoint=os.getenv("AZURE_SEARCH_SERVICE_ENDPOINT"),
    azure_search_key=os.getenv("AZURE_SEARCH_ADMIN_KEY"),
    index_name=index_name
)

ドキュメント格納

vector_store.add_texts(
    [
        "In 2020, I visited Beijing",
        "In 2021, I visited New Orleans",
        "In 2022, I visited New York",
        "In 2023, I visited Paris",
        "In 2024, I visited Tokyo",
    ]
)

フルテキスト検索

from langchain_community.retrievers import AzureAISearchRetriever

retriever = AzureAISearchRetriever(
    content_key="content", 
    service_name=os.getenv("AZURE_SEARCH_SERVICE_ENDPOINT"),
    api_key=os.getenv("AZURE_SEARCH_ADMIN_KEY"),
    index_name=index_name
)

retriever.invoke("When did I visited Asia?")

ベクトル検索

retriever = vector_store.as_retriever(search_type="similarity")
retriever.invoke("When did I visited Asia?")

ハイブリッド検索

retriever = vector_store.as_retriever(search_type="hybrid")
retriever.invoke("When did I visited Asia?")

セマンティックランカー利用

Azure のドキュメントに従って、セマンティックランカー("test-semantic")を作成しておくこと。

vector_store = AzureSearch(
    embedding_function=embeddings.embed_query,
    azure_search_endpoint=os.getenv("AZURE_SEARCH_SERVICE_ENDPOINT"),
    azure_search_key=os.getenv("AZURE_SEARCH_ADMIN_KEY"),
    index_name=index_name,
    semantic_configuration_name="test-semantic",
)

retriever = vector_store.as_retriever(search_type="semantic_hybrid")
retriever.invoke("When did I visited Asia?")

Discussion