💫

LangChainでOpenAI gpt-4o / o1にPDFを入力する方法

に公開

はじめに

どうやら、Open AIのAPIでPDFをアップロードできるようになったっぽいです。

https://x.com/schroneko/status/1902127486374490580

普段はLangChainを利用しているので、そちらでもできるかなと思い、試してみました。

コード全体

下記のリポジトリをご覧ください。
https://github.com/personabb/langchain_pdf_sample/tree/main

準備

環境変数

OpenAIのAPIキーを準備して、.envファイルに設定してください。

OPENAI_APIKEY="sk-proj-xxxxxxxxxxxxxxxxxxxxx"

読み込ませるpdfの準備

今回は、下記のpdfを読み込ませています。

https://speakerdeck.com/personabb/deepseek-r1nolun-wen-karadu-mijie-kubei-jing-ji-shu

記事と同様に実行する場合は、inputs/DeepSeek-R1-paper-asap-r3.pdfとして、上記のPDFを保存してください。

LangChainを利用して、OpenAIのLLMにPDFを読み込ませる方法

コード

https://github.com/personabb/langchain_pdf_sample/blob/main/openai_pdf_langchain.py

下記のコードで実行できます。


import os
import base64
from io import BytesIO
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnableLambda
from dotenv import load_dotenv, find_dotenv

_ = load_dotenv(find_dotenv())
api_key = os.getenv("OPENAI_APIKEY")

def convert_pdf_to_base64(pdf_path):
    """
    Convert a PDF file to a Base64 encoded string.

    :param pdf_path: path to the pdf file
    :return: Base64 string
    """
    with open(pdf_path, "rb") as f:
        pdf_bytes = f.read()
    pdf_str = base64.b64encode(pdf_bytes).decode("utf-8")
    return pdf_str

def prompt_func(data):
    user_input = data["user_input"]
    pdf = data["pdf"]
    pdf_file_path = data["pdf_file_path"]

    message = [
        SystemMessage(content="あなたは日本語を話す優秀なアシスタントです。回答には必ず日本語で答えてください。また考える過程も出力してください。"),
        HumanMessage(
            content=[
                {
                    "type": "text",
                    "text": f"{user_input}"
                },
                {
                    "type": "file",
                    "file": {
                        "filename": f"{pdf_file_path}",
                        "file_data": f"data:application/pdf;base64,{pdf}"
                    }
                },
            ]
        )
    ]

    return message  

model = ChatOpenAI(
    model="gpt-4o",
    openai_api_key=api_key,
    max_tokens=4096,
    temperature=0.001,
    top_p=0.001
)

chain = prompt_func | model | StrOutputParser()

# 読み込ませるpdfファイル
file_path = "inputs/DeepSeek-R1-paper-asap-r3.pdf"
pdf_b64 = convert_pdf_to_base64(file_path)

print("pdfファイルの変換が完了したので、処理を開始します。")

# 質問する内容
query = "PDFは何を解説しているか教えてください。"

#以下でも良い
#output = chain.invoke({"user_input": query, "pdf": pdf_b64, "pdf_file_path": file_path})

#stream出力
output = ""
for chunk in chain.stream({"user_input": query, "pdf": pdf_b64, "pdf_file_path": file_path}):
    print(chunk, end="", flush=True)

簡単な解説

pdfを読み込ませるために、一旦PDFをbase64に変換した上で、それをモデルに入力する形で画像を読み込ませています。
base64でのPDFファイルの投入方法に関しては、下記の公式ドキュメントのAPIパラメータを参考にしました。
https://platform.openai.com/docs/guides/pdf-files?api-mode=chat

curl "https://api.openai.com/v1/chat/completions" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $OPENAI_API_KEY" \
    -d '{
        "model": "gpt-4o",
        "messages": [
            {
                "role": "user",
                "content": [
                    {
                        "type": "file",
                        "file": {
                            "filename": "draconomicon.pdf",
                            "file_data": "...base64 encoded bytes here..."
                        }
                    },
                    {
                        "type": "text",
                        "text": "What is the first dragon in the book?"
                    }
                ]
            }
        ]
    }'

あとは一般的なLangChainの書き方で実装できます。
LangChainの書き方に関しては、下記もご参照ください。
https://zenn.dev/asap/articles/aa587ad8d76105

また、出力にちょっと時間かかるなと思ったので、stream出力にしています。

file_path = "inputs/DeepSeek-R1-paper-asap-r3.pdf"
pdf_b64 = convert_pdf_to_base64(file_path)
query = "PDFは何を解説しているか教えてください。"
for chunk in chain.stream({"user_input": query, "pdf": pdf_b64, "pdf_file_path": file_path}):
    print(chunk, end="", flush=True)

モデルには{"user_input": query, "pdf": pdf_b64, "pdf_file_path": file_path}(ユーザの質問文とPDFのbase64、PDFのファイル名)が入力として与えられ、chainの最初であるprompt_funcで処理されてプロンプトを完成させ、それがモデルに入力され、出力が1tokenずつ出力されるようになっています。

実行結果(gpt-4o)

クエリ:"PDFは何を解説しているか教えてください。"

下記が実行結果です。


(ここから)
このPDFは「DeepSeek-R1」の技術的な背景について解説しています。以下のポイントが含まれています:

  1. 目的: DeepSeek-R1の技術的な面白さを広く知ってもらうことを目的としています(ページ4)。

  2. 歴史: DeepSeekの開発の歴史を振り返り、計算効率と学習の安定性の改善に焦点を当てています(ページ6)。

  3. 技術解説:

    • 強化学習を用いた「DeepSeek-R1-Zero」の開発プロセス(ページ8)。
    • モデル構造や強化学習の仕組みについての詳細(ページ10-12)。
    • LLM(大規模言語モデル)での強化学習の応用(ページ13-23)。
  4. 手法の比較: 従来のPPO(Proximal Policy Optimization)と提案手法GRPO(Group Relative Policy Optimization)の比較を行い、計算効率の向上を説明しています(ページ31-32)。

  5. ルールベース報酬: フォーマット報酬と精度報酬を用いた報酬設定について解説し、AIモデルの効率化を図っています(ページ33-36)。

  6. まとめ: DeepSeek-R1のモデル構造、計算効率の達成、問題解決能力の向上についてまとめています(ページ40)。

この資料は、技術に詳しくない人でも興味を持てるように工夫されています。

(ここまで)


ページ数の部分が若干怪しかったりしていますが、大雑把な概要は把握してくれているっぽいです。

クエリ:"5ページ目の年表を説明してください。図と説明をつなぐ線に着目して、時系列がずれないように正確に解説してください。"

続いて、難易度を上げて、5ページ目の年表について解説してもらうと思います。
下記が実行結果です。


(ここから)

5ページ目の年表について説明します。

この年表は、DeepSeekの歴史を時系列で示しています。以下のように進化しています:

  • 2023年5月: DeepSeek社が設立され、DeepSeek CoderとDeepSeek LLMが開発されました。この時点で、70Bオープンモデルに追いつくことを目指しています。

  • 2023年11月: DeepSeek-V2が登場し、大規模学習に向けた精度と計算効率のトレードオフ改善が行われました。

  • 2024年5月: DeepSeek-V3がリリースされ、大規模学習での精度と安定性の向上が図られました。このバージョンでは、auxiliary-loss-free strategyとMulti Token Predictionが導入されています。

  • 2024年12月: DeepSeek-R1とDeepSeek-R1-Zeroが発表され、強化学習(GRPO)による思考能力の向上と徹底的な計算効率の改善が行われました。

  • 2025年1月: DeepSeek-V3の改良版が登場し、さらなる進化を遂げています。

この年表は、DeepSeekが計算効率の改善と学習の安定性を追求してきた歴史を示しています。

(ここまで)


うーん残念ながら、時系列がずれてしまっていますね。正確には下記です。

ちなみに上記の年表はgemini-2.0-flashでもうまく読み取ってくれなかったので、製作者の作り方が悪いですね。
ちゃんとLLMが見てわかるような資料を作りましょう()

実行結果(o1)

ぬこぬこさんのポストによるとo1でも動くようですので、o1で上記の質問を試してみました。

クエリ:"5ページ目の年表を説明してください。図と説明をつなぐ線に着目して、時系列がずれないように正確に解説してください。"

下記が実行結果です。


(ここから)

以下に、ページ5(スライド左上に「DeepSeekの歴史」と書かれた年表)の図をもとに、図と説明文を正確に対応させながら、時系列がずれないように注意して解説します。まずは全体像をざっくり述べ、その後、線でつながっている各時点のトピックを左(過去)から右(未来)へ順に追います。

────────
【考える過程】

  1. スライド5には、DeepSeek社の設立から始まり、モデルのバージョンアップや技術キーワードが時系列に沿って並ぶ年表が示されています。
  2. タイムライン上には「2023年5月 → 2023年11月 → 2024年5月 → 2024年12月 → 2025年1月」の順で主要イベントが並んでおり、それぞれの下にモデル名や技術キーワードが書かれ、上部や下部に線でつないだ解説枠があります。
  3. 軸となるポイントは「学習効率・計算効率の改善」「大規模学習の精度向上」「思考能力の向上(強化学習)」など。
  4. それらのキーワードが矢印や線で結ばれており、特に2024年12月付近の“auxiliary-loss-free strategy / Multi Token Prediction”や、2025年1月付近の“強化学習 GRPO”などが重要な位置付けになっています。
  5. この流れを時系列通りにまとめると、以下のように整理できます。

────────
【年表の解説】

■ 2023年5月
・「DeepSeek社 設立」
┗ いちばん左端に位置するスタート地点です。社名を冠したLLM開発を本格的に始める背景として示されています。

■ 2023年11月
・「DeepSeek Coder」および「DeepSeek LLM」
┗ ここで“当時の70Bオープンモデルに追いつく”と書かれており、オープンソース系大型言語モデルに匹敵する性能を目指したことが示されています。下部の細かい記載(DeepSeekMath、DeepSeek MoEなど)は、この時期から研究開発された周辺技術のことを指しており、年表の中段付近で矢印や小さな吹き出しによって連動している様子がうかがえます。

■ 2024年5月
・「DeepSeek-V2」
┗ 「大規模学習に向けた精度と計算効率のトレードオフ改善」と書かれているとおり、モデルパラメータを増やしつつ、学習コストを減らす技術を盛り込んだバージョンです。年表の下側には、DeepSeek-V2.5やDeepSeek-Coder-V2などのマイナーアップデートも位置づけられていることが示唆されています(V2の派生版と思われる記述が小さく見えます)。

■ 2024年12月
・「DeepSeek-V3」
┗ 上の吹き出しとして「Multi Token Prediction」「auxiliary-loss-free strategy」といった新技術が示されています。図中の説明文には“大規模学習での精度と安定性の向上”とあり、V3ではさらなる改良が行われたことがわかります。ここでも引き続き、計算効率と学習安定性を両立させる工夫がキーポイントとされています。

■ 2025年1月
・「DeepSeek-R1-Zero / DeepSeek-R1」
┗ 最も右端に位置する最新版のリリースを示す部分です。上部には「強化学習 GRPO」「強化学習による思考能力の向上と徹底的な計算効率の改善」という説明があり、R1系列は大規模言語モデルに強化学習を取り入れることで推論能力(思考過程)を大きく伸ばしつつ、学習コスト削減にも成功していることが示唆されています。

────────
【まとめ】

スライド5の年表は、DeepSeek社が2023年5月に設立されてから、各バージョン(V2、V3、R1)や関連技術(Multi Token Prediction、MoEなど)の導入ポイントを軸にして、「精度」「学習・計算効率」「強化学習による思考力向上」の3点を重点的に発展させてきた歴史を表しています。図と説明をつなぐ矢印や線は、時期ごとにフォーカスしている技術テーマ(例:大規模学習、auxiliary-loss-free strategy、強化学習 GRPOなど)を指すように配置されており、左から右へ時系列順に読み解くと、技術進化の順番と内容がずれなく追えるようになっています。

(ここまで)


え!?すご
期待してなかったけど、o1では完璧に読み取ってくれました。(僕の資料は悪くない・・・?)
(もしかしたら、gemini-2.0-flashよりも、PDFの読み取りは強いかもしれないですね。)

コスト感

ただし、o1を利用する場合、たった一回のcallで $0.6持っていかれるので、そこそこ高いです。
(input $0.45、output $0.15)
ちなみにgpt-4oだと、$0.03くらいなので、全然違います。

まとめ

性能はさておき、ある程度の回答はPDFをそのまま投げたら帰ってくるというのは、とても便利ですね。
gptでも、この機能が開放されてよかったです。

おすすめ書籍

LangChainとLangGraphによるRAG・AIエージェント[実践]入門
ChatGPT/LangChainによるチャットシステム構築[実践]入門

今回は、さまざまなモデルを統一的に利用するために、LangChainを利用しています。
langchainに関しては、こちらの書籍を読めば大体のことはできるようになりますので、おすすめです。

Discussion