6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GENDA インターン体験記(入江):表データを抽出するOCRエージェントを作った話

Last updated at Posted at 2025-12-21

はじめに

GENDAのインターンで、メーカーごとにフォーマットが異なる注文書・請書から表データを抽出するAIエージェントを開発しました。様々なOCRを比較検討し、最終的に「段階的フォールバック」の仕組みで精度とコストを両立させた話を書きます。

この記事で書くこと

  • 複数のOCRツール(Mistral OCR、PyMuPDF、GPT-4o Visionなど)の比較
  • コストを抑えつつ精度を確保する段階的フォールバックの設計
  • GENDAでのインターンについて

自己紹介

株式会社GENDAのFE/BE開発部でインターンをしている入江です。東京大学工学部機械工学科の4年生で、来年GENDAに入社予定となっています。

入社のきっかけ

3年生の夏に東京大学発のエンジニアサークル UTTC(UTokyo Tech Club)に6期生として入り、Webアプリケーション開発について学びました。

その後UTTCのカリキュラムを終えて、UTTC経由でGENDAのインターンをさせていただくことになりました。

インターン先の候補はいくつかあったのですが、裁量が大きくエンジニアとして成長できそうなこと、エンタメ企業なのでユーザー目線でサービスを考えられること、そしてエンタメ業界に興味があったことからGENDAを選びました。

今回の記事では3月から6月まで関わっていた、オンクレ(オンラインクレーンゲーム)のAI Agent開発についてお話していきます。

プロジェクトの背景

スクリーンショット 2025-12-21 3.01.44.png

GENDAのオンクレ事業では、商品の注文情報を管理する業務があります。

具体的には、商品を注文する際に注文書を送付し、メーカーからは請書が返送されます。
これらの注文書・請書の情報を一つのエクセルフォーマットに集約するという業務です。

しかし、この業務にはいくつかの問題がありました:

課題 詳細
フォーマットがバラバラ メーカーごとに表の配置・文言・拡張子が異なる
文言の揺れ 同じ意味でも「商品名」「景品名」「摘要」など表記が違う
属人化 特定の担当者しか対応できない
手入力の負荷 手作業で転記するため時間がかかる

そこで、様々なフォーマットから表を抽出するAIエージェントを一から開発することになりました。

開発体制

メンターの方にサポートいただきながら、基本的には一人で要件定義から実装まで担当しました。担当者へのヒアリング → 要件定義 → 実装 → リリースという流れで進め、現在は実際の業務で導入されています。

アプリケーションの仕様

技術スタック

  • 言語: Python
  • フロントエンド: Streamlit
  • LLM: GPT-4o

オンクレ.png

処理フロー

1. ユーザーがファイルをアップロード
2. 拡張子を判別(Excel / PDF / PNG)
3. 拡張子ごとに適切な方法で表を抽出
4. 指定のフォーマットに変換して出力

文言の揺れへの対応

抽出した表データを指定フォーマットに変換する際、「商品名」「景品名」「摘要」といった文言の揺れに対応する必要があります。

LLMに対応関係を判断させることも検討しましたが、以下の理由からconfigファイルで定義する方針にしました:

  • 同じ言葉でもメーカーによって別の項目を指すケースがある
  • 項目がずれると業務に支障が出る
  • メーカーが新規追加されることは稀

OCRの技術選定

現在はExcel・PDF・PNGの3つの拡張子に対応していますが、ここに至るまでに様々な試行錯誤がありました。

Excelの場合(簡単)

Excelファイルは比較的簡単です。行ごとにテキストを取り出してLLMに渡し、ヘッダー行の位置を特定させることで表を抽出しています。

PDF・PNGの場合(難しい)

問題はPDFとPNGでした。正確なテキストデータを構造を保ったまま抽出する必要があります。

試したこと①:Mistral OCR

当時リリースされたばかりで高性能と話題だったMistral OCRを試しました。
Mistral OCRはマルチモーダルな文書からテキストを抽出しマークダウンとして出力可能なAPIです。

結果: うまくいかず

  • 欲しいデータが画像として出力されてしまう
  • 日本語の文字認識精度が低い

試したこと②:各種OCRライブラリ

PyOCR(Tesseract)など、他のOCRも試しました。

結果: うまくいかず

注文書には半角カタカナが多用されていますが、日本語対応のOCRでも半角カタカナには弱いものが多いです。また、表の枠線が薄いと構造が崩れる問題もありました。

試したこと③:PyMuPDF

PDFのテキストデータを構造化して取り出せるOSSライブラリ「PyMuPDF」を試しました。

RAGの実装でPDFからテキストを抽出する際によく使われるライブラリで、LangChainやLlamaIndexのドキュメントローダーでも採用されています。PDFに埋め込まれたテキストレイヤーを直接読み取るためOCR不要で、page.find_tables()でテーブルを自動検出し構造化データとして取得できます。

結果: めちゃくちゃ使える

メリット

  • テーブルを自動検出して取り出せる
  • PDFのテキストデータを使うため文字化けしない(半角カタカナも問題なし)

デメリット

  • 枠線が細い・点線などの場合、テーブルを検出できない
  • スキャンPDFなどテキストデータがないPDFには対応できない

試したこと④:GPT-4o Vision

そこでGPT-4oに画像を直接渡してOCRさせました。

結果: 非常に精度が高い

表形式を崩さず抽出でき、半角カタカナも問題がなかったです。
ただし、処理速度とコストの点では上記の方法よりは劣っています。

比較まとめ

手法 料金 日本語精度 表構造の維持 備考
Mistral OCR $1/1000ページ マルチモーダルな文書も読み取れるが文字認識精度が低い
PyOCR (Tesseract) 無料 × OSSだが精度は低い
PyMuPDF 無料 テキストデータを使用するので文字は正確に抽出できるがテーブル検出できない場合あり
GPT-4o Vision 約2円/回 精度は高いがコスト・速度が課題

結論: どの手法も一長一短があり、単体では完璧に対応できない

段階的フォールバックの設計

そこで最終的に採用したのが、段階的フォールバックという仕組みです。

コストの低い方法から順に試し、失敗したら次の方法にフォールバックすることで、精度とコストを両立させています。

LLMに投げる前にフォールバックするので、APIを呼び出すのは一回で済みます。

スクリーンショット 2025-12-21 3.04.08.png

Excelの場合:ヘッダー行をLLMが検出

Excelに関しては、ヘッダー行をLLMが検出することで、表データを抽出しています。

prompt_template = """
以下のテキストは商品の注文情報です。このテキストは次の三つの部分から構成されています。
a. タイトル、挨拶、宛先、住所、日付など注文内容に関係のない情報(ない場合もある)
b. テーブルのヘッダー行(カラム名(商品名、商品コード、数量、入数、金額、発売月、出荷日、注文日、など複数の項目)が含まれる行(ただし言い回しが違う場合もあります)、必ず存在、1行目にくることもある)
c. テーブルのデータ(商品名、数量、金額などのデータが含まれる行、ヘッダー行の次に必ず存在)
bのヘッダー行(列名が含まれている行)を特定してください。
ヘッダー行は、通常、列名が含まれており、データ行とは異なる特徴を持っています。
ヘッダー行の行番号の整数値のみを返してください。

テキスト:
{prompt_input_text}

ヘッダー行の行番号(整数値のみ):
"""

PDF・PNGの場合:段階的フォールバック

Step 1: PyMuPDFでテーブル抽出 → LLMで整形

まずPyMuPDFでテーブルを抽出します。抽出した全テーブルをLLMに渡し、「商品の詳細情報を含む注文情報テーブル」を判別・整形してもらいます。

テーブル以外もテーブルとして認識してしまうことや、複数テーブルが存在すること、検出できてもテーブルの形式が崩れることがあるため、このようにLLMに整形させるようにしています。

prompt = f"""
以下に、PDFから抽出された複数のテーブルが「抽出された全テーブル」として提示されます。
このテーブルは正確に抽出されているものもあれば、複数行や列が結合されているものもあります。

あなたのタスクは以下の通りです。
1. 最初に、提示された複数のテーブルの中から、「商品の詳細情報(品番、品名、数量、金額など)を含んでいる主要な注文情報テーブル」を一つだけ見つけ出してください。担当者名や合計金額のみが記載された、無関係なメタデータテーブルは無視してください。ただし商品の詳細情報が書かれているテーブルが見つからない場合はタスクを中断し、何も出力しないでください。
2. 次に、見つけ出した主要なテーブルだけを使い、複数行や列が結合されている場合は、それを展開して、最終的にクリーンなCSVを出力してください。

【ルール】
- 重要: 出力は必ず「JSON形式の配列の配列(List of Lists)」としてください。
- 1行目(最初の配列)がヘッダー、2行目以降(2番目以降の配列)がデータとなるように構成してください。
- 説明や言い訳は絶対に含めず、JSONオブジェクトのテキストのみを出力してください。
- 金額や数量が空の場合は、空文字 "" もしくは null を使用してください。

【抽出された全テーブル】
{all_tables_text}

【クリーンな出力CSV】
"""

Step 2: テーブル検出失敗 → 全テキストから構造化

PyMuPDFでテーブルが検出できなかった場合(枠線が細いなど)は、抽出した全テキストをLLMに渡してテーブルを構造化してもらいます。

prompt = f"""
以下に、PDFから抽出された生のテキストが「ドキュメント全文」として提示されます。
あなたのタスクは、このテキスト全体を注意深く読み、「商品の詳細情報(品番、品名、数量、金額など)を含んでいる主要な注文情報テーブル」を見つけ出し、その内容をクリーンなCSV形式で出力することです。

【ルール】
- 重要: 出力は必ず「JSON形式の配列の配列(List of Lists)」としてください。
- 1行目(最初の配列)がヘッダー、2行目以降(2番目以降の配列)がデータとなるように構成してください。
- 説明や言い訳は絶対に含めず、JSONオブジェクトのテキストのみを出力してください。
- 金額や数量が空の場合は、空文字 "" もしくは null を使用してください。

【ドキュメント全文】
{full_text}

【クリーンな出力CSV】
"""

Step 3: テキストなし → GPT-4o Vision OCR

テキストデータが含まれないPDFや画像ファイルの場合、最終手段としてGPT-4o Visionで画像を直接OCRします。

messages = [{
    "role": "user",
    "content": [
        {"type": "text", "text": f"""
        以下に提示される画像の中から、商品の注文情報が記載された主要なテーブルを見つけ出し、その内容をクリーンなCSVで出力してください。

        【ルール】
        - 重要: 出力は必ず「JSON形式の配列の配列(List of Lists)」としてください。
        - 1行目(最初の配列)がヘッダー、2行目以降(2番目以降の配列)がデータとなるように構成してください。
        - 説明や言い訳は絶対に含めず、JSONオブジェクトのテキストのみを出力してください。
        - 金額や数量が空の場合は、空文字 "" もしくは null を使用してください。

        【クリーンな出力CSV】
        """}]
}]

for b64_img in base64_images:
    messages[0]["content"].append({
        "type": "image_url",
        "image_url": {"url": f"data:image/png;base64,{b64_img}"}
    })

精度の担保について

最後に残る課題は「本当に正確に読み取れているのか」という点です。

現時点で100%正確なOCRは不可能だと考えています。そこでこのアプリでは、変換元のファイルと変換結果を並べて表示し、人の目で最終チェックしてもらうUIにしました。

この方法は結局工数がかかってしまうため、より良い方法がないか引き続き検討していきたいです。

振り返り

学んだこと

  • OCRツールの特性: 各ツールの得意・不得意を実際に試して把握できた
  • 一気通貫の開発経験: 要件定義からリリースまでを一人で担当

特にドキュメントを構造化データとして抽出するというのは、RAGの実装など様々な事業領域で発生しうる課題だと思うので、その知見が溜まったのは大きかったです。

反省点

担当者とのコミュニケーション不足が最大の反省点です。

  • 要件を十分に擦り合わせずに開発着手 → 手戻りが発生
  • メーカーごとの複雑な処理ロジックの理解に時間がかかった
  • 結果としてスケジュールが遅延

もっと早い段階で担当者と擦り合わせておくべきでした、、、ただインターンでこの経験ができたことは、今後に活かせる貴重な学びだと思っています。

成果発表

GENDAでは定期的にインターン生の成果発表会があり、この内容を発表しました。

IMG_0890.jpg

自分の成果をアピールしつつ、他のインターン生の取り組みも知れる刺激的な場です。

おわりに

今はこのオンクレの経験を活かして、別のプロジェクトを任せていただいています。

GENDAでは要件定義から任せていただけることが多く、自走力や課題解決力が特に身についたと感じています。今後はより大きなプロジェクトに携わり、さらに成長していきたいです。

ここまで読んでいただきありがとうございました!

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?