Groq API + LangChainでRAG📌PDFマニュアルを参照して質問に回答する

2024/06/17に公開

概要

PDFで作成したマニュアルの情報を参照してLLMが質問に答えられるようにRAGを実装します。

処理の流れ

  1. pypdf.PdfReaderでPDF内のテキストを読み込む
  2. RecursiveCharacterTextSplitterを用いてテキストを分割
  3. Faissを用いてVectorDBを作成
  4. LangChainを用いてRAG chainを作成、実行

構成は下記の図のようになっています。

  • VectorDB: Faiss
  • LLM: Groq API

今回は動作確認のためにカフェのマニュアル(仮)を作成してみました。
当然このマニュアルの情報はLLMは知らない情報になっています。

コード

PDFからテキストを抽出

read_pdf
from pypdf import PdfReader


def read_text_from_pdf(pdf_path):
    reader = PdfReader(pdf_path)
    text = ""
    for page_num in range(len(reader.pages)):
        page = reader.pages[page_num]
        text += page.extract_text()
    return text


# PDFファイルのパスを指定してテキストを取得
pdf_text = read_text_from_pdf("./rag_example.pdf")

テキストを分割し、Faiss DBを作成

prepare_retriever
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter

# チャンク間でoverlappingさせながらテキストを分割
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=50,
)
# テキストを分割
splited_text = text_splitter.split_text(pdf_text)

embeddings = HuggingFaceEmbeddings(
    model_name="oshizo/sbert-jsnli-luke-japanese-base-lite"
)

# テキストを埋め込みベクトルに変換
index = FAISS.from_texts(splited_text, embedding=embeddings)
# FaissのRetrieverを取得
retriever = index.as_retriever(search_kwargs={"k": 4})

RAG chainを実行

main
import os

from langchain_core.prompts import ChatPromptTemplate
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_groq import ChatGroq


# Get Groq API key
groq_api_key = os.environ["GROQ_API_KEY"]
groq_chat = ChatGroq(groq_api_key=groq_api_key, model_name="llama3-70b-8192")

system_prompt = (
    "あなたは便利なアシスタントです。"
    "マニュアルの内容から回答してください。"
    "\n\n"
    "{context}"
)

user_input = input("質問を入力してください: ")

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_prompt),
        ("human", "{input}"),
    ]
)

# ドキュメントのリストを渡せるchainを作成
question_answer_chain = create_stuff_documents_chain(groq_chat, prompt)
# RetrieverとQAチェーンを組み合わせてRAGチェーンを作成
rag_chain = create_retrieval_chain(retriever, question_answer_chain)

response = rag_chain.invoke({"input": user_input})

print("User: ", user_input)
print("Assistant:", response["answer"])

『この店で開催されるイベントは?』と聞いてみました。想定していた正解は6.1と6.3に記載のあるバリスタショーとワークショップでしたが、正しく回答できています。

User:  この店で開催されるイベントは?
Assistant: この店で開催されるイベントは、以下の2つです。

1. バリスタショー:毎週土曜日の午後 2時から、バリスタによるラテアートのデモンストレーションを開催。
2. ワークショップ:月に一度、コーヒーの淹れ方講座を開催。予約制。

ちなみに、RAGで参照されたドキュメント情報はresponseに含まれているので下記のように確認することができます。

response["context"][0].page_content

RAG chainを実行(LCELで実装するパターン)

main
import os

from langchain_core.prompts import ChatPromptTemplate
from langchain_groq import ChatGroq
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# Get Groq API key
groq_api_key = os.environ["GROQ_API_KEY"]
groq_chat = ChatGroq(groq_api_key=groq_api_key, model_name="llama3-70b-8192")

system_prompt = (
    "あなたは便利なアシスタントです。"
    "マニュアルの内容から回答してください。"
    "\n\n"
    "{context}"
)

user_input = input("質問を入力してください: ")

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_prompt),
        ("human", "{input}"),
    ]
)

rag_chain = (
    # contextにはRetriever、inputにはユーザーの質問を渡す
    {"context": retriever, "input": RunnablePassthrough()}
    | prompt
    | groq_chat
    | StrOutputParser()
)
response = rag_chain.invoke(user_input)

print("User: ", user_input)
print("Assistant:", response)
User:  この店は何時に開店しますか?
Assistant: この店は、平日は8:00 AM、土曜日は9:00 AM、日曜日は10:00 AMに開店します。

参考

参考にさせていただきました。ありがとうございます。

https://python.langchain.com/v0.2/docs/tutorials/pdf_qa/#next-steps
https://zenn.dev/yumefuku/articles/llm-langchain-rag#discuss
https://note.com/npaka/n/nda9dc5eae1df
https://medium.com/@ahmed.mohiuddin.architecture/using-ai-to-chat-with-your-documents-leveraging-langchain-faiss-and-openai-3281acfcc4e9
https://zenn.dev/pipon_tech_blog/articles/8cdb27830236c5

Discussion