はじめに
Build 2023 にて Microsoft が発表した開発フレームワークである「Copilot stack」に従って、プラグインエコシステムを構築しましょう。
今回は Azure Cognitive Search に新たに追加されたベクトル検索とハイブリッド検索の機能を ChatGPT プラグインとして実装してローカル開発環境からテストしてみましょう。
プラグイン開発
ChatGPT 規格のプラグインを開発すれば、以下のようにブラウザ版 GPT-4 だけでなく、Bing や Microsoft 365 Copilot、Windows Copilot などからも利用可能になります。当然、組織独自の Copilot と統合することもできます。このプラグインによって、最新の情報や組織内のデータを反映させた(Grounding)回答を行ったり、ReAct などのタスクを実行させることが可能になります。
Azure Cognitive Search のベクトルインデックスを利用して Grounding するプラグインはすでに OpenAI 公式より、ChatGPT Retrieval Plugin が公開されていますし、Azure Cognitive Search も対応しましたので、さっそくこちらを見ていきましょう。
ChatGPT Retrieval Plugin
このプラグインは、OpenAI の text-embedding-ada-002
Embeddings モデルを使用してドキュメント チャンクの Embeddings を生成し、バックエンドのベクトル データベースを使用してそれらを保存およびクエリします。オープンソースの自己ホスト型ソリューションとして、開発者は独自の Retrieval プラグインをデプロイし、ChatGPT に登録できます。Azure Cognitive Search のデータソースプロバイダー は Microsoft の DE である Pablo Castro によってコミットされました。
プラグインは Python の FastAPI サーバー上で稼働し、ドキュメントの更新挿入、クエリ、削除を行うためのプラグインのエンドポイントを公開します。ユーザーは、ソース、日付、作成者、またはカスタム可能なメタデータ フィルターを使用して、検索結果を絞り込むことができます。
AI オーケストレーション
このプラグインの威力はアプリ利用だけに留まりません。Copilot stack における「AI オーケストレーター」からの呼び出しも可能になりますので、組織独自の Copilot から ReAct の Tools や Skills として組織内の様々なデータソース、API 化したアプリケーションと接続できるようになります。Build 2023 では Azure AI Studio と Azure Cognitive Search の連携も発表されていましたので、わざわざプラグイン開発しなくてもいい気がしますが、内部の理解のために実施します。
今回は AI オーケストレーターとして LangChain を利用してプラグインを呼び出しています。
ローカルデバッグ
OpenAI のモデルを利用したエンタープライズナレッジサーチのテストでは、ブラウザ版 GPT-4 を使用してプラグインをアタッチすると、データが OpenAI 社に行ってしまいます。そのため、Azure OpenAI Service を用いて仮想ネットワークを構築して、プライベートネットワーク経由でモデルを利用できるようにしてローカル環境に用意した LangChain からプラグインを呼ぶことにします。LangChain に追加されている ChatGPT Plugin を利用するための ChatGPTPluginRetriever を利用してローカルでデバッグします。
フロー解説
説明のため、コードはシンプル化しています。
- UI からユーザーの質問や指示が投げられます
- クエリは LangChain が受け取り、RetrievalQA Chain を用いて Retrieval Augmented Generation(RAG) を実施します。RetrievalQA は ChatGPTPluginRetriever を通じて ChatGPT Retrieval Plugin ローカルサーバーへクエリを投げます。
- ローカルサーバーは Embeddings API を呼んでクエリをベクトルに変換し、Azure Cognitive Search にベクトル検索を行います
- Azure Cognitive Search が検索結果を返却します
- ChatGPT Retrieval Plugin が整形を行って呼び出し元の LangChain に返却します
- RetrievalQA が ChatGPT を使って回答を生成し、UI に返却します
環境構築
Quick Start に従って構築するだけで簡単に実行できます。あらゆる設定が環境変数の定義だけで反映されるように作られており、非常にスマートです。
BEARER_TOKEN
はプラグインの呼び出し元を認証するためのトークンで、jwt.io などを使って任意に生成してセットします。このトークンを LangChain の ChatGPTPluginRetriever
で指定します。
export DATASTORE=azuresearch
export BEARER_TOKEN=<your_bearer_token>
export OPENAI_API_KEY=<your_openai_api_key>
export OPENAI_API_BASE=https://<AzureOpenAIName>.openai.azure.com/
export OPENAI_API_TYPE=azure
export OPENAI_EMBEDDINGMODEL_DEPLOYMENTID=<Name of text-embedding-ada-002 model deployment>
Azure Cognitive Search 接続定義
# Azure Cognitive Search
export AZURESEARCH_SERVICE=<your_search_service_name>
export AZURESEARCH_INDEX=<your_search_index_name>
export AZURESEARCH_API_KEY=<your_api_key> (optional, uses key-free managed identity if not set)
Azure Cognitive Search インデックス定義
Azure Cognitive Search のインデックス定義とのマッピングを行う。対応するマッピングが無い場合は未定義で OK。AZURESEARCH_FIELDS_SOURCE
だけは file
等にする必要がある。詳細な定義は azuresearch_datastore.py
を参照。
export AZURESEARCH_FIELDS_ID=<your_vectorindex_id_field>
export AZURESEARCH_FIELDS_TEXT=<your_vectorindex_content_field>
export AZURESEARCH_FIELDS_EMBEDDING=<your_vectorindex_vector_field>
export AZURESEARCH_FIELDS_SOURCE=file
export AZURESEARCH_FIELDS_SOURCE_ID=<your_vectorindex_filename_field>
Azure Cognitive Search 日本語アナライザー定義
日本語ドキュメントのキーワード検索やハイブリッド検索を実施する際には、content
フィールドの analyzer
に適切な日本語アナライザーを設定してください。クエリが自然言語に近い形になっていくと、ja.microsoft
では検索ノイズが増える可能性がありますので、ja.lucene
を推奨します。アナライザーの比較はこちらで解説しています。
REST で呼び出し
REST API のエンドポイント docs は、http://localhost:3333/docs#
にアクセスすれば閲覧できます。ドキュメント検索は /query
エンドポイントを使用し、Authorization ヘッダに Bearer Token をセットします。
POST http://localhost:3333/query
{
"queries": [{
"query": "三浦義澄って何した人",
"top_k": 3
}]
}
LangChain からの呼び出し
from langchain.retrievers import ChatGPTPluginRetriever
BEARER_TOKEN=<your_bearer_token>
retriever = ChatGPTPluginRetriever(url="http://localhost:3333", bearer_token=BEARER_TOKEN)
result = retriever.get_relevant_documents("三浦義澄って何した人")
print(result)
プラグインを直接呼び出すには上記コードを使います。実行すると以下のように生の検索結果が返却されます。
[Document(page_content='三浦 義澄(みうら よしずみ)は、平安時代末期、鎌倉時代初期の武将。鎌倉幕府の御家人。桓武平氏の流れを汲む三浦氏の一族で、三浦介義明の次男。十三人の合議制の一人。 経歴 相模国三浦郡矢部郷の出身。具体的な時期は不明ながら、上総常澄の加冠によって元服し義澄と名乗ったとされる[1][注釈 1]。 平治元年(1159年)の平治の乱では源義平に従うが、平家方に敗れて京都から郷里に落ち延びる。長寛2年(1164年)、兄・杉本義宗が亡くなり、それによって三浦氏の家督を継ぐ。', metadata={'id': '5LiJ5rWm576p5r6ELTAudHh0', 'metadata': {'source': 'file', 'source_id': '三浦義澄-0.txt', 'url': '', 'created_at': '', 'author': '', 'document_id': ''}, 'embedding': None, 'score': 0.8817653})]
LangChain から ChatGPT 質問応答
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
from langchain.retrievers import ChatGPTPluginRetriever
retriever = ChatGPTPluginRetriever(url="http://localhost:3333", bearer_token=BEARER_TOKEN)
llm = ChatOpenAI(model_name="gpt-35-turbo", temperature=0, model_kwargs={"deployment_id":"gpt-35-turbo"})
chain = RetrievalQA.from_chain_type(llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True)
answer = chain({'query': "三浦義澄って何した人"})
print(answer['result'])
ChatGPT(gpt-35-turbo) に回答を生成させると、以下のような出力が得られます。
三浦義澄は、平安時代末期から鎌倉時代初期にかけての武将で、鎌倉幕府の御家人でした。三浦氏の一族で、十三人の合議制の一人でもありました。彼は平治の乱に参加し、源義平に従いましたが、平家方に敗れて郷里に落ち延びました。その後、治承4年の以仁王の挙兵時に大番役として在京していた彼は、関東に下り、源頼朝の下に参上し、以仁王の挙兵の詳細を報告しました。彼は石橋山の戦いにも参加し、その後、父・義明が河越重頼や江戸重長に討ち取られたため、安房国へ渡りました。
課題 チャンク分割
return_source_documents
を True
にしているので、print(answer['source_documents'])
することで検索結果の中身を表示することができるのですが、前回から使いまわしの戦国武将データではベクトル検索だけでは必要なドキュメントを上位に持ってくることができませんでした。特に 2 位以降の精度が悪いです。そういえば Embeddings の生成に使用したチャンクが小さすぎました。text-embedding-ada-002
は最大 8,191 トークンまで入力できるので、こちらを考慮して再度データを作ろうかと思います。
ハイブリッド検索
ChatGPT Retrieval Plugin はデフォルトでハイブリッド検索が有効になっていますが、AZURESEARCH_DISABLE_HYBRID
を 1
に設定するとベクトル検索のみになります。ベクトル検索の精度が悪い場合、別の人物の検索結果を上位に持ってきてしまい、そのまま ChatGPT によって回答が統合されてしまいます。このような場合は、キーワード検索を組み合わせたハイブリッド検索が有効です。(もちろんキーワード検索単独も確実ですが…)用途に応じて検証することが必要です。
セマンティックハイブリッド検索
AZURESEARCH_SEMANTIC_CONFIG
にセマンティック構成名をセットすることで、セマンティックハイブリッド検索が有効になります。セマンティックハイブリッド検索はハイブリッド検索の結果を Microsoft の Turing モデルを利用した Re-ranker で結果を並べ替えることでさらに精度を高める独自の手法です。
試してみたい機能
- Memory 機能
プラグインの/upsert
エンドポイントを利用することにより、ChatGPT は対話データをベクトルデータベースに保存して、後で参照できるようにすることができます。Azure Cognitive Search に直接対話データ入れちゃうのはアリなのか…(笑) 過去の記憶もハイブリッド検索で正確性上げていくアプローチ。 - OAuth 認証
OAuth プロバイダーを使用して、プラグインを追加するユーザーを認証し、API へのアクセスを許可できます。 - デプロイ
Azure Container Apps へのデプロイ機能
6/13 ちなみに LangChain の Retriever として Azure Cognitive Search でキーワード検索ができる AzureCognitiveSearchRetriever やベクトル検索に対応した VectorStore も追加され直接利用することが可能になりました。
Microsoft 365 Copilot 用プラグイン開発
Microsoft 365 Copilot 用のプラグインは、早期アクセス プレビュー段階であり、参加するには開発者プレビュープログラムへの申請が必要です。Microsoft 365 Copilot では、Teams メッセージ拡張機能や Microsoft Graph コネクタが提供されます。
おわりに
こちらのエンタープライズサーチの MRKL アプローチに複数の ChatGPT Plugin を組み合わせたアプリを開発すると面白そうですね。
AI を中心とした Copilot stack やプラグイン エコシステムについては 7 月に開催予定の Azure OpenAI Developers セミナー 第 2 回でも詳しく解説する予定です。