🔎

LangChainチャットボットのベクターストアにAzure Cognitive Searchを使ってみる

2023/09/01に公開

先日、「LangChainチャットボットのコードに簡易的なウェブインターフェイスを追加してみる」というエントリーの中で、LangChainのチャットボットサンプルコードを動かしてみました。このエントリーはそのコードを少し拡張して、ベクターストアにAzure Cognitive Searchを使ってみた話をします。

検証目的でローカルで動かし、少ない文章量を処理させる範囲では前回のコードでも問題ないのですが、今後もう少しちゃんとしたシステムに仕上げていくには永続的にデータを保管できるPaaS型のベクターストアを使いたいところです。

LangChainのドキュメントのVector Storesを眺めてみると、選択肢はいくつかありそうですが、このエントリーではその中のAzure Cognitive Searchを試してみた話をします。今回のエントリーでも、チャットボット部分はAzure OpenAI Serviceではなく、OpenAI APIを引き続き使用しています。

なお、このエントリーを書いている時点では、Azure Cognitive SearchのVector Search機能はパブリックプレビューの状態にある(GAではない)のと、Pythonモジュールのazure-search-documentsもプレビュー版ですので、本番システムでの利用検討はその点を意識しておく必要があります。

Azure Cognitive Searchのリソースを作成する

Azure PortalからAzure Cognitive Searchのリソース作成を行います。

リソースの作成画面で、「Azure Cognitive Search」で検索し、作成ボタンを押します。

サブスクリプションは任意のものに変更してください。リソースグループは「LangChain」と新規作成しました。サービス名も好みのもので良いのですが、グローバルでユニークだと思いますので、名前が重なったら別の名前にしてください。場所は今回は「Japan East」(東日本リージョン)にしました。

入力が終わったら画面したの「確認及び作成」ボタンを押し、確認画面で間違いないことを確認の上で「作成」ボタンを押します。リソース作成が完了したら、リソースの画面に移ります。

概要画面で、エンドポイントURLを確認し、コピーしておきます。作成画面で指定したサービス名に沿った、https://{your-service-name}.search.windows.net/という名前になっていると思います。

設定セクション配下のキーの画面で、APIキーを取得します。プライマリ管理者キーをコピーしておきます。

ここで取得したエンドポイントURLとAPIキーを後ほど利用します。

Azure Cognitive Searchにデータを追加する

Azure Cognitive Searchは、AzureストレージアカウントのBLOBストレージに保管したファイルをインデクサーが収集して、その際にAzure Cognitive Serviceの画像分析機能を組み合わせたりできるパワフルなAI検索サービスなのですが、現時点でインデクサーがベクターストア向けに対応していません。

そのため、コードでベクター表現に変換して、Azure Cognitive Searchにデータを追加させます。前回のコードはベクターストア作成、検索機能、チャットボットWebインターフェイスが同居していましたが、ベクターストア作成部分は切り出して、別のファイル(vector-store.py)を作ります。

ファイル体系は以下の通りです。

/your_directory
|-- app.py
|-- vector-store.py
|-- .env
|-- templates/
|   |-- index.html
|-- html_data/
|   |-- Equinox_EN.html
|-- pdf_data/
|   |-- Equinox_JP.pdf

.envファイルを拡張します。AZURE_COGNITIVE_SEARCH_ENDPOINTには、Azure Cognitive Searchリソースの概要ページで取得したURLを、AZURE_COGNITIVE_SEARCH_ADMIN_KEYには、キーのページで取得したAPIキーを設定します(以下の文字列はダミーです)。AZURE_COGNITIVE_SEARCH_INDEX_NAMEは作成するインデックスの名前です。好みで「langchaintest-index」から変更してください。

.env
OPENAI_API_KEY="D7f9G2uMEK1lvaoA8eB0xjXVNyR5rFQ63LhtSgZWmPc"
AZURE_COGNITIVE_SEARCH_ENDPOINT="https://langchaintest-acs.search.windows.net/"
AZURE_COGNITIVE_SEARCH_ADMIN_KEY="9X8rZ4FgH2D6uOJQsV7WnTzC5pL1vByEwRiY0aNx3klMbm"
AZURE_COGNITIVE_SEARCH_INDEX_NAME="langchaintest-index"

ベクターストア作成部分を切り出したコードです。このエントリーの冒頭でも描きましたが、内部的に使われているazure-search-documentsはプレビューバージョンが必要なので、pip install azure-search-documents --pre --upgradeでモジュールインストールすることが前提です。

Wikipediaのキタサンブラックに関する英語ページ(ウェブページ)、ローカル保存したWikipediaのイクイノックスに関する英語ページ(HTMLファイル)、同じくローカル保存したイクイノックスに関する日本語ページ(PDFファイル)をベクター表現に変換し、Azure Cognitive Searchにデータ追加します。

Text Splitterの箇所をこのエントリー最後に書いている参考サイトのコードに変えてみたのですが、これが良いか悪いかは十分に検証していません(扱うドキュメントの種類によっても変わりそうだと思います)。

vector-store.py
# Langchain imports
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import TokenTextSplitter
from langchain.vectorstores.azuresearch import AzureSearch

# Other imports
import os
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

# Create embeddings and vector store instances
embeddings = OpenAIEmbeddings(deployment="text-embedding-ada-002", chunk_size=1)
acs = AzureSearch(
    azure_search_endpoint=os.environ['AZURE_COGNITIVE_SEARCH_ENDPOINT'],
    azure_search_key=os.environ['AZURE_COGNITIVE_SEARCH_ADMIN_KEY'],
    index_name=os.environ['AZURE_COGNITIVE_SEARCH_INDEX_NAME'],
    embedding_function=embeddings.embed_query,
)

data = []

# Load web article
from langchain.document_loaders import WebBaseLoader
urls = [
     "https://en.wikipedia.org/wiki/Kitasan_Black"
]
for url in urls:
    loader = WebBaseLoader(url)
    partial_data = loader.load()
    data.extend(partial_data)

# get data from HTML files
from langchain.document_loaders import BSHTMLLoader
pages = ["html_data/" + f for f in os.listdir("html_data") if f.endswith('.html')]
for page in pages:
    loader = BSHTMLLoader(page, open_encoding='utf-8')
    partial_data = loader.load()
    data.extend(partial_data)

# get data from PDF files
from langchain.document_loaders import PyPDFLoader
pdfs = ["pdf_data/" + f for f in os.listdir("pdf_data") if f.endswith('.pdf')]
for pdf in pdfs:
    loader = PyPDFLoader(pdf)
    partial_data = loader.load_and_split()
    data.extend(partial_data)

# Split and vectorize text
text_splitter = TokenTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(data)

# Add documents to Azure Search
acs.add_documents(documents=docs)

pytnon vector-store.pyを実行して正常終了することを確認します。

Azrue PortalのAzure Cognitive Searchリソースの画面に戻り、検索結果セクションのインデックス画面を開きます。「langchaintest-index」が作成されていることを確認しましょう。

インデックス名をクリックし、開いた検索エクスプローラーでクエリ文字列にsearch=*&$count=trueと検索してみます。"content_vector"のところにベクターの数字が並んでいることが確認できました。

Azure Cognitive Searchをベクターストアとしてチャットボットに使わせる

ベクターストア作成で切り出された部分を削除し、使用するベクターストアとしてAzure Cognitive Searchのリソースを指定するように変更します。

app.py
# Flask imports
from flask import Flask, request, jsonify, render_template

# Langchain imports
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationalRetrievalChain, LLMChain
from langchain.embeddings import OpenAIEmbeddings
from langchain.memory import ConversationSummaryMemory
from langchain.vectorstores.azuresearch import AzureSearch

# Other imports
import os
from dotenv import load_dotenv

app = Flask(__name__)

# Load environment variables
load_dotenv()

# Initialize Langchain model
llm = ChatOpenAI()

# Connect to Azure Cognitive Search
embeddings = OpenAIEmbeddings(deployment="text-embedding-ada-002", chunk_size=1)
vector_store = AzureSearch(
    azure_search_endpoint=os.environ['AZURE_COGNITIVE_SEARCH_ENDPOINT'],
    azure_search_key=os.environ['AZURE_COGNITIVE_SEARCH_ADMIN_KEY'],
    index_name=os.environ['AZURE_COGNITIVE_SEARCH_INDEX_NAME'],
    embedding_function=embeddings.embed_query,
)

# Initialize memory and retriever
memory = ConversationSummaryMemory(llm=llm, memory_key="chat_history", return_messages=True)
retriever = vector_store.as_retriever()
qa = ConversationalRetrievalChain.from_llm(llm, retriever=retriever, memory=memory, verbose=True)

# Flask routes
@app.route('/')
def index():
    return render_template('index.html')

@app.route('/ask', methods=['POST'])
def ask():
    question = request.json['question']
    answer = qa(question)['answer']
    return jsonify({"answer": answer})

# Run application
if __name__ == '__main__':
    app.run()

最後にpython app.pyと実行して前回同様に使用できるか確認してみます。

与えたデータを活用してうまく回答してくれました。検証が終わったら、作成したAzure Cognitive Searchリソースは削除しておきましょう。

参考サイト
更新履歴
  • 2023/9/2 表現修正とリンク追加
  • 2023/9/3 次のエントリーのリンクを追加

Discussion