HyDE手法によるRAGシステムを構築

はじめに

HyDE(Hypothetical Document Embeddings)というRAGを改善する手法を使ったRAGを構築した。 この投稿では、HyDEを試した内容をまとめた。 使用するLLMは、コストを抑えるためgpt-4o-miniを使用した。

情報源

  1. LLMのファインチューニングとRAG この書籍でHyDEのことを知り、そこに掲載されているサンプルを試した。
  2. langchainとDatabricksで(私が)学ぶRAG : HyDEを使ったRAG この記事のコードを参考にさせてもらった。
  3. 【RAG】LangChainでHyDEを試す こちらのコードも参考にした。
  4. AutoHyDE: 次世代のRAG開発のための手法(HyDEを拡張したAutoHyDEの紹介) HyDEの発展形のAutoHyDEについて解説記事。今後、こちらも試してみたい。

コード

コードの多くの部分は、こちらの投稿を流用した。

ベクトルデータベースを読み込む

WIKI_DB = "../20240813_RAG_MakeDB/wiki_vs.db"
import os
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS

model_name = "intfloat/multilingual-e5-large"
model_path = f"/workdir/models/{model_name}"

embeddings = HuggingFaceEmbeddings(
    model_name = model_path,
    model_kwargs = {'device':'cuda:0'},
)

# 事前に構築したベクトルデータベースを読み込む
if os.path.exists(WIKI_DB):
    db = FAISS.load_local(WIKI_DB, embeddings, allow_dangerous_deserialization=True)
else:
    print("="*80)
    print("You need to make vector database")
    print("="*80)

検索器を構築

# 検索器を構築
# ベクトル検索で2文書を得る(k=2)
retriever = db.as_retriever(search_kwargs={'k':2})

APIキーを設定

# OpenAIのAPIキーを設定する
import os

os.environ['OPENAI_API_KEY'] = 'xxxxxxxxxxxxxxxxx'

gpt-4o-miniをLLMとして設定

# LLMを設定
from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain

llm = ChatOpenAI(model_name="gpt-4o-mini")

HyDEテンプレートを準備

# HyDEテンプレート(仮想検索用のテンプレート)

from langchain_core.prompts.prompt import PromptTemplate
from langchain.retrievers import RePhraseQueryRetriever

hyde_prompt_template = """以下の質問の回答を書いてください。
質問: {question}
回答:"""

# Hydeプロンプト
hyde_prompt = PromptTemplate.from_template(hyde_prompt_template)

# HyDE retriever
rephrase_retriever = RePhraseQueryRetriever.from_llm(
    retriever = retriever,
    llm = llm,
    prompt = hyde_prompt,
)

RAGテンプレートを準備

# テンプレートを準備

template = """
以下の「前提条件」を元に「質問」に回答してください。
なお、前提条件に無い情報は回答に含めないでください。
また、前提条件から回答が導けない場合は「分かりません」と回答してください。

前提条件:{context}

質問:{question} 
"""

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_template(template)

Documentsを整形する関数を定義

# Documentsを整形する関数

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

from langchain_core.output_parsers import StrOutputParser
output_parser = StrOutputParser()

Chainを構築

# LangChain(LCEL)によりChainを作成

from langchain_core.runnables import RunnableParallel, RunnablePassthrough

chain = (
    {"context": rephrase_retriever | doc_to_str, "question": RunnablePassthrough()}
    | prompt
    | llm
    | output_parser
)

質問

question = "B2FH論文について教えてください"
print(chain.invoke(question))

回答

B2FH論文BFH論文)は、元素の起源に関する記念碑的な論文で、題名は "Synthesis of the Elements in Stars" です。著者はマーガレット・バービッジ、ジェフリー・バービッジ、ウィリアム・ファウラー、フレッド・ホイルの4名で、彼らの頭文字を取って「B2FH」として知られています。この論文は1955年から1956年にかけてケンブリッジ大学とカリフォルニア工科大学で執筆され、1957年にアメリカ物理学会の査読付き学術誌"Reviews of Modern Physics"で発表されました。

BFH論文は、恒星内元素合成の理論をレビューし、観測データと実験データを用いてそれを裏付けました。また、鉄よりも重い元素を生成するための元素合成過程を特定し、宇宙の元素構成比について説明を提供しました。この論文は天文学と原子物理学の双方に大きな影響を与えるものとなりました。

回答内容は、wikipediaの該当項目の冒頭の部分とほぼ同じ内容のようだ。

まとめ

情報源1.の書籍では、 Hypothetical DocumentEmbedder()を使った実装となっていたが、今回は情報源2.等を参考にして、RePhraseQueryRetrieverクラスのfrom_llm()関数を使って実装した。chainを使ったとてもシンプルな実装となった。

今後、AutoHyDEを試してみたい。もう少しLCELについても理解を深めたい。