2026.09.14

PDFを投げるとトークンはいくらかかる? Claude・ChatGPT・Gemini APIのファイル入力を比較し、anydoc / MarkItDown / PaddleOCRで前処理してみた

こんにちは。次世代システム研究室のL.W.です。

「PDFや議事録のWordをそのままLLM APIに投げた」ということが増えてきました。三社(Anthropic Claude・OpenAI・Google Gemini)とも「ファイルをそのまま渡せる」ようになっていますが、受け付ける形式、内部での処理の流れ、そしてトークンコストがそれぞれ微妙に違います。ここを押さえずに大量投入すると、請求書を見て驚くことになります。 今回は三社の公式ドキュメントを突き合わせて整理し、あわせてローカルでMarkdownに前処理する OSS(anydoc・MarkItDown・PaddleOCR)でどこまでコストを削れるか、Node.jsで検証コードを書いて確かめてみます。

1.三社APIのファイル入力を使ってみた

1.1 「ファイルを渡す」には3つの方式がある

三社とも、ファイルをリクエストに含める方法は大きく3つに収斂しています。まずここを揃えておくと比較しやすくなります。

方式 Claude (Messages API) OpenAI (Responses API) Gemini API
① base64 インライン document.source.type: "base64" input_file.file_data(data URL) inline_data / type: "document", data
② 公開URL参照 document.source.type: "url" input_file.file_url 直接URL指定は不可(Files APIにアップロードして uri 参照)
③ 事前アップロード Files API → file_id Files API(purpose: "user_data")→ file_id Files API → file_uri
ストレージ上限 500MB/ファイル、1TB/組織 (ファイル単位 50MB、リクエスト合計も 50MB) 2GB/ファイル、20GB/プロジェクト
保持期間 削除するまで(expires_in_seconds で1時間〜90日の自動失効も設定可) 削除するまで 48時間で自動削除
アップロード・保管の料金 無料(推論時の入力トークンのみ課金) 無料(同上) 無料(同上)
出典:Claude Files API / OpenAI File inputs / Gemini Document understanding
 
Claudeの Files API は2025年4月にベータ(files-api-2025-04-14ヘッダー)で始まりましたが、現在はGAになりベータヘッダー不要です。SDKも client.beta.files ではなく client.files で呼びます。逆にGeminiの Files API は「48時間で消える」一時置き場なので、繰り返し参照する用途では再アップロードの運用が必要です。

1.2 受け付けるファイルの種類

ここが三社で一番差が出るところです。特に Office系(docx / xlsx / pptx)をネイティブに受け付けるのはOpenAIだけです。
形式 Claude OpenAI Gemini
PDF ○ document ブロック(テキスト+ページ画像) ○ input_file(テキスト+ページ画像) ○ document(ネイティブビジョン)
画像(JPEG/PNG/GIF/WebP) ○ image ブロック ○ input_image ○ image
プレーンテキスト(txt/md/csv) ○ text/plain として document ブロック可 ○ テキスト抽出のみ ○ プレーンテキスト扱い(構造は失われる)
Word / PowerPoint(docx/pptx) × テキストかPDFに変換が必要 ○ テキストのみ抽出(埋め込み画像・図表は無視) × テキスト系MIMEのみ対応
Excel / CSV(xlsx/csv/tsv) △ code execution tool に container_upload で渡す ○ 先頭1,000行/シートを解析+要約メタデータ付与 △ csv はテキスト扱い、xlsx は×
コードファイル(py/js/json/…) △ テキストとして本文に埋め込む ○ 多数の拡張子をテキスト抽出 ○ 一部MIMEをテキスト扱い
音声 / 動画 × ×(input_file 非対応) ○(本記事では対象外)
OpenAIの docx/pptx 対応は便利ですが、公式ドキュメントに「非PDFファイルは埋め込み画像やグラフをモデルのコンテキストに入れない。図表の忠実度が必要ならPDFに変換してから送れ」と明記されています。つまり「docxを直接送れる」=「docxの中身を全部見てくれる」ではない点に注意です。Claudeも「画像を含むdocxはPDFにしてから送ると、組み込みの画像解析とcitationが使える」と案内しています。

1.3 リクエストの上限

  Claude OpenAI Gemini
リクエストサイズ 32MB(PDF以外の内容も含む合計) 50MB/ファイル、複数ファイル合計も50MB 50MB/PDF(インライン・Files API共通)
PDFページ数 600ページ(コンテキスト1M未満のリクエストでは100ページ) ドキュメント上の明示なし(トークン上限に依存) 1,000ページ
PDFの制約 パスワード・暗号化なしの標準PDF ビジョン対応モデルのみページ画像を処理 大きいページは 3072×3072 に縮小、小さいページは 768×768 に拡大
出典:Claude PDF support / OpenAI File inputs / Gemini Technical details
 
Claudeのドキュメントには「密度の高いPDF(小さいフォント、複雑な表、重い図)はページ上限に達する前にコンテキストを埋め尽くすことがある」「Files API経由でもページ上限前に失敗することがある」と正直に書かれています。ページ数の上限は”保証”ではなく”天井”と読むべきです。

2. 処理の流れとトークンコスト

2.1 PDFは「テキスト+ページ画像」の二重投入

三社とも、PDFの内部処理は驚くほど似ています。各ページを画像に変換し、同時にテキストを抽出して、両方をモデルに渡すという流れです。だからこそ図表やグラフを読めるわけですが、その代償としてトークンが二重にかかります。

PDF (N ページ)
  │
  ├─► ① ページごとに画像化 ──────► 画像トークン(ページ数 × 画像1枚分)
  │
  └─► ② テキストレイヤーを抽出 ──► テキストトークン(文字量に比例)
                                          │
                                          ▼
                                  モデルの入力コンテキスト
                                  (①+②+プロンプト+システム指示)

ただし、この②のテキスト分を課金するかどうかで三社が分かれます。

  Claude OpenAI Gemini(Gemini 3以降)
ページ画像 通常の画像コストと同じ計算 (≒ 幅×高さ÷750 トークン、最大長辺1568pxで約1,568トークン。Opus 4.7以降は高解像度対応でさらに増える) 通常の画像コスト(タイル方式)。detail パラメータで low/high/auto を選択可。GPT-5.6以降は auto=high、それ以前は low 1ページ=258トークン固定(IMAGE モダリティとして計上)。media_resolution で low/medium/high を選択可
抽出テキスト 課金対象。内容密度により 1,500〜3,000トークン/ページ 課金対象(detail はテキスト部分には影響しない) 課金対象外(ネイティブテキスト分は無料と明記)
1ページあたり目安 約3,000〜4,500トークン (Bedrock向けドキュメントでは「3ページで約7,000トークン」と記載) テキスト量+画像タイル(モデル・detail依存) 約258トークン+プロンプト
事前見積り messages.count_tokens POST /v1/responses/input_tokens(PDF対応) models.count_tokens

出典:Claude: Estimate your costs / Claude: Vision / OpenAI: PDF detail levels / OpenAI: Counting tokens / Gemini: Gemini 3 models

 

ここから読める重要な差は二つです。

  • Geminiは「ページ単価」が桁で安い。テキスト抽出分を課金せず、ページ画像も258トークン固定なので、100ページのPDFでも約26,000トークンで収まります。同じPDFをClaudeに渡すと30〜45万トークンになり得ます(単価の違いは別として、トークン数の時点で10倍以上の差)。
  • OpenAIは detail のデフォルトがモデル世代で変わる。GPT-5.6以降で何も指定しないと high になるため、旧モデルから移行すると同じコードでPDFのコストが跳ねます。テキスト中心のPDFなら detail: "low" を明示するのが安全です。

2.2 Claudeの流れをコードで見る

Claudeの公式ドキュメントによると、リクエストは次のように処理されます。①システムがドキュメントを抽出(各ページを画像化し、テキストも抽出して画像に添える)→ ②Claudeがテキストと画像の両方を解析 → ③PDFを参照しながら回答、という3ステップです。レスポンスの usage に入力トークン数が返るので、コスト検証はここを見ればよいです。

// Claude: Files API にアップロードして file_id で参照(再利用向き)
const uploaded = await anthropic.files.upload({
  file: await toFile(fs.createReadStream("report.pdf"), "report.pdf", {
    type: "application/pdf",
  }),
});

const res = await anthropic.messages.create({
  model: "claude-opus-5",           // 執筆時点のドキュメント記載モデル。最新に差し替えてください
  max_tokens: 1024,
  messages: [{
    role: "user",
    content: [
      // PDF はテキストより前に置くと精度が上がる(公式のベストプラクティス)
      { type: "document", source: { type: "file", file_id: uploaded.id },
        cache_control: { type: "ephemeral" } },   // 同じPDFに何度も質問するならキャッシュ
      { type: "text", text: "この文書の要点を3つ挙げてください。" },
    ],
  }],
});

console.log(res.usage);
// → { input_tokens: 45000, output_tokens: 300, ... } のような形で返る

公式サンプルでは、Claude 3 Model Card の追補PDF(十数ページ)で input_tokens: 45000 という数値が例示されています。1ページ3,000トークン前後という目安と整合します。

2.3 OpenAIの流れ:ファイル種別で分岐する

OpenAIは input_file の中でファイル種別ごとに処理が分岐します。公式ドキュメントの記載をまとめると以下の通りです。

input_file
  ├─ PDF ─────────────► テキスト抽出 + ページ画像(detail: auto/low/high)
  ├─ docx/pptx/txt/code ► テキスト抽出のみ(画像・図表は捨てられる)
  └─ xlsx/csv/tsv ────► 「スプレッドシート拡張」
                         先頭1,000行/シートを解析し、モデル生成の要約+ヘッダー情報を付与
                         (集計・結合・グラフ化が要るなら Hosted Shell を使えと案内)
// OpenAI: Responses API に base64 で直接投入。テキスト中心のPDFなら detail を明示
const res = await openai.responses.create({
  model: "gpt-5.5",                 // 執筆時点のドキュメント記載モデル
  input: [{
    role: "user",
    content: [
      { type: "input_file",
        filename: "report.pdf",
        file_data: `data:application/pdf;base64,${pdfBase64}`,
        detail: "low" },            // 省略すると GPT-5.6 以降は high 扱い
      { type: "input_text", text: "この文書の要点を3つ挙げてください。" },
    ],
  }],
});
console.log(res.usage);             // input_tokens / output_tokens

2.4 Geminiの流れ:PDFだけがビジョン、他はテキスト

Geminiのドキュメントは明確で、「ドキュメントビジョンが意味を持つのはPDFのみ。TXT/Markdown/HTML/XMLなど他のMIMEも渡せるが純粋なテキストとして抽出され、レンダリングされた見た目(表・図・タグ・Markdown書式)は失われる」と書かれています。

// Gemini: Files API にアップロード(48時間で消える)→ uri で参照
const file = await ai.files.upload({ file: "report.pdf",
                                     config: { mimeType: "application/pdf" } });

const res = await ai.models.generateContent({
  model: "gemini-3.8-flash",        // 執筆時点のドキュメント記載モデル
  contents: [
    { fileData: { fileUri: file.uri, mimeType: file.mimeType } },
    { text: "この文書の要点を3つ挙げてください。" },
  ],
});
console.log(res.usageMetadata);
// promptTokenCount と promptTokensDetails(modality: IMAGE / TEXT ごとの内訳)が返る
// Gemini 3 以降、PDFページ分は IMAGE モダリティに計上され、抽出テキスト分は課金されない

なお、Geminiでは新しい Interactions API(client.interactions.create)がドキュメントのデフォルト表示になっていますが、まだBetaで「本番は generateContent を推奨」とされているため、本記事は generateContent で書いています。

3. 前処理ツール(anydoc / MarkItDown / PaddleOCR)で削ってみた

3.1 なぜ前処理するのか

2章の通り、PDFをそのまま渡すと「テキスト+画像」の二重コストがかかります。しかし社内文書の多くはテキスト主体で、図表の”見た目”までは要らないものです。それなら先にMarkdownへ変換してテキストだけ送れば、トークンは文字数分だけで済みます。加えて、ClaudeやGeminiが受け付けないdocx/xlsx/pptxも、Markdownにすれば三社共通で送れます。この目的で今よく使われるOSSを3つ並べます。

  anydoc(Firecrawl) MarkItDown(Microsoft) PaddleOCR(Baidu)
実装 / ライセンス Rust、MIT。Node.js / Python / WASM バインディング Python、MIT Python(PaddlePaddle)、Apache-2.0。C++ / Java / JS(PaddleOCR.js)/ MCP server あり
得意な入力 Office系(doc/docx/ppt/pptx/xls/xlsx/odt/ods/odp/rtf/epub/csv)+テキストPDF PDF・Office・HTML・CSV/JSON/XML・画像(EXIF/OCR)・音声・ZIP・YouTube URL など幅広い スキャンPDF・画像。PaddleOCR-VL-1.6(0.9B VLM)/ PP-StructureV3 で表・数式・グラフを含むレイアウト解析。3.5.0からOffice→Markdownも
スキャン(画像のみ)PDF ×(Unsupported エラー。ホスト版 Firecrawl Parse でOCR) △ 標準は不可。markitdown-ocr プラグイン(LLM Vision)か Azure Document Intelligence / Content Understanding で対応 ○ これが本領。100言語超、日本語対応
速度 中央値 4.4ms/文書(自社ベンチ、Ryzen 9 9950X3D)。MLモデル不使用 同ベンチで中央値 134.8ms GPU推奨(VL系)。PP-OCRv6 は CPU で 5.2倍高速化と発表
出力 GFM Markdown。埋め込み画像はaltテキスト+bytesを保持 Markdown Markdown / JSON(座標付き)/ DOCX
依存・運用 ネイティブバイナリ1つ。npx @firecrawl/anydoc report.docx で即動く pip install 'markitdown[all]'。形式ごとにoptional依存 モデルDL・Paddle/推論バックエンドの整備が必要。重い

出典:firecrawl/anydoc README / microsoft/markitdown README / PaddlePaddle/PaddleOCR README

 

anydocのREADMEには、100文書・14形式でLLM判定(Claude Sonnet 5)を使った自社ベンチが載っており、MarkItDown(6/14形式対応、スコア65)や docling・pandoc・unstructured と比べてanydocが全形式で最高スコア(81)とされています。ただしこれはanydoc側が作ったベンチで、コーパスも非公開なので、鵜呑みにせず自分の文書で試すのが筋です。逆にPaddleOCRのREADMEにも「PP-OCRv6は主要VLM(Qwen3-VL-235B、GPT-5.5)を超える」という自己申告の数字が並んでいます。どちらも「自社ベンチ」と割り切って読みましょう。 役割分担としては、次のように整理できます。

  • anydoc:テキストベースのOffice文書・PDFを大量に、ミリ秒で、依存ゼロでMarkdownにしたい。Lambda等の軽量ランタイムにも載せやすい。
  • MarkItDown:形式が雑多(ZIP、音声、HTML、画像のメタデータまで)で、Pythonパイプラインに組み込みたい。OCRは外部(LLMかAzure)に任せる設計。
  • PaddleOCR:スキャン文書・紙の写真・レイアウトの複雑な帳票を、GPUのあるバッチ処理でMarkdown/JSON化したい。日本語を含む多言語OCRがローカルで完結する。

3.2 Node.jsで検証コードを書いてみた

3.2.1 やりたいこと

同じPDFを (a) そのまま三社に投げた場合と、(b) anydocでMarkdownに変換してテキストだけ投げた場合で、入力トークン数がどれだけ違うかを usage から読み取ります。スキャンPDFは anydoc が Unsupported を返すので、その場合は PaddleOCR(別プロセス)でMarkdownにしたものを流す分岐を入れます。

npm install @anthropic-ai/sdk openai @google/genai @firecrawl/anydoc
# PaddleOCR 側(スキャンPDF用、別環境)
pip install "paddleocr[doc-parser]"   # 詳細は公式ドキュメント参照。GPU 推奨

3.2.2 コード

import fs from "fs";
import { execFileSync } from "child_process";
import Anthropic, { toFile } from "@anthropic-ai/sdk";
import OpenAI from "openai";
import { GoogleGenAI } from "@google/genai";
import { toMarkdown } from "@firecrawl/anydoc";

const anthropic = new Anthropic();
const openai = new OpenAI();
const ai = new GoogleGenAI({});

const PROMPT = "この文書の要点を3つ、日本語で箇条書きにしてください。";
const MODELS = {
  claude: "claude-opus-5",
  openai: "gpt-5.5",
  gemini: "gemini-3.8-flash",
};

// ---------- (b) 前処理:anydoc → 失敗したら PaddleOCR ----------
async function toMarkdownWithFallback(pdfPath) {
  try {
    // テキストPDF・Office文書はここで数msで終わる
    return { text: await toMarkdown(pdfPath), via: "anydoc" };
  } catch (e) {
    if (e.code !== "Unsupported") throw e;   // 画像のみPDFは Unsupported
    // スキャンPDF:PaddleOCR (PP-StructureV3 / PaddleOCR-VL) で Markdown 化
    execFileSync("paddleocr", ["doc_parser", "-i", pdfPath, "--save_path", "./out"]);
    const md = fs.readdirSync("./out").filter(f => f.endsWith(".md"))
      .map(f => fs.readFileSync(`./out/${f}`, "utf8")).join("\n\n");
    return { text: md, via: "paddleocr" };
  }
}

// ---------- (a) PDF をそのまま各社へ ----------
async function claudePdf(pdfPath) {
  const up = await anthropic.files.upload({
    file: await toFile(fs.createReadStream(pdfPath), "doc.pdf", { type: "application/pdf" }),
  });
  const r = await anthropic.messages.create({
    model: MODELS.claude, max_tokens: 512,
    messages: [{ role: "user", content: [
      { type: "document", source: { type: "file", file_id: up.id } },
      { type: "text", text: PROMPT },
    ]}],
  });
  return r.usage.input_tokens;
}

async function openaiPdf(pdfPath, detail = "low") {
  const b64 = fs.readFileSync(pdfPath).toString("base64");
  const r = await openai.responses.create({
    model: MODELS.openai,
    input: [{ role: "user", content: [
      { type: "input_file", filename: "doc.pdf",
        file_data: `data:application/pdf;base64,${b64}`, detail },
      { type: "input_text", text: PROMPT },
    ]}],
  });
  return r.usage.input_tokens;
}

async function geminiPdf(pdfPath) {
  const f = await ai.files.upload({ file: pdfPath, config: { mimeType: "application/pdf" } });
  const r = await ai.models.generateContent({
    model: MODELS.gemini,
    contents: [{ fileData: { fileUri: f.uri, mimeType: f.mimeType } }, { text: PROMPT }],
  });
  // Gemini 3 以降:PDF ページは IMAGE モダリティ、抽出テキストは非課金
  return { total: r.usageMetadata.promptTokenCount,
           byModality: r.usageMetadata.promptTokensDetails };
}

// ---------- (b) Markdown テキストだけを各社へ ----------
async function claudeText(md) {
  const r = await anthropic.messages.create({
    model: MODELS.claude, max_tokens: 512,
    messages: [{ role: "user", content: `${md}\n\n${PROMPT}` }],
  });
  return r.usage.input_tokens;
}
async function openaiText(md) {
  const r = await openai.responses.create({ model: MODELS.openai, input: `${md}\n\n${PROMPT}` });
  return r.usage.input_tokens;
}
async function geminiText(md) {
  const r = await ai.models.generateContent({ model: MODELS.gemini, contents: `${md}\n\n${PROMPT}` });
  return r.usageMetadata.promptTokenCount;
}

// ---------- 実行 ----------
const pdf = process.argv[2];
const t0 = performance.now();
const { text: md, via } = await toMarkdownWithFallback(pdf);
console.log(`[preprocess] via=${via} ${Math.round(performance.now() - t0)}ms, ${md.length} chars`);

console.table({
  claude: { pdf: await claudePdf(pdf),       markdown: await claudeText(md) },
  openai: { pdf: await openaiPdf(pdf, "low"), markdown: await openaiText(md) },
  gemini: { pdf: (await geminiPdf(pdf)).total, markdown: await geminiText(md) },
});

3.2.3 ポイント

  • 比較しているのは入力トークン数です。単価は三社・モデルごとに違うので、最終的な金額は各社の料金表(Claude / OpenAI / Gemini)と掛け合わせてください。
  • OpenAIは detail: "low" で測っています。high にすると画像分が増えるので、図表の読み取りが要る場合は両方測るのが正確です。
  • anydoc は 画像のみのPDFには Unsupported を返す設計です。そこで PaddleOCR に落とすフォールバックを入れています。PaddleOCR 側は CLI 名・サブコマンドがバージョンで変わるため、公式の PP-StructureV3 / PaddleOCR-VL ドキュメントで確認してください。

3.3 実行結果

テキストレイヤーのあるPDF(四半期レポート・10ページ)と、スキャンPDF(請求書・3ページ)の2種類で実行しました。前処理はそれぞれ anydoc と PaddleOCR(PP-StructureV3、PaddleOCR 3.7.0)に振り分けられています。

3.3.1 テキストPDF(10ページ、10,600文字)

$ node compare.mjs samples/quarterly_report.pdf
[preprocess] via=anydoc 638ms, 10600 chars

┌─────────┬──────────┬──────────┐
│ (index) │   pdf    │ markdown │
├─────────┼──────────┼──────────┤
│ claude  │  20,807  │   5,338  │
│ openai  │   5,637  │   3,667  │
│ gemini  │   2,589  │   3,813  │
└─────────┴──────────┴──────────┘

3.3.2 スキャンPDF(3ページ、3,263文字)

$ node compare.mjs samples/scanned_invoice.pdf
[preprocess] via=paddleocr 184673ms, 3263 chars

┌─────────┬──────────┬──────────┐
│ (index) │   pdf    │ markdown │
├─────────┼──────────┼──────────┤
│ claude  │   4,760  │   2,524  │
│ openai  │   2,368  │   1,976  │
│ gemini  │     783  │   1,730  │
└─────────┴──────────┴──────────┘

3.3.3 入力トークン数の比較

「PDFをそのまま渡す」を基準に、Markdown化でどれだけ変わったかをまとめます(マイナスが削減、プラスが増加)。

サンプル 前処理   Claude OpenAI(detail: low) Gemini
テキストPDF
10ページ
anydoc
638ms
PDF直投入 20,807 5,637 2,589
Markdown 5,338 3,667 3,813
差分 −74% −35% +47%
スキャンPDF
3ページ
PaddleOCR
約185秒
PDF直投入 4,760 2,368 783
Markdown 2,524 1,976 1,730
差分 −47% −17% +121%

3.3.4 読み取れること

  • Claudeは前処理の恩恵が最大。PDF直投入では10ページで20,807トークン、1ページあたり約2,080トークンで、2.1節の「テキスト1,500〜3,000+画像」という公式の目安と整合します。Markdown化で74%削減できました。
  • OpenAIも一貫して削減(−17〜−35%)。detail: "low" のためPDF直投入でもClaudeほど高くありませんが、Markdownの方が常に安い結果です。
  • Geminiだけ一貫して逆転。ページ画像258トークン固定+抽出テキスト非課金なので、10ページで2,589トークン(≒258×10+プロンプト)と公式の記載どおりでした。テキスト量が多いページほどPDF直投入が有利になり、今回はMarkdown化で1.5〜2.2倍に増えています。ただし、テキスト非課金はGemini 3以降の仕様なので、旧モデルでは結果が変わります。
  • 前処理コストの差は3桁違う。anydoc(テキストレイヤーあり)は638msでほぼ無視できる一方、PaddleOCR(スキャン)は約185秒かかりました。内訳はほぼPP-StructureV3のパイプラインロードとOCR推論で、初回はモデルロードが支配的です。プロセス常駐にすればページあたり数秒程度に収まるはずですが、「テキストレイヤーの有無でルーティングする」設計は必須と言えます。

実装メモ:PaddleOCRは paddleocr-env(PaddleOCR 3.7.0)に隔離して paddle_ocr.py 経由で子プロセス実行し、PADDLE_PYTHON 環境変数でインタプリタのパスを上書きできるようにしています。

3.4 分析

Geminiは「そのまま投げる」でも安いが、テキスト化するとClaude/OpenAIの方が得な場面が出る

興味深いのは、Markdown化するとGeminiの優位が消える点です。Geminiの安さは「ページ画像258トークン固定+テキスト非課金」というPDF専用の特別扱いに由来しており、テキストとして渡すと他社と同じ「文字数分の課金」になります。一方、Claude/OpenAIはPDF直投入が高い分、Markdown化の効果が桁で出ます。前処理の投資対効果はClaude/OpenAIの方が大きい、というのが結論です。

前処理で失うもの

安くなる代わりに、次のものを捨てていることを自覚する必要があります。

  • 図・グラフ・写真の内容:anydoc/MarkItDownは画像をaltテキスト(または説明生成)に置き換えます。「このグラフの傾向は?」という質問には答えられなくなります。
  • レイアウト情報:段組・ページ内の位置関係・押印・手書き。PaddleOCRは座標付きJSONを出せるので、ここは一部補えます。
  • citation:ClaudeのPDF document ブロックはページ単位のcitationを返せますが、テキスト化するとその対応は自前で管理することになります。

実務的な振り分け

文書の性質 推奨
テキスト主体のOffice文書・PDF(議事録、仕様書、契約書) anydoc でMarkdown化 → 任意のモデルへ。最安・最速・形式非依存
図表の読み取りが本質の文書(決算資料、技術図面、スライド) PDFのまま渡す。コスト重視ならGemini、citationや高解像度が要るならClaude(Opus 4.7以降)、Office直投入の手軽さならOpenAI(ただし図は落ちる)
スキャン・紙の写真・帳票 PaddleOCR(PP-StructureV3 / PaddleOCR-VL)でMarkdown/JSON化してから渡す。VLM直投入より安く、日本語もローカル完結
同じ大きなPDFに何度も質問 Files API+prompt caching(Claude)/ context caching(Gemini)。Geminiは48時間で消える点に注意

セキュリティ面の注意

  • Claudeの Files API はワークスペース全体で共有されます。エンドユーザーから file_id を受け取ってそのまま使うと、他ユーザーのファイルを読める穴になります。公式も「file_id は必ずサーバー側の参照として扱い、マルチテナントならワークスペースを分けよ」と警告しています。
  • MarkItDown はREADME冒頭で「プロセスの権限でI/Oする。信頼できない入力は convert_local() / convert_stream() など最も狭いAPIで扱え」と注意しています。URL変換を許すとSSRFの入口になり得ます。

4.まとめ

三社の「ファイル添付」は入口(base64 / URL / Files API)こそ揃ってきましたが、受け付ける形式とPDF1ページの値段は依然として大きく違います。

1. 三社の整理

  Claude OpenAI Gemini
PDF処理 テキスト+ページ画像、両方課金 テキスト+ページ画像(detail可変)、両方課金 ページ画像258トークン固定、テキスト非課金
Office直投入 ×(変換必須) ○(テキストのみ) ×(変換必須)
上限 32MB / 600p(or 100p) 50MB 50MB / 1,000p
Files API保持 無期限(失効設定可) 無期限 48時間
強み citation、高解像度(Opus 4.7+)、prompt caching 形式の広さ、スプレッドシート拡張 ページ単価の安さ、1,000ページ

2. 「そのまま投げる」か「Markdownにしてから投げる」か

PDF直投入の価値は図表を”見て”もらえることにあり、その対価が二重トークンです。図表が要らない文書は anydoc でミリ秒でMarkdown化すれば、Claude/OpenAIで桁のコスト削減になります。Geminiは直投入でも安いので、前処理の効果は相対的に小さくなります。


3. 前処理ツールの使い分け

anydocはテキストベースのOffice/PDFを高速・軽量に、MarkItDownは雑多な形式をPythonパイプラインで、PaddleOCRはスキャン文書をローカルOCRで、という分担です。anydocはスキャンPDFを明示的に Unsupported で拒否するので、PaddleOCRへのフォールバックを最初から組み込んでおくと運用が楽になります。


4. 導入前にやること

三社ともトークンカウントAPIを持っています(Claude count_tokens、OpenAI /v1/responses/input_tokens、Gemini count_tokens)。本番投入前に、自分の代表的な文書10件ほどで「直投入」と「Markdown化」の両方を測っておくと、モデル選定と前処理の要否を数字で決められます。OpenAIは detail のデフォルトがモデル世代で変わる点、Geminiは Files API が48時間で消える点、Claudeは Files API がワークスペース共有である点を、それぞれ設計に織り込んでおきましょう。

5.最後に

次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。 皆さんのご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事