Home About Contact
LangChain , LLM , GPT

LangChain RetrievalQA を使って兼好法師に質問するその3 GPT-4o を使う

GPT-4o ( gpt-4o-2024-05-13 ) が出たので、兼好法師に質問するときにこのモデルを使ってみた。 基本的には、前回のエントリー(LangChain RetrievalQA を使って兼好法師に質問するその2) と同じ。 ただし、実際にやってみると以前のコードのままでは肝心の Q&A する部分が 作動しなかったので、その部分は調整しました。

環境

前回 と同じなので省略。

参考にしたページは LangChain 本家のこのページです。

以前のままで動いた部分

データベースを用意するところまでは変更はありません。

buildIndex.py

import os

from langchain.text_splitter import CharacterTextSplitter
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_community.vectorstores import Chroma

from langchain_community.embeddings import HuggingFaceEmbeddings
from huggingface_hub import snapshot_download


def toDocs(datasource_dir):
    loader = DirectoryLoader(
        datasource_dir,
        glob="*.txt",
        recursive=False,
        loader_cls=TextLoader,
        loader_kwargs={"autodetect_encoding": True})
    docs = loader.load()

    splitter = CharacterTextSplitter(
        chunk_size=50,
        chunk_overlap=0,
        add_start_index=True,
        separator="\n")

    return splitter.transform_documents(docs)

def toEmbeddings(model_repo_id, model_local_dir):
    if not os.path.exists(model_local_dir):
        snapshot_download(
            repo_id=model_repo_id,
            local_dir=model_local_dir)

    return HuggingFaceEmbeddings(
        model_name=model_repo_id,
        model_kwargs={"device":"cpu"})


current_dir = os.path.dirname(__file__)
docs = toDocs( os.path.join(current_dir, "Datasource") )

model_name = "multilingual-e5-base"
model_repo_id = "intfloat/multilingual-e5-base"
model_local_dir = os.path.join(current_dir, "model", model_name)

embeddings = toEmbeddings(model_repo_id, model_local_dir)

db_dir = os.path.join(current_dir, "chroma_db")
Chroma.from_documents(
    docs,
    persist_directory=db_dir,
    embedding=embeddings)

次に用意したデータベースを使って回答を得る(兼好法師に質問する)部分です。 前回のコード query.py そのままでは明示的に GPT-4o (gpt-4o-2024-05-13) を指定し場合エラーで作動しません。

元はこのように、モデル名を指定しないで OepnAI() 記述していたコードを・・・

qa_chain = RetrievalQA.from_llm(
  llm=OpenAI(),
  retriever=db.as_retriever())

次のように明示的に GPT-4o を使うように変更。

qa_chain = RetrievalQA.from_llm(
  llm=OpenAI(model_name="gpt-4o-2024-05-13"),
  retriever=db.as_retriever())

これで実行してみるとエラーが出て動きません。

なお、モデル名については次のページを参照のこと。

GPT-4o で動いた Q&A 部分のコード

GPT-4o 対応の query.py 。 次のように変更しました。

query.py

import os

from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings

from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

#from langchain_openai import OpenAI
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate


def toEmbeddings(model_repo_id, model_local_dir):
    if not os.path.exists(model_local_dir):
        snapshot_download(
            repo_id=model_repo_id,
            local_dir=model_local_dir)
    
    return HuggingFaceEmbeddings(
        model_name=model_repo_id,
        model_kwargs={"device":"cpu"})


current_dir = os.path.dirname(__file__)

model_name = "multilingual-e5-base"
model_repo_id = "intfloat/multilingual-e5-base"
model_local_dir = os.path.join(current_dir, "model", model_name)
embeddings = toEmbeddings(model_repo_id, model_local_dir)

db_dir = os.path.join(current_dir, "chroma_db")
db = Chroma(
    persist_directory=db_dir,
    embedding_function=embeddings)

retriever = db.as_retriever()


template = """Use the following pieces of context to answer the question at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
Use three sentences maximum and keep the answer as concise as possible.
回答は徒然草の作者 吉田兼好の文体で、100文字程度でまとめてください。

{context}

Question: {question}

Helpful Answer:"""
custom_rag_prompt = PromptTemplate.from_template(template)


def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

#llm = ChatOpenAI(model="gpt-3.5-turbo-0125")
llm = ChatOpenAI(model="gpt-4o-2024-05-13")

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | custom_rag_prompt
    | llm
    | StrOutputParser()
)

q = "粋な老人の振る舞いとはどうのようなものですか?"
a = rag_chain.invoke(q)
print(a)

前半部分の buildIndex.py で事前に用意したデータベースをロードする部分は前回から変更ありません。そのあとの Q&A する部分を変更しました。

システムプロンプト部分を参考にしたページにあったものを流用した関係で、 英語と日本語と混じった状態ですが、特に問題はないようです。

実行してみると次のようになりました。

「粋な老人の振る舞いとはどうのようなものですか?」に対するこたえ:

老人の粋な振る舞いとは、心を平坦に保ち、無意味な事をせず、健康に気を遣い、面倒な事には関わらないことである。
さらに、世俗の儀式や義理立てに縛られず、全てを捨てて無心で過ごすことが理想とされる。
年老いても淡々とした心持ちで、世間の評価を気にせず、静かに生きる姿が粋である。

ちなみに、以前にも質問した「素敵な女性とはどういう人ですか?」も 聞いてみました。

素敵な女性とは、その美しさや香りに惑わされることなく、内面の真実を見極めることが肝要である。
外見だけでなく、心の奥深くにある謙虚さや誠実さが重要であり、時折の距離感が恋心を保つ秘訣である。

だそうです。

追伸

今日(2024-05-18)あらためて別の質問をしてみたのです。

洗濯機が壊れました。修理できると思いますが、もう10年も使っているので、新しく買うべきか悩んでいます。
このような状況でどう判断するべきかアドバイスをください。

そうしたら:

洗濯機が壊れること、10年も使いしものなれば、直すに及ばず新しきを求むべし。
それが理に適う選択なり。
徒然草の教えに従えば、迷うことなかれ、さっさと新しきを手に入れよ。

なんか、吉田兼好風の口調で回答するようになった! これは偶然かも知れませんが(温度設定がデフォルトだし)。 なんだか楽しい。