66
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ローカルで気軽にRAGを使って会話することが簡単すぎてビビった。

Posted at

今日は、ローカルにてRAG(Retrieval-Augmented Generation)を使って、あるPFDについて質問するチャットを作ろうと思う。
OpenAIを使うので、無料ではないので注意されたい。

今回のソースは
LangChain 完全入門
という本を参考にしていて、とても勉強になるので購入をお勧めする。

行いたいこと

ローカルでPDFを読み込ませて、内容を質問したり、要約させること。

大きな流れ

  1. 環境準備
  2. チャットアプリの土台の作成
  3. プロンプトを定義
  4. アップロードされたPDFファイルを分割
  5. 内容をベクトル化し保存
  6. 入力された質問とPDFから抽出した適した文をOpenAIに送信
  7. 回答を得る。

詳細

環境準備

環境構築

  • Python : 3.11.6
  • pip ライブラリ
    • chainlit==1.0.101
      • チャット画面を簡単に作るライブラリ
    • chromadb==0.4.22
      • ベクトルに特化したDBライブラリ。PDFを読み込んだ後に内容を保存する。
    • langchain==0.0.353
      • 大規模言語モデルを簡単に使えるようにするライブラリ。
    • openai==1.6.1
      • OpenAI用のライブラリ。
    • PyMuPDF==1.23.12
      • PDFを読み込むためのライブラリ。
    • tiktoken==0.5.2
      • embedding化するためのライブラリ。
    • python-dotenv==1.0.0
      • .envファイルを読み込み環境変数に登録するためのライブラリ。OpenAIのキーを保存する。

手順

  1. 上記をインストールした仮想空間のプロジェクトを作成。(割愛)
  2. OpenAIを使えるように、設定。
    Open AIのセットアップはOpenAIを使うまでを参照すると良い。

チャットアプリの土台の作成

チャットアプリを簡単に作るために、「chainlit」を利用する。
https://docs.chainlit.io/get-started/overview

めちゃくちゃ簡単に作れるので正直ビビった・・・・。

import chainlit as cl

# プロンプトを定義する.

@cl.on_chat_start
async def on_chat_start():
    """初回起動時に呼び出される."""

    # PDFを読み込む処理
    # PDFを分割する処理
    # PDFの内容をベクトル化して保存する処理
    await cl.Message(content=f"チャット開始").send()


@cl.on_message
async def on_message(input_message: cl.Message):
    """メッセージが送られるたびに呼び出される."""

    # 入力された文字と似たベクトルをPDFの中身から抽出
    # プロンプトを作成して、OpenAIに送信

    # 下記で結果を表示する(content=をOpenAIの結果にする。)
    await cl.Message(content="メッセージを受け取ったよ。").send()

プロンプトを定義する

今回は、下記でOpenAIに送るものとする。

文章を前提にして質問に答えてください。
--------------------------------------------
文章 :
「# 入力された文字と似たベクトルをPDFの中身から抽出の中身」
--------------------------------------------
質問 : 「入力された質問」
from langchain.prompts import PromptTemplate

prompt = PromptTemplate(
    template="""
    文章を前提にして質問に答えてください。

    文章 :
    {document}

    質問 : {question}
    """,
    input_variables=["document", "question"],
)

PDFに関する処理

PDFを読み込む

from langchain.document_loaders import PyMuPDFLoader

files = None

# awaitメソッドのために、whileを利用する。アップロードされるまで続く。
while files is None:
    # chainlitの機能に、ファイルをアップロードさせるメソッドがある。
    files = await cl.AskFileMessage(
        # ファイルの最大サイズ
        max_size_mb=20,
        # ファイルをアップロードさせる画面のメッセージ
        content="PDFを選択してください。",
        # PDFファイルを指定する
        accept=["application/pdf"],
        # タイムアウトなし
        raise_on_timeout=False,
    ).send()

file = files[0]
# アップロードされたファイルのパスから中身を読み込む。
documents = PyMuPDFLoader(file.path).load()

PDFを分割する処理

PDFの中身を分割するのは理由がある。
現在、GPTの最大トークン数は限られている。よって、PDFの中身を全てGPTに送るわけにもいかない。
最大トークン数とモデル

よって、日本語の意味が壊れない程度にPDFの内容を分割しておく。

from langchain.text_splitter import SpacyTextSplitter


text_splitter = SpacyTextSplitter(chunk_size=400, pipeline="ja_core_news_sm")
splitted_documents = text_splitter.split_documents(documents)

pipelineの種類は下記で参考にするといい。

トレーニングされたモデルは事前にダウンロードする必要があるので注意する。

python -m spacy download ja_core_news_sm

PDFの内容をベクトル化して保存する処理

Open AIのembedding APIを利用して、取得したテキストをベクトルで保存する。
ベクトルで保存する理由は、質問された質問とベクトルの類似度を測って抽出するためである。

保存先は、簡単にするためにベクトルストアのChromaを利用する。

from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma

# テキストをベクトル化するOpenAIのモデル
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")

# Chromaにembedding APIを指定して、初期化する。
database = Chroma(embedding_function=embeddings)

# PDFから内容を分割されたドキュメントを保存する。
database.add_documents(splitted_documents)

# 今回は、簡易化のためセッションに保存する。
cl.user_session.set("data", database)

ここまでで、アップロードされたファイルをRAGとして利用する準備は整った。

入力された質問とPDFから抽出した適した文をOpenAIに送信

ここからは、入力された質問を保存したベクトルから類似度の高いものを抽出し、Open AIに送信して、回答を得る流れを明記する。
何度も言うが、抽出する理由は、送信できる文字数には限界があるからである。
「何を抽出するか」と言うところがRAGで一番難しいところである。

from langchain.chat_models import ChatOpenAI
from langchain.schema import HumanMessage


# チャット用のOpenAIのモデル。今回は3.5を選定
open_ai = ChatOpenAI(model="gpt-3.5-turbo")

# セッションからベクトルストアを取得(この中にPDFの内容がベクトル化されたものが格納されている)
database = cl.user_session.get("data")

# 質問された文から似た文字列を、DBより抽出
documents = database.similarity_search(input_message.content)

# 抽出したものを結合
documents_string = ""
for document in documents:
    documents_string += f"""
    ---------------------------------------------
    {document.page_content}
    """

# プロンプトに埋め込みながらOpenAIに送信
result = open_ai(
    [
        HumanMessage(
            content=prompt.format(
                document=documents_string,
                query=input_message.content
            )
        )
    ]
).content

最後に

LangChainのおかげで、LLMをより簡単に利用できるようになったような感じがする。
今は、LangChain=0.0.353(353の時点ですごい・・・)を使っているが、LangChain=0.1.4がリリースされたので、試してみたい。

おまけ

私は、ローカルで「今までの人生の振り返り」と言うまとめてきたファイルを読み込ませて、
「今やるべきことは一言で言うと何ですか?」
という問いをした。
解答は、「もっと技術の勉強してください」であった・・・・笑

参考

LangChain 完全入門

66
59
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
66
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?