OpenAIのRAG(Retrieval Augmented Generation、検索拡張生成)を紹介します。
RAGの手法により、企業の社内文書や最新ニュースなど、GPTが学習していない独自の知識を元に、回答することが可能です。

RAG(Retrieval Augmented Generation)とは

質問文に対し、検索した参考情報(Context)を付けてLLMに質問する仕組みがRAGです。RAGは質問タスクを、Contextの要約タスクに変換していると言えます。

RAGは、「DB登録」「検索」「生成」の3つのステップを経て、質問に対する答えを生成します。
「DB登録」では、社内文書を分割(チャンク)してベクトルDBに格納します。ベクトル化にはLLMが用いられることが多いです。
「検索」では、質問文との類似度を検索(Retrieval)し、関連文書を抽出します。
「生成」では、抽出した関連文書を元に、LLMが自然な回答文章を作ります。

検索させる参考情報は、テキスト情報なら何でも利用可能で、例えば企業内のword文書やPDFファイルなども利用可能です。

最新のRAGについて学びたい方は、以下書籍の4.4節、6章、7章をご参照ください。2025年1月時点で、Advanced RAGと評価方法について最もよくまとまった日本語の書籍です。

LangChainとは

LangChainは、GPT-4などの大規模言語モデル(LLM)を効率的に実装するためのライブラリです。おなじみPythonでの実装が可能です。

かなり使われており、機械学習分野のscikit-learnのような位置づけになってくる可能性があります。openAI APIを直接利用するより、簡単に実装が可能です。

ただし、LangChainは、裏で英語のプロンプトが使われているため、日本語出力が安定しない問題点があります。そのため、必ずしも使った方がいいというわけではありませんが、少なくともRAG周りは使っといて損はないかなという印象です。

実装のヒントとベストプラクティス

RAGの実装のヒントをまとめました。

DB登録するPDFファイルの品質担保

RAGがうまくいかない原因の多くが、検索に起因しています。検索精度の向上には、参照情報を適切な形で登録することが必要です。

もしPDFファイルに冗長な表現があれば削除したり、要件に関係のない内容を削除することで、回答精度が高まります。参照情報はチャンクサイズで分割するので、可能な限り同一の知識が同じチャンクに収まるように工夫していきましょう。
大変そうな作業ですが、PDFファイルの情報整理に、GPTを使うアプローチもあります。

PDFファイルの難しいところは、表形式や図が含まれていることです。GPT-4の場合わりと理解してくれる印象ですが、場合によってはデータ加工が必要になるかもしれません。

チャンクサイズの最適化

LangChainのCharacterTextSplitter関数を利用すると、指定したチャンクサイズ(チャンクの文字数)とオーバーラップで分割可能です。

チャンクサイズが小さいと情報が足りない可能性が高まり、チャンクサイズが大きいと関係ない文書が含まれてしまうので、ハルシネーションリスクが高まります。

ただ、GPT-4oを利用する場合、クリティカルに精度に影響する部分ではないと感じます。なぜならGPT-4oの言語理解力が高いため、検索結果が長文で雑多な文書だったりしても、文書内に回答可能な情報さえ入っていれば、わりといい感じに必要な情報を抽出してくれるからです。

ベクトルDBの類似度検索

参考文書の類似度検索は、いくつか手法があります。Microsoftの調査では、ハイブリット検索(キーワード検索とベクトル検索を組み合わせた文書検索)が最も精度が良いとされています。理由は、キーワード検索の明確さと、ベクトル検索の柔軟性を併せ持つためです。

検索エンジンには、Azure AI Searchが使われることが多い印象です。

ベクトルDBの検索アプローチ

上と似ていますが、そもそもの検索アプローチにも工夫の余地があります。例えば、ベクトル検索では質問文と関連文書を検索していますが、一般的に両者は必ずしも似ているとは限りません。本来なら、回答文と関連文書を検索した方が妥当性の高い検索になります。

そのため、HyDE(Hypothetical Document Embeddings:仮の文書の埋め込み)という手法は、質問文から仮の回答をLLMに作成させて、仮回答に似たドキュメントを検索します。

一定の精度改善が見込めますが、LLMの呼び出し回数が増えるため、レスポンス時間が長くます。現状GPTなどのLLMを商用利用する場合の大きな課題は、レスポンス時間の長さです。そのため、やや実用性に欠けると言えるでしょう。

さらに、質問文から検索用のクエリを生成し、そのクエリから関連文書を検索する方式も一般的です。例えば、LLMのプロンプトに、「以下はユーザーの質問文です。ユーザーが求める情報を見つけるための検索用のクエリーを作成してください。」と記載して、LLMに検索クエリを生成させることができます。

ただ、この手法もLLMの呼び出し回数が増えるため、まずはシンプルに動かして精度評価する方が良いでしょう。

LangChainでPDFを参照するRAGをプログラム実装する

LangChainで、PDFの内容を元に回答するチャットボットを実装します。質問を入力すると、自動でPDF内の関連情報を探し出し、答えを文章で返してくれます。
import os
import glob
import openai
from langchain.document_loaders import PyPDFLoader from langchain.text_splitter import CharacterTextSplitter from langchain.embeddings.openai import OpenAIEmbeddings from langchain.vectorstores import Chroma from langchain.chat_models import ChatOpenAI from langchain.prompts import PromptTemplate from langchain.chains import RetrievalQA # PDF文書の読み込み
url = glob.glob('./pdf/*.pdf')
pages_list = [] for i in url: loader = PyPDFLoader(i) pages = loader.load_and_split() #PDFからテキストを抽出、抽出したテキストをページごとに分割 pages_list.extend(pages) # チャンクに分割
text_splitter = CharacterTextSplitter(chunk_size=1500, chunk_overlap=100) docs = text_splitter.split_documents(pages_list) # ベクトル検索用のretrieverを初期化 embeddings = OpenAIEmbeddings() #ベクトルストアへのドキュメントの格納 vectorstore = Chroma.from_documents(docs, embedding=embeddings) retriever = vectorstore.as_retriever() # 質問に関するドキュメントの抽出 context_docs = retriever.get_relevant_documents(query) # 回答用のLLMを初期化 chat = ChatOpenAI(temperature=0)
# プロンプト作成(LLMはこのプロンプトに従い、ユーザーの質問に答える) prompt_template = """ 以下の参考用のテキストの一部を参照して、Questionに回答してください。 {context} Question: {question} Answer: """ PROMPT = PromptTemplate( template=prompt_template, input_variables=["context","question"])
# チャンク情報をLLMに渡して回答文作成 qa_chain = RetrievalQA.from_chain_type( llm=chat, chain_type="stuff", retriever=retriever, #return_source_documents=True, chain_type_kwargs={"prompt":PROMPT} )

# ユーザーからの質問 query = "〇〇サービスの申込方法を教えて"

result = qa_chain(query) print(result["result"])
実行すると、PDFファイルを元にGPTが回答を生成してくれます。

まとめ

どの企業でも、PDFファイルなどの社内文書を保有していると思います。RAGを利用することで、カスタマーサポート、研究、企業内ナレッジベースなど、多岐にわたる分野での応用が可能です。

関連記事:Azure AI Document IntelligenceとLangChainを活用したRAGの実装