GeminiAPI + LangChainでRAGを実装する

お疲れ様です。

最近の会社の勉強会でチャットボットのWebアプリを作成しています。(何故か教える側で…。)
やっている中で返答を返してくれる生成AIでRAG(検索拡張生成)を実装してみたいと思い、実際に作成してみたのでそれをまとめておきます。

やったこととしては以下になります。
生成AIには基本お金がかからず、個人のPCの性能に左右されないようにGemini APIを使用しています。

Gemini APIの準備

Gemini APIAPIキーの取得とPythonで使えるように準備をし確認するところまでやっています。

APIキーの取得

Google AI StudioにアクセスしてAPIキーを取得
https://ai.google.dev/ 2か所チェックをつけて「続行」をクリック。

APIキーを作成」をクリック。

※課金が無効になっているか確認する場合は下記にアクセスして「このプロジェクトには請求先アカウントがありません」となっていればおそらくOKです。
https://console.cloud.google.com/billing/linkedaccount
プロジェクトの部分からAPIキーをコピーしましょう。

SDKをインストール

Pythonの開発環境で"google-generativeai"をインストール
pip install google-generativeai

Pythonコードで動作確認をします。
この時APIキーの取り扱いには注意しましょう。直書きはNG。環境変数などに入れておくのが良いです。
コードは以下のような形で。

import os

import google.generativeai as genai


# APIキーを設定(環境変数に設定するパターン)
genai.configure(api_key=os.environ["GEMINI_API_KEY"]) ## 注意

# モデルを準備
model = genai.GenerativeModel('gemini-1.5-flash')

# 出力してみる
response = model.generate_content("エレファントカシマシはどんなバンドですか?")
print(response.text)


データベース作成

LangChainの機能を使ってChromaDBのデータベースを作成しました。
チャットボットで使うためにデータベースを出力してチャットボット側で読み込む想定で進めました。 必要なライブラリは以下。

langchain
langchain_community
sentence-transformers
unstructured
chromadb

コードは以下のようになっています。(いろいろなサイトを参考にさせていただきました🙇‍♂️)

# Webページの内容を知識として読み込み
urls = ["https://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%AC%E3%83%95%E3%82%A1%E3%83%B3%E3%83%88%E3%82%AB%E3%82%B7%E3%83%9E%E3%82%B7"]
loader = UnstructuredURLLoader(urls=urls)
docs = loader.load()
    
# 読込した内容を分割する
text_splitter = JapaneseCharacterTextSplitter(chunk_size=200, chunk_overlap=40)
docs = text_splitter.split_documents(docs)
    
# ベクトル化する準備
embedding = HuggingFaceEmbeddings(
    model_name="intfloat/multilingual-e5-base"
)
    
# 読込した内容を保存
vectorstore = Chroma.from_documents(
    documents=docs,
    embedding=embedding,
    persist_directory="./chroma",
    collection_name="elephants"
)

今回はwebサイトの中身をデータとして取得するUnstructuredURLLoaderを使用しました。 document_loadersはほかにも種類があるので利用したいデータに応じて使い分けができます。
変数urlsはリストで複数指定が可能です。
あとはChromaDBの保存の部分で、Chroma.from_documentsの引数collection_nameはチャットボットの読み込みの際にも使用するので覚えておきましょう。
こんな感じでsqlite3のファイルで保存されます。一緒に入っているフォルダも必要になります。


チャットボットに返答機能として実装

実際にチャットボットに返答生成できるように実装しました。 チャットボットのWebアプリはPythonライブラリのStreamlitで作成しています。

コード実装

まずは必要なライブラリのインストールから。データベース作成で使用したライブラリに加えて以下をインストールしてください。LangchainでGemini APIを使うためのライブラリです。

langchain_google_genai

そしてコードは以下になります。

TEMPLATE = """
あなたは質問応答のアシスタントです。質問に答えるために、検索された文脈の以下の部分を使用してください。
答えがわからない場合は、わからないと答えましょう。

質問: {question}
コンテキスト: {context}
答え:
"""
DB_PATH = "./rag/chroma"

# モデルを準備
llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash")
# プロンプトを設定
prompt = PromptTemplate.from_template(TEMPLATE)
# チェーンを準備
rag_chain = (prompt | llm)

# 直前のユーザの入力を取得(実際はWebアプリからの入力を取得)
user_input = "エレファントカシマシはどんなバンドですか?"

# ベクトル化する準備
embedding = HuggingFaceEmbeddings(
    model_name="intfloat/multilingual-e5-base"
)

# DBを読み込んで知識データ取得
vectorstore = Chroma(collection_name="elephants", persist_directory=DB_PATH, embedding_function=embedding)
docs = vectorstore.similarity_search(query=user_input, k=5)
context = "\n".join([f"Content:\n{doc.page_content}" for doc in docs])

# 返答を取得
response = rag_chain.invoke({"question": user_input, "context": context}).content

ここでもGemini APIを使いますがAPIキーはGOOGLE_API_KEYという名前で環境変数に入れておきます。
データベースの読み込みの部分では先ほど指定したcollection_nameとsqlite3ファイルが入ったフォルダのパスを指定します。
後は一般的なLangChainでのRAGの実装と同じかなと思います。

チャットボットアプリ上での表示

ここまで実装したアプリを使ってみました。アプリの機能として通常のGeminiの返答生成とRAGを使ったGeminiの返答生成を機能として実装しているので、出力結果を比較してみます。
ちなみにRAGで使用したデータベースはエレファントカシマシ関係のwikipediaをいくつか読み込んで作成したものになります。
質問文はいずれも「「俺たちの明日」という曲について教えてください。」としました。

  • 通常のGeminiの返答 そもそも誰の曲やねん、って感じですね。

  • RAGを使ったGeminiの返答 wikipediaに書かれていた情報を使ってそれらしき返答が出力されました。

今回のソースコードは下記のGitHubリポジトリにありますのでご参考ください。
といっても、まだ開発の途中段階なので修正される可能性はあります。(そのため今回は記事内にソースコードを書き込むように作成したのですが…。)

github.com

また今回参考にした記事は勉強段階のメモでTwitter(X)に残してあるのでそちらも参考になるかもです。

まだいろいろ試したいことはあるので、何かネタになりそうなら記事にしたいですね…!