【Python】ChatGPTとLangChain使って自分のデータから検索させる

目的

ユーザーがChatGPTに質問をするとOpenAIが持っているデータベースを参照して回答してくれます。しかしこのデータは2021年までしか保存されていないため、それ以降のデータは質問しても回答してくれません。例として2022年にリリースされたLangChainのフレームワークのことをChatGPTに質問すると下記の画像のように知らないと回答されます。

また、自分でアップロードしたデータを参照させたい場合もファイルのサイズに制限があります。

しかし、今日紹介する、PyhotnフレームワークのLangChainとChatGPTを使用することで自分で指定したデータからクエリさせることが可能になります。例として同じLangChainについて質問をさせました。そしてChatGPTにLangChainのドキュメンテーション(英語)を読み取りさせています。ChatGPTに読ませたファイルはGitHubのdataディレクトリ内にあります。

このテクノロジーはアメリカの大企業のQ&Aやカスタマーサポートなどで使われ始めている機能で、すぐに当たり前の技術になってくるでしょう。(私がアメリカにいるので日本ではどれくらい復旧しているかは確認していません。)

完成したコードはGitHubから見てください。

先に準備(理解)しておくこと

今回のプロジェクトにはOpenAIのAPIキーが必要になります。このキーを取得するにはクレジットカードを登録する必要があります。テストくらいのリクエストのみであればジュース代だと思って使うのもありだと思います。※自己責任で進めてください。

chromaDBはオプショナルですが、ChatGPTに入力した記録を残しChatGPTがその会話をデータとして参照できるようにします。

LangChainは今回の主役になるフレームワークです。

プロジェクトのセットアップ

では下記のコマンドでGitHubからリポジトリをクローンしましょう。

git clone https://github.com/DanNakatoshi/chatGPT-LangChain.git
cd chatGPT-LangChain

#virtualenv仮想環境を使う場合
pip install virtualenv
virtualenv env
#Windowsの場合
env\Scripts\activate
#Linuxの場合
source env/bin/activate

#(env)がアクティベートされたら
pip install -r requirements.txt

以下に手順を詳しく説明します:

  1. 最初のコマンド git clone https://github.com/DanNakatoshi/chatGPT-LangChain.git は、GitHub上の chatGPT-LangChain というリポジトリを複製するために使用されます。このコマンドを実行すると、現在のディレクトリに chatGPT-LangChain フォルダが作成され、その中にリポジトリのファイルがダウンロードされます。
  2. 次のコマンド cd chatGPT-LangChain は、chatGPT-LangChain フォルダに移動します。これにより、以降のコマンドはこのフォルダ内で実行されます。
  3. 次に、仮想環境を使用する場合の手順を示します。仮想環境は、プロジェクトごとに独立したPython環境を作成するために使用されます。仮想環境を使用しない場合は、この手順をスキップしてください。
    • pip install virtualenv コマンドは、仮想環境を作成するために必要な virtualenv パッケージをインストールします。
    • virtualenv env コマンドは、env という名前の仮想環境を作成します。このコマンドを実行すると、現在のディレクトリに env フォルダが作成されます。
    • Windowsの場合は、次のコマンドで仮想環境をアクティベートします:env\Scripts\activate(env\Scripts\activate)
    • Linuxの場合は、次のコマンドで仮想環境をアクティベートします:source env/bin/activate
    • (env) というプロンプトが表示されれば、仮想環境が正常にアクティベートされています。
  4. (env) がアクティベートされたら、次のコマンド pip install -r requirements.txt を実行します。これにより、requirements.txt ファイルに記載されている依存関係が自動的にインストールされます。このコマンドを実行する前に、仮想環境がアクティベートされていることを確認してください。

APIキーを貼り付ける

constantsCOPY.pyのファイル名からCOPYを取り除き、constants.pyにします。次にChatGPTのAPIキーを貼り付けます。

APIKEY = "ここにAPIキーを貼り付ける"

ファイルを実行する

ではdataディレクトリにchatGPTに読ませたいデータを入れておきます。.txtファイル、PDFファイル、CSVファイル、JSONファイルなどが読み込み可能です。

次に下記のコマンドでpyファイルを実行します。

python chatgpt.py

では次にコードを詳しく説明します。

メソッドのインポート

リポジトリになるように下記に必要なメソッドとモジュールをインポートします。

import os
import sys

import openai
from langchain.chains import ConversationalRetrievalChain, RetrievalQA
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import DirectoryLoader, TextLoader
from langchain.embeddings import OpenAIEmbeddings
from langchain.indexes import VectorstoreIndexCreator
from langchain.indexes.vectorstore import VectorStoreIndexWrapper
from langchain.llms import OpenAI
from langchain.vectorstores import Chroma

import constants
  • ConversationalRetrievalChain: 対話型のリトリーバルチェーンを構築するためのクラスです。このチェーンは、質問応答システムを作成する際に使用されます。
  • RetrievalQA: リトリーバルチェーン内で使用される質問応答モデルを構築するためのクラスです。
  • ChatOpenAI: LangChain内で使用される対話モデルを構築するためのクラスです。
  • DirectoryLoaderおよびTextLoader: テキストデータやディレクトリ内の複数のテキストファイルからデータをロードするためのクラスです。
  • OpenAIEmbeddings: OpenAIの埋め込みモデルを使用してテキストデータを埋め込むためのクラスです。
  • VectorstoreIndexCreatorおよびVectorStoreIndexWrapper: ベクトルストアのインデックスを作成し、操作するためのクラスです。
  • Chroma: Chromaベクトルストアを作成および操作するためのクラスです。
  • constantsモジュールには、APIキーなどの機密情報が constants.APIKEY の形式で定義しています。

ChromaDBのコンフィグ

PERSIST = False

この行は、モデルをディスクに保存して再利用するための設定を行っています。

PERSIST は、モデルをディスクに保存して再利用するかどうかを制御するためのブール値の変数です。現在は False に設定されていますので、モデルの永続化は無効になっています。

PERSISTTrue に設定すると、モデルがディスクに保存され、同じデータに対して繰り返しクエリを実行する場合に、モデルを再作成する必要がありません。これにより、処理時間を節約できます。

ただし、モデルをディスクに保存すると、ディスク容量の使用量が増えることに注意する必要があります。また、ディスク上の保存データの管理や更新も考慮する必要があります。

クエリの作成

query = None
if len(sys.argv) > 1:
  query = sys.argv[1]

if PERSIST and os.path.exists("persist"):
  print("Reusing index...\n")
  vectorstore = Chroma(persist_directory="persist", embedding_function=OpenAIEmbeddings())
  index = VectorStoreIndexWrapper(vectorstore=vectorstore)
else:
  #loader = TextLoader("data/data.txt") # Use this line if you only need data.txt
  loader = DirectoryLoader("data/")
  if PERSIST:
    index = VectorstoreIndexCreator(vectorstore_kwargs={"persist_directory":"persist"}).from_loaders([loader])
  else:
    index = VectorstoreIndexCreator().from_loaders([loader])

chain = ConversationalRetrievalChain.from_llm(
  llm=ChatOpenAI(model="gpt-3.5-turbo"),
  retriever=index.vectorstore.as_retriever(search_kwargs={"k": 1}),
)

このコードは、query 変数を使用してインデックスを再利用するかどうかを判断し、その結果に応じてインデックスを作成します。

まず、queryNone に初期化します。次に、条件 len(sys.argv) > 1 を使用して、コマンドライン引数の数が1つ以上あるかどうかをチェックします。もし引数が存在する場合、sys.argv[1] の値が query に代入されます。

その後、PERSISTTrue かつ "persist" という名前のディレクトリが存在する場合、インデックスを再利用していることを示すメッセージが表示されます。Chroma クラスのインスタンスである vectorstore を作成し、VectorStoreIndexWrapper を使用してインデックスを作成します。

もし条件に該当しない場合、DirectoryLoader("data/") を使用してディレクトリからデータをロードし、PERSISTTrue の場合は永続化されたインデックスを作成します。そうでなければ、新しいインデックスを作成します。

最後に、ConversationalRetrievalChain.from_llm() メソッドを使用して、LangChainの ChatOpenAI モデルと作成されたインデックスを使用して、対話型リトリーバルチェーン chain を構築します。

このコードは、インデックスの作成および再利用に関するロジックを含んでおり、ユーザーのクエリに応じて適切なインデックスを作成または再利用するためのものです。

チャットの作成

chat_history = []
while True:
  if not query:
    query = input("質問: ")
  if query in ['quit', 'q', 'exit']:
    sys.exit()
  result = chain({"question": query, "chat_history": chat_history})
  print(result['answer'])

  chat_history.append((query, result['answer']))
  query = None

このコードは、ユーザーとの対話を行うためのループを表しています。

chat_history は空のリストとして初期化されます。その後、無限ループが開始されます。

ループ内では、まず query が存在しない場合(初回のループまたは直前のループの応答がない場合)、ユーザーに対して質問を入力するように促します。入力した質問が ['quit', 'q', 'exit'] のいずれかである場合、スクリプトの実行を終了します。

それ以外の場合、chain を使用して質問と過去の対話履歴を含む辞書を渡し、応答を取得します。取得した応答は result に格納されます。

その後、取得した応答を出力します(result['answer'])。また、対話履歴に現在の質問と応答を追加します。

最後に、queryNone に設定して次のループに進みます。

このコードは、ユーザーからの質問に応答するために対話型リトリーバルチェーンを使用し、対話履歴を記録しながら対話を継続するためのものです。

まとめ

このコードを使って、LangChainフレームワークを使用して、会話型の検索と質問応答の機能を実装することができました。さらにエンドユーザーが利用できるためにはUIが必要になるのでそれが課題になると思います。

今回のコードはhttps://github.com/techleadhd/chatgpt-retrievalを参考にしています。