PDFファイルを使ったRAGに挑戦(1)
今ではLLMを使う場合にRAG(Retrieval-augmented generation)の技術は必須となってきています。理屈は理解できているのですが、本当に正しく知識をつかえて言えるのか心配ですよね。
今回、SIGNATEのコンペに挑戦しながら、いろんな手法を試してみました。
SIGNATE
SIGNATEでは、Kaggleのようなデータ分析のコンペが開催されています。日本語なので、初心者にはとっつきやすいのと、特に”日本語のデータ分析”ができるので、何度か参加させていただいています。
今回参加したのは、以下のコンペです。

簡単に言うと、企業のレポート(pdfファイル)をデータとして読み取り、そのデータを使って質問にどれだけ正確に答えられるかを競うものです。企業のレポートは19社分あり、質問は固定で100問です。
アプローチ方法
コンペのルールとして、RAGの構築について、以下のように定められています。
- 使用可能なLLMや計算環境、ライブラリ等に制約はありません。
- 手動で回答を作成することは禁止です。コンペで提供された質問を入力として、提供データ(documents.zip)を利用し、自動で結果(質問に対する回答)が出力されるプログラムのみが評価対象となります。
- 1回答あたりの受付可能な最大文字数は54トークンです。これを超えた場合はエラーになりますのでご注意ください。
- 応答時間の制限は80時間です。
自動で回答するプログラムを作れば、それ以外の制約はあまりなさそうです。
実際は細かな試行錯誤をしていたのですが、LLMに情報を渡す方法としては、以下の方法を試しています。
- PDFから単純にテキスト部分を抽出して、コンテキストとしてLLMに渡す。
- PDFを直接渡すことができるLLMに対して、直接質問文とpdfファイルとを渡す。
- PDFを一旦、MarkDown形式に変換して、直接質問文と、mdファイルとを渡す。
- LangChainを使って、mdファイルをベクトルデータベースに保存する。
- LangChainを使って、pdfファイルをベクトルデータベースに保存する。

質問内容の分類やファイルの紐付けなどの”前処理”、結果を整形したり統合したりする”後処理”などもありますが、ここでは与えられた知識(ファイル)をどうやってLLMに渡すかについてのみ言及いたします。
「1」〜「3」はDatabase化していないので、RAGの定義からは外れるかもしれませんが、コンペなのでスコアが良い方を取ろうかと思って、やってみました。
また、「1」は、以下の説明からは割愛し、「2」以降の方法について記述いたします。
PDF対応LLM
PDFを直接入力できるLLMは、2025年1月時点で以下がありました。
- gemini-1.5-pro
- claude-3-5-sonnet-20241022
Geminiは入力token数が、2,097,152 tokensと桁違いに大きいので、今回のpdfをそのまま入力することができました。しかし、Claudeは入力token数の上限が200k tokensとGeminiの約1/10であったため、処理することができませんでした。pdfを分割して渡すなどの方法もやってみましたが、結果が今ひとつなので説明は割愛します。
なお今回は、質問ごとにどのファイルを見るべきかは、前処理でわかっているものとします。
Gemini 1.5 Pro
geminiを使うためのライブラリをインストールします。
pip install google-generativeai
Geminiの場合は、ファイルを一旦アップロードして蓄積(キャッシュ)しておくことができます。今回は全てのファイルをuploadしておきました。
ちなみに、キャッシュは2日後に削除されます。
import google.generativeai as genai
API_TOKEN = <Your API key>
genai.configure(api_key=API_TOKEN)
model = genai.GenerativeModel("gemini-1.5-pro")
#ファイルのアップロード
pdf = genai.upload_file("filename.pdf")
#ファイル取得方法
for f in genai.list_files():
if f.display_name=="poi.pdf": # "poi.pdf"というファイルのファイルオブジェクトを取得する
break
質問とファイル名とを入力して回答を得るコードは以下のようにしました。
import google.generativeai as genai
import os
import threading
import time
MODEL_NAME = "gemini-1.5-pro"
MODEL_NAME_FOR_OUTPUT = "gemini-1-5-pro"
API_TOKEN = "<Your API key>"
def analyze_pdf_with_gemini(pdf_filepath: str,prompt: str):
SYSTEM_PROMPT = f"""
<<< システムプロンプト >>>
質問:{prompt}"""
# LLMの設定
genai.configure(api_key=API_TOKEN)
model = genai.GenerativeModel(MODEL_NAME)
# 対象のファイルを検索
for f in genai.list_files():
if f.display_name==pdf_filepath:
break
response = model.generate_content([
SYSTEM_PROMPT,
f
])
# 消費したトークンの表示
print(f"input token: {response.usage_metadata.prompt_token_count}")
print(f"output token: {response.usage_metadata.candidates_token_count}")
print(f"total token: {response.usage_metadata.total_token_count}")
return ''.join([chunk.text for chunk in response])
Geminiには、システムプロンプトと、ファイルオブジェクトを渡すだけなので、非常に簡単に使うことができます。