【LangChain】Azure AI Searchを使ったカスタムRetrieverでRAGを自作する

# 【LangChain】Azure AI Searchを使ったカスタムRetrieverでRAGを自作する

## 3行まとめ

- LangChainでAI Searchを使ったRAGやりたい
- AI Search用のRetrieverはあるけど、セマンティック検索とかもできるようにしたい
- BaseRetrieverを使ってカスタマイズしてみる

## AI Search用のRetrieverは既にあるけど

- セマンティック検索とかができない
- 弄れないパラメータも多い

## やりたいこと

- retrieverを自作し、AI Searchはazureライブラリで使う
- retrieverに、queryと一緒にパラメータも投げられるようにする
  - パラメータのイメージ：

    ```python
    inputs = {
        "query": "<query>", # ここにqueryを入れる
        "params": {
            "index_name": "<index_name>", # 検索するインデックス
            "search_key": "<search_key>", # 検索対象のキー
            "metadata_keys": [key1, key2], # metadataとして取得したいキーのリスト
            "top_k": n, # 何件検索結果を出すか
        }
    }
    ```

## ソースコード

### AI Searchを使ったRetriever

```python:ai_search_retriever.py
from langchain.schema import BaseRetriever, Document
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from azure.search.documents.models import VectorizedQuery

class AISearchRetriever(BaseRetriever):
    """
    AzureAISearchRetriever
    """
    search_endpoint = "<your-ai-search-endpoint>"
    api_key = "<your-ai-search-api-key>"
    api_version = "<your-desired-api-version>"
    def _get_relevant_documents(self, inputs):
        documents = []
        query = inputs["query"]
        vector_query = model.encode(query)
        params = inputs["params"]
        search_key = params["search_key"]
        metadata_keys = params["metadata_keys"]
        select_keys = metadata_keys + [search_key]
        search_credential = AzureKeyCredential(self.api_key)
        search_client = SearchClient(
        endpoint=self.search_endpoint,
        index_name=params["index_name"],
        credential=search_credential,
        )
        results =  search_client.search(
            query_type="semantic",
            search_text=query,
            select=select_keys,
            vector_queries=[VectorizedQuery(
                vector=vector_query,
                k_nearest_neighbors=3, 
                fields=params["vector_key"],
            )],
            semantic_configuration_name="my-semantic-config",
            query_caption="extractive",
            top=params["top_k"],
            include_total_count=True,
        )
        for data in results:
            page_content = data.get(search_key)
            metadata = {key: value for key, value in data.items() if key in metadata_keys}
            doc = Document(page_content=page_content, metadata=metadata)
            documents.append(doc)
        return documents
```

### AI Search Retrieverを使ったシンプルRAG

```python:rag_with_ai_search.py
from langchain_openai import AzureChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

from ai_search_retriever import AISearchRetriever

import os
openai_endpoint = os.getenv("OPENAI_ENDPOINT")
openai_api_key = os.getenv("OPENAI_API_KEY")

ai_search_endpoint = os.getenv("AI_SEARCH_ENDPOINT")
ai_search_api_key = os.getenv("AI_SEARCH_API_KEY")

prompt_str = """
なんか入れる
{context}
"""

llm = AzureChatOpenAI(
    deployment_name="<deployment-name>",
    api_key=openai_api_key,
    azure_endpoint=openai_endpoint,
    openai_api_version="<your-desired-api-version>"
)

retriever = AISearchRetriever(
    search_endpoint="<your-ai-search-endpoint>",
    api_key="<your-ai-search-api-key>",
    api_version="<your-desired-api-version>"
)

prompt = ChatPromptTemplate.from_template(prompt_str)

rag_chain = {"context": retriever} | prompt | llm

inputs = {
    "query": "<query>",
    "params": {
        "index_name": "<index_name>",
        "search_key": "<search_key>",
        "metadata_keys": "<metadata_keys>",
        "top_k": "top_k",
    }
}

response = rag_chain.invoke(inputs)

print(response.content)

```

## 得られた知見

- retrieverに入れる`input`って辞書でも(リストでも)よかったことを知らなかった
- BaseRetrieverを継承すればめちゃくちゃ自由にretrieverが作れる
  - 今回はAI Searchをazureライブラリを使って動かしているが、API叩くのもアリ
  - AI Searchに限らず何でもretrieverに使えそう