AzureOpenAIEmbeddingsでPDFをページごとにベクトル化してローカルに保存してみた

## はじめに
AzureOpenAIEmbeddingsでPDFをページごとにベクトル化して、ベクトル化したデータをローカルに保存してみました。ページ数が多いPDFをベクトル化すると、Azureのレート制限エラーが出てしまいます。各ページを個別にベクトル化することで、このエラーを回避することができます。
　　
※レート制限のエラーとは、以下のようなエラーのことです。
>openai.error.RateLimitError: Requests to the Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. Operation under Azure OpenAI API version 2023-05-15 have exceeded call rate limit of your current OpenAI S0 pricing tier. Please retry after 48 seconds.

このエラーを再現したい場合は、今回の記事で使うPDFを以下の記事のコードでベクトル化してみてください。

https://qiita.com/tatsuki-tsuchiyama/items/a48d1cc4afce588e1797

## 環境
- Python 3.11.5
- AzureOpenAI
- LangChain
- PyPDFLoader
- FAISS

## 実装
### 環境変数の設定
.envファイルを作成し、Azure Open AIの環境変数の設定をします。
ご自身の環境に合わせて設定してください。
```.env
# APIキー
OPENAI_API_KEY = "XXXXX"

# エンドポイント
AZURE_OPENAI_ENDPOINT = "XXXXX"

# 使用するOpenAI APIのバージョン
OPENAI_API_VERSION = "XXXXX"
```

　　
### 必要なライブラリのインストール
以下のコマンドで必要なライブラリをインストールします。
```terminal
pip install langchain openai python-dotenv install faiss-cpu pypdf PyPDF2 tiktoken
```
　　
### PDFの準備
今回実行するPythonファイルと同じ階層にdatasetsディレクトリを作成し、以下のPDF（Microsoft Fabricの概要に関するドキュメント）を追加してください。

https://learn.microsoft.com/pdf?url=https%3A%2F%2Flearn.microsoft.com%2Fja-jp%2Ffabric%2Fget-started%2Ftoc.json

　　
Pythonファイルの中身は以下のとおりです。
```test.py
import time
from langchain.document_loaders import PyPDFLoader
from langchain.vectorstores import FAISS
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import AzureOpenAIEmbeddings
import os

from dotenv import load_dotenv
load_dotenv()

embeddings = AzureOpenAIEmbeddings(
    azure_deployment="text-embedding-ada-002",
    openai_api_version="2023-05-15"
)

dummy_text, dummy_id = "1", 1
vectorstore = FAISS.from_texts([dummy_text], embeddings, ids=[dummy_id])
vectorstore.delete([dummy_id])

dirname = "datasets"
files = []

# ディレクトリ内のPDFファイルを検索
for filename in os.listdir(dirname):
    full_path = os.path.join(dirname, filename)
    if os.path.isfile(full_path):
        files.append({"name": filename, "path": full_path})

for file in files:
    if file["path"].endswith(".pdf"):
        last_processed_page = 0  # 最後に処理したページ番号を記録する変数
        while True:
            try:
                loader = PyPDFLoader(file["path"])
                pages = loader.load_and_split()
                for page_number, page in enumerate(pages, start=1):
                    if page_number <= last_processed_page:
                        continue  # すでに処理されたページはスキップ

                    page.metadata["source"] = file["path"]
                    page.metadata["name"] = file["name"]
                    text_splitter = CharacterTextSplitter(
                        chunk_size=1000, chunk_overlap=0)
                    docs = text_splitter.split_documents([page])
                    vectorstore.merge_from(
                        FAISS.from_documents(docs, embeddings))

                    print(f"ベクトル化完了: {file['name']} - ページ {page_number}")
                    last_processed_page = page_number  # 処理されたページを更新
                break
            except Exception as e:
                print(f"エラーが発生しました: {e}")
                print(
                    f"ファイル '{file['name']}' のページ {last_processed_page + 1} にてエラー。1分間待機してから再試行します。")
                time.sleep(60)  # 1分（60秒）待機してから再開
                continue  # エラー発生後、ループを続ける

vectorstore.save_local("./vectorstore")
```
コードの解説は以下のとおりです。

1. **環境変数の読み込み**: `dotenv`ライブラリを用いて、`.env`ファイルから環境変数を読み込んでいます。
2. **Azure OpenAI Embeddingsの設定**: AzureのOpenAI埋め込みモデル（`AzureOpenAIEmbeddings`）を使用して、テキストをベクトル化します。
3. **FAISSベクトルストアの初期化**: `FAISS.from_texts`を使用して、ダミーテキストをベクトル化し、その後、`vectorstore.delete`でこのダミーデータを削除しています。
4. **PDFファイルの探索とリストアップ**: `datasets`ディレクトリ内の全てのPDFファイルを探索し、それらのファイル名とパスをリストに追加しています。
5. **PDFファイルの処理**:
    - 各PDFファイルに対して、`PyPDFLoader`を使用してPDFからテキストを読み込んでいます。
    - `load_and_split`メソッドを用いて、PDFをページごとに分割します。
    - 各ページのテキストを`CharacterTextSplitter`を使ってさらに分割し、これらの小さなテキストチャンクをベクトル化してFAISSベクトルストアに追加します。
6. **例外処理**: ファイルの読み込みやベクトル化中に例外が発生した場合、エラーメッセージを出力し、1分間待機した後に処理を再開します。
7. **ベクトルストアの保存**: 最後に、ベクトルストアをローカルに保存します。

　　
### 実行結果
以下が実行結果になります。
```terminal
ベクトル化完了: microsoft-fabric.pdf - ページ 1
ベクトル化完了: microsoft-fabric.pdf - ページ 2
ベクトル化完了: microsoft-fabric.pdf - ページ 3
ベクトル化完了: microsoft-fabric.pdf - ページ 4
ベクトル化完了: microsoft-fabric.pdf - ページ 5
(中略）
ベクトル化完了: microsoft-fabric.pdf - ページ 445
```
　　
処理がすべて完了すると、以下のようにvectorstoreが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/f9e447b2-a137-9161-88ca-2e13064660a3.png)

  
## おわりに
読んでいただき、ありがとうございました！
以下のXアカウントにて、情報発信を行っています！

https://x.com/t___tatsuki