Claude3とAzure AI Document Intelligenceを使ってドキュメント読み取りの精度をあげてみた

2024.03.21

はじめに

新規事業部 生成AIチーム 山本です。

ChatGPT(OpenAI API)をはじめとしたAIの言語モデル(Large Language Model:以下、LLM)を使用して、チャットボットを構築するケースが増えています。通常、LLMが学習したときのデータに含まれている内容以外に関する質問には回答ができません。そのため、例えば社内システムに関するチャットボットを作成しようとしても、素のLLMでは質問に対してわからないという回答や異なる知識に基づいた回答が(当然ながら)得られてしまいます。

この問題を解決する方法として、Retrieval Augmented Generation(以下、RAG)という手法がよく使用されます。RAGでは、ユーザからの質問に回答するために必要そうな内容が書かれた文章を検索し、その文章をLLMへの入力(プロンプト)に付け加えて渡すことで、ユーザが欲しい情報に関して回答させることができます。

以前の記事では、RAGを構成する際に大きな課題となっている「ドキュメントが意図しない読み込まれ方になってしまう」という問題に対して、Claude3にパワーポイントのスクリーンショットを画像として読ませることで、人間が読むような形のテキストに起こせることを確認しました。また、読み込める文字のサイズについても調べた結果、文字サイズがある程度大きくないと読み取れないことがわかりました。この問題に対して、既存のOCRサービスを試した結果、OCRの文字の認識精度が高く、文字起こしの補助に使えそうなこともわかりました。

Claude3を使って人間が読むようにパワポ資料を読み込んでみる | DevelopersIO

Claude3で認識できる文字のサイズを簡単に調べてみた | DevelopersIO

Azure AI Document Intelligenceを使ってみた | DevelopersIO

この記事では、Claude3にAzureのDocument intelligenceを組み合わせることで、読み取りの精度を上げた内容について記載します。

課題

以下の2つのような問題があります。詳細については以前の記事をご覧ください。

https://dev.classmethod.jp/articles/simple-examination-on-recognizable-char-size-with-claude-3/#toc-9

小さい文字が読めない

以前の記事に書いたように、人間なら読めそうな文字サイズ(中)でも、認識に失敗するケースが見受けられました。

対策として、画像サイズを大きくする方法も考えられますが、以下の2点が気になります。

  • コストが上がってしまう点。画像サイズに応じて上がってしまいます
  • 画像サイズに制約がある点。AnthropicのAPIリファレンスによると「画像の長辺が 1568 ピクセルを超える場合、または画像が約 1600 トークンを超える場合、サイズ制限内になるまでアスペクト比を維持しながら縮小されます」と書かれています。画像サイズを拡大してAPIをコールしても、自動で縮小されてしまうので、画像サイズによって認識精度をあげるのは限界がありそうです。(段落ごとに切り出すことも考えられますが、段落部分を正確に検出する処理を実装するのは大変そうです)

別の対策として、大きいモデルを変更する方法も考えられますが、以下の点が気になります

  • コストが大きく上がってしまう。以下のように、大きいモデルに変更すると、利用料が大幅に増えてしまいます。
    • Sonnet → Opus:5倍
    • Haiku → Opus:60倍

    補足:1200 x 1000[px]の画像だと、Haikuの場合0.06円、Sonnetの場合0.72円、Opusの場合3.6円です。数枚程度あれば問題ありませんが、社内の多くのドキュメントを文字起こししようとしたら、大きな金額差になります。

たまに誤字脱字がある

  • ある程度の文字サイズが大きくても、認識ミスすることがある
  • 文脈が優先されて文字が選択されている(ような)ときがある。これはこれでありがたいですが、まずは書かれている文字を正確に起こしさせたいところです(その後修正する、という流れの方が、精度検証や運用時に扱いやすそうです)。

今回やること

上記の課題に関して、今回はClaude3とDocument Intelligenceを組み合わせて解決します。

概要

Claude3のようなLLM、Document IntelligenceのようなOCR AI、今回の組み合わせで実現したいことを比較すると、以下の表のとおりです。

※ ◯や△などはおおよその感覚値です。厳密な比較をしたわけではないので、ご注意ください。

LLM OCR AI LLM + OCR AI
(理想)
文字認識精度
(サイズ:通常~大)

高い

かなり高い(ほぼミスなし)

かなり高い(ほぼミスなし)
文字認識精度
(サイズ:小)

認識ミスが起きることがある

かなり高い(ほぼミスなし)

かなり高い(ほぼミスなし)
画像中の文字
認識ミスが起きることがある
(プロンプト中の指示にも依存する)

かなり高い(ほぼミスなし)

かなり高い(ほぼミスなし)
画像自体の説明
できる

できない

できる

できる

できるが、たまに認識ミスがある

できる
文章の構造
(順番・親子関係)

作成者の意図どおりに出力してくれる

作成者の意図とは少し異なるケースがある

作成者の意図どおりに出力してくれる

両者を組み合わせて良いとこ取りをすることで、より精度が高く、かつ、人間の認識するような(≒ 作成者が意図したような)形式で文字起こしをすることが目的です。

他の手法との比較

ドキュメントローダとの比較

もちろんパワーポイントであれば、ファイルを解析すれば書いてある文字やその位置をデータとして取得できます。ただ、今回は汎用的な方法にしたいため、パワーポイントだけではなく、PDFにも対応させたいです。しかし、PDFの中には、文字は書かれているものの、画像のようになっているケースがあり、単純なドキュメントローダーではデータとして読めません。

こうしたケースであっても対応できる方が(文字起こしできる方が)嬉しいため、今回はOCRを使用して文字起こしする方法を試します。

範囲

この記事では、LLM単体では文字認識が低い場合でも、OCRの文字認識を加えることで、正確に文字起こしできるかを確認します。表や文章の構造に関しては、別の記事で確認したいと思います。

実装

以下のように実装しました。いくつか方式が考えられると思いますが、今回は単純な方式を実装しました。方式の比較については、この記事の末尾の「補足」の節で記載しますので、そちらをご覧ください

概要

処理の概要は下図のとおりです。ドキュメントの画像をLLMとOCR AIの両方を使って文字起こしをさせ、2つの結果をもとに、もう一度LLMを呼び出して、結果を修正させます。この処理をPythonのスクリプトで実装しました。

前提

以下を前提とします

AWS

  • Bedrockのモデルアクセスで、Claude3を許可している(手順については鈴木さんのページをご覧ください)
  • プログラムを実行する環境で、権限設定ができている(以降でプロファイル名を使用します)

Azure

  • Document Intelligenceでリソースを作成している
  • エンドポイントのURLとキーを取得している(以降でURLとキーを使用します)
    • ※ 下図のように、作成したリソースの「Keys and Endpoint」の項目で取得できます

準備・必要なライブラリ

以下のコマンドでインストールしました。

pip install azure-ai-documentintelligence==1.0.0b1 boto3

今回、使用した環境変数は以下のとおりです。値は上記で設定・取得した、AWSのプロファイル名と、AzureのDocument IntelligenceのURLとキーを設定しました。環境変数に反映させる方法として、.envファイルに書いてVSCodeのlaunch.jsonのenvとして指定しました(お好みの方法で環境変数として設定してください)

AWS_PROFILE_NAME=xxxxxxxxxxxxxx
AZURE_AI_DOCUMENT_INTELLIGENCE_ENDPOINT=https://xxxxxxxxxxxxxxxxxxxxxxxx.cognitiveservices.azure.com/
AZURE_AI_DOCUMENT_INTELLIGENCE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxx

コード・プロンプト

プログラムは、以下のものを使用しました。以下のような処理です。

  • Document IntelligenceでOCRを実行する
    • 画像をバイナリで読み込んで、b64にエンコードする
    • begin_analyze_documentのoutput_content_formatとして、ContentFormat.MARKDOWNを指定し、markdownとして出力させる
    • (補足:どこかに公開されているデータであれば、URLを指定して渡すこともできます)
  • Claude3で文字起こしを実行する
    • (プロンプトは前回使用したものではなく、少し前の記事で使用した、Markdownで出力させるものです)
  • 2つの結果をもとに、Claude3で文字起こしを実行する
    • invoke_modelのmessagesに(会話履歴として)1回目の文字起こし結果を持たせて、OCRの結果をプロンプトに入れて、誤字を修正させる、という方法です

(以下のコードでは、Claude3のモデルは両方ともsonnetを使用しました)

import base64
import json
import os
from pathlib import Path

import boto3
from azure.ai.documentintelligence import DocumentIntelligenceClient
from azure.ai.documentintelligence.models import (
    AnalyzeDocumentRequest,
    AnalyzeResult,
    ContentFormat,
)
from azure.core.credentials import AzureKeyCredential

# environment variables
AWS_PROFILE_NAME = os.getenv("AWS_PROFILE_NAME")
assert AWS_PROFILE_NAME is not None
endpoint = os.getenv("AZURE_AI_DOCUMENT_INTELLIGENCE_ENDPOINT")
assert endpoint is not None
key = os.getenv("AZURE_AI_DOCUMENT_INTELLIGENCE_API_KEY")
assert key is not None

# AWS
session = boto3.Session(profile_name=AWS_PROFILE_NAME)
bedrock_runtime = session.client("bedrock-runtime", region_name="us-east-1")

PROMPT_TEMPLATE_READ = """
これはパワーポイントファイルのスクリーンショットです。文字起こしをして、markdown形式で出力してください。
このとき以下の[制約]を守ってください。

<制約>
- 出力は、'''などで囲わず、markdownのみ出力してください。
- 人が見て認識するような順番・親子関係としてheading(#や##)や箇条書き(-)をしてください。
- 写真やフロー図・構成図などがスクリーンショット内部に含まれている場合は、内容を説明して[]で囲ってテキストとして出力してください。このスクリーンショットは画像ですが、スクリーンショット自体の説明は不要です
- 表はmarkdownの表形式で出力してください
- 書いてある文字は修正しないでください。これは絶対守ってください
- オブジェクトや図形の説明などは不要です。
</制約>
""".strip()

PROMPT_TEMPLATE_FIX = """
先程の文字起こしの結果に含まれる、誤字や脱字を修正してください
このとき以下の[制約]を守ってください。

<制約>
- 出力は、'''などで囲わず、markdownのみ出力してください。
- [OCRの結果]を参考に、誤字や脱字を修正してください。
  - [OCRの結果]の認識した文字を正としてください。
  - ただし、順序や親子関係はもとのままにしてください。
</制約>

<OCRの結果>
{ocr_result}
</OCRの結果>
""".strip()

def analyze_layout(filepath_input: Path):
    document_intelligence_client = DocumentIntelligenceClient(endpoint=endpoint, credential=AzureKeyCredential(key))

    with open(filepath_input, "rb") as f:
        data = f.read()
    data_b64 = base64.b64encode(data).decode("utf-8")
    poller = document_intelligence_client.begin_analyze_document(
        "prebuilt-layout",
        AnalyzeDocumentRequest(base64_source=data_b64),
        output_content_format=ContentFormat.MARKDOWN,
        features=[],
    )

    result: AnalyzeResult = poller.result()

    return result

def image_to_content(filepath_image: Path):
    with open(filepath_image, "rb") as f:
        bytes_image = f.read()
    bytes_image_b64 = base64.b64encode(bytes_image).decode("utf-8")  # Base64変換

    content_image = {
        "type": "image",
        "source": {
            "type": "base64",
            "media_type": "image/jpeg",
            "data": bytes_image_b64,
        },
    }

    return content_image

def call_claude3_bedrock(messages: list[dict]):
    # call bedrock API
    body = json.dumps(
        {
            "anthropic_version": "bedrock-2023-05-31",
            "max_tokens": 1000,
            "temperature": 0,
            "messages": messages,
        }
    )
    response = bedrock_runtime.invoke_model(
        body=body,
        # modelId="anthropic.claude-3-haiku-20240307-v1:0",
        modelId="anthropic.claude-3-sonnet-20240229-v1:0",
        accept="application/json",
        contentType="application/json",
    )
    response_body = json.loads(response.get("body").read())

    text = response_body["content"][0]["text"]

    return text

def method_1_1(filepath_image: Path, ocr_result: AnalyzeResult):
    # LLMを実行(文字起こし)
    read_result = call_claude3_bedrock(
        [
            {
                "role": "user",
                "content": [
                    image_to_content(filepath_image),
                    {
                        "type": "text",
                        "text": PROMPT_TEMPLATE_READ,
                    },
                ],
            },
        ]
    )

    print(read_result)

    print("=" * 100)

    prompt_fix = PROMPT_TEMPLATE_FIX.format(ocr_result=ocr_result.content)

    print(prompt_fix)

    print("=" * 100)

    # LLMを実行(修正)
    fix_result = call_claude3_bedrock(
        [
            {
                "role": "user",
                "content": [
                    image_to_content(filepath_image),
                    {
                        "type": "text",
                        "text": PROMPT_TEMPLATE_READ,
                    },
                ],
            },
            {
                "role": "assistant",
                "content": [
                    {
                        "type": "text",
                        "text": read_result,
                    },
                ],
            },
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": prompt_fix,
                    },
                ],
            },
        ]
    )

    return fix_result

def main(filepath_image: Path):
    # analyze layout with Document Intelligence (OCR)
    analyse_result = analyze_layout(filepath_image)

    # read and fix with Claude3 (LLM)
    fix_result_1_1 = method_1_1(filepath_image, analyse_result)
    print(fix_result_1_1)

if __name__ == "__main__":
    main(Path("data/images/sample_50.png"))

※ 各プロンプトは改良の余地があると思います。

OCR AIを実行するコードは、Document Intelligence Studioで試した際に、結果についてくるコードを利用しました。

https://dev.classmethod.jp/articles/try-azure-ai-document-intelligence/#toc-19

使用データ

以下の画像を使用しました。サイズは733 x 391です。以前の記事で試した結果、sonnet・haikuだと、文字起こしで認識ミスがおきたものです。

結果

文字起こし(修正前)

Document Intelligence(Markdown)

旅程:

•Day 1: 金閣寺⇒銀閣寺⇒哲学の道。夜は祇園を散策し、「#京都グルメ」を楽しみます。

•Day 2: 清水寺⇒伏見稲荷大社。ここでは、赤い鳥居が印象的な「伏見稲荷大社」を訪れます。 飛行機のチケットは、事前にウェブサイトで予約しました。ホテルは予約サイトで探し、中心地に近いコ ンビニエントな場所
を選びました。

食事:

·京都では、さまざまな料理を試しました。特に、「寿司」「ラーメン」「抹茶スイーツ」は絶品でした。

●「特注 京都限定の抹茶ビール」も試す価値あり!

気に入った場所:

1\.「金閣寺」-金ピカの外観が美しい。

2\.「清水寺」-木造の舞台からの眺めが最高。

3\.「伏見稲荷大社」-無数に続く鳥居をくぐる体験は唯一無二。

感想:

この旅行で、文化と自然の美しさを存分に感じることができました。特に、伝統とモダンが融合する京都 の街並みは、忘れがたい印象を残しました。また、「友人宛にポストカードを送る」という体験も心に 残っていま 
す。

次回訪れる際には、秋の「紅葉狩り」を計画しています。京都の美しさを再び体験できることを楽しみに しています。

Claude3(1回目・修正前)

# 旅程
- Day 1: 金閣寺 -> 銀閣寺 -> 哲学の道、夜は祇園を散策し、[青茶苑グルメ]を楽しみます。
- Day 2: 清水寺 -> 伏見稲荷大社、ここでは、赤い鳥居が印象的な「伏見稲荷大社」を訪れます。
飛行機のデッキ上は、時間にゆとりをもって移動しました。ホテルは予約サイトを活用し、コストパフォーマンスを意識しました。

# 食事
- 京都では、きざみ昆布理を試しました。特に、「湯葉」「ラーメン」「抹茶スイーツ」は絶品でした。
- 「京都ビール」も新鮮な味わいでした。

気になった場所:
- 1. 金閣寺の外観が美しい。
- 2. 「清水寺」・木造の細部分の彫刻が素晴らしい。
- 3. 「伏見稲荷大社」-無数に続く鳥居をくぐる体験は唯一無二。

感想:
- この旅行で、文化と自然の美しさを存分に感じることができました。特に、伝統とモダンが融合する京都の街並みは、忘れがたい印象を残しました。また、「食人菜にポストカードを送る」という体験も心に残っていま
す。
- 次回訪れるときは、秋の「紅葉狩り」を計画しています。京都の美しさを四季体験できることを楽しみにしています。

修正

Claude3の出力(2回目・修正後)

# 旅程
- Day 1: 金閣寺 ⇒ 銀閣寺 ⇒ 哲学の道、夜は祇園を散策し、「#京都グルメ」を楽しみます。
- Day 2: 清水寺 ⇒ 伏見稲荷大社、ここでは、赤い鳥居が印象的な「伏見稲荷大社」を訪れます。飛行機のチケットは、事前にウェブサイトで予約しました。ホテルは予約サイトで探し、中心地に近いコンビニエントな場
所を選びました。

# 食事
- 京都では、さまざまな料理を試しました。特に、「寿司」「ラーメン」「抹茶スイーツ」は絶品でした。
- 「特注 京都限定の抹茶ビール」も試す価値あり!

気に入った場所:
- 1. 「金閣寺」-金ピカの外観が美しい。
- 2. 「清水寺」-木造の舞台からの眺めが最高。
- 3. 「伏見稲荷大社」-無数に続く鳥居をくぐる体験は唯一無二。

感想:
- この旅行で、文化と自然の美しさを存分に感じることができました。特に、伝統とモダンが融合する京都の街並みは、忘れがたい印象を残しました。また、「友人宛にポストカードを送る」という体験も心に残っていま 
す。
- 次回訪れる際には、秋の「紅葉狩り」を計画しています。京都の美しさを再び体験できることを楽しみにしています。

分析

良かった点

  • 認識ミスを修正できた。Claude3を1回目に実行した文字起こしの結果では、誤字が含まれていますが、2回目に実行した文字起こしの結果では、誤字が修正されています

悪かった点

  • 「気になった場所」や「感想」にHeadingがついていない。Claude3での1回目の文字起こしで、Headingがつかなかったのが、そのまま出力されてしまいました(このあたりは、プロンプトの指示に加えれば、修正させることができそうです)

料金

今回実行したDocument IntelligenceとClaude3の料金は以下のとおりです。

  • 文字起こし・Document Intelligence・Layout
    • 10$/1000ページ * 1ページ = 1.5円
  • 文字起こし・Claude3(sonnet)
    • 入力トークン:3$/1Mトークン * 744 = 0.33円
    • 出力トークン:15$/1Mトークン * 495 = 1.11円
    • (合計:1.44円)
  • 文字起こし・Claude3(sonnet)
    • 入力トークン:3$/1Mトークン * 1920 = 0.86円
    • 出力トークン:15$/1Mトークン * 502 = 1.13円
    • (合計:1.99円)
  • (合計:4.93円)

(1ドル150円換算)

少し高い印象です。後述の「補足」で述べるように、より安いモデル(Haiku)で実行したり、OCRの結果をプロンプトに入れて実行する(= 1回で済ませる)、といった方法で安く抑える方法が良さそうです

まとめ

Azure Document Intelligenceを使うことで、LLMの文字起こしの認識ミスを修正することができました。

新しいLLM単体でも十分強力ですが、こうした「既存のAIと新しいLLMとを組み合わせることで、より精度が高い処理を実現する」という手法は、色々な場面で使えそうです。

補足

Haikuで実行した結果

Haikuで実行した結果、正しく文字を認識できました。Sonnetの場合と比べて、Headingのされ方が異なりますが、むしろこちらの方が合っているように思えます。また、料金が1/3程度になり、大幅に安くすることができそうです。実際のケースでは、認識精度を見ながら、モデルを選択することになりそうです。

Document Intelligence(Markdown)

旅程:

•Day 1: 金閣寺⇒銀閣寺⇒哲学の道。夜は祇園を散策し、「#京都グルメ」を楽しみます。

•Day 2: 清水寺⇒伏見稲荷大社。ここでは、赤い鳥居が印象的な「伏見稲荷大社」を訪れます。 飛行機のチケットは、事前にウェブサイトで予約しました。ホテルは予約サイトで探し、中心地に近いコ ンビニエントな場所
を選びました。

食事:

·京都では、さまざまな料理を試しました。特に、「寿司」「ラーメン」「抹茶スイーツ」は絶品でした。

●「特注 京都限定の抹茶ビール」も試す価値あり!

気に入った場所:

1\.「金閣寺」-金ピカの外観が美しい。

2\.「清水寺」-木造の舞台からの眺めが最高。

3\.「伏見稲荷大社」-無数に続く鳥居をくぐる体験は唯一無二。

感想:

この旅行で、文化と自然の美しさを存分に感じることができました。特に、伝統とモダンが融合する京都 の街並みは、忘れがたい印象を残しました。また、「友人宛にポストカードを送る」という体験も心に 残っていま 
す。

次回訪れる際には、秋の「紅葉狩り」を計画しています。京都の美しさを再び体験できることを楽しみに しています。
:unselected:

Claude3(1回目・修正前)

# 旅程:
## Day 1: 金閣寺→銀閣寺→哲学の道、夜は祇園を散策し、「祇園グルメ」を楽しみます。
## Day 2: 清水寺→伏見稲荷大社。ここでは、赤い鳥居が印象的な「伏見稲荷大社」を訪れます。
飛行機のチケットは、事前にウェブサイトで予約しました。ホテルは予約サイトで探し、中心地に近いコンビニエントな場所を選びました。

# 食事:
京都では、さまざまな料理を試しました。特に、「寿司」「ラーメン」「抹茶スイーツ」は絶品でした。「特注京都限定の抹茶ビール」も試飲しました。

# 気に入った場所:
1. [金閣寺] - 金色の外観が美しい。
2. [清水寺] - 木造の舞台からの眺めが最高。
3. [伏見稲荷大社] - 無数に続く鳥居をくぐる体験は非常一。

# 感想:
京都の街並み、文化と自然の美しさを存分に感じることができました。特に、伝統とモダンが融合する京都の街並みは、忘れがたい印象を残しました。また、「女人堂にポストカードを送る」という体験も心に残っていま 
す。
次回訪れる際には、秋の「紅葉狩り」を計画しています。京都の美しさを再び体験できることを楽しみにしています。

Claude3(2回目・修正後)

# 旅程:
## Day 1: 金閣寺→銀閣寺→哲学の道。夜は祇園を散策し、「#京都グルメ」を楽しみます。
## Day 2: 清水寺→伏見稲荷大社。ここでは、赤い鳥居が印象的な「伏見稲荷大社」を訪れます。
飛行機のチケットは、事前にウェブサイトで予約しました。ホテルは予約サイトで探し、中心地に近いコンビニエントな場所を選びました。

# 食事:
京都では、さまざまな料理を試しました。特に、「寿司」「ラーメン」「抹茶スイーツ」は絶品でした。
「特注 京都限定の抹茶ビール」も試す価値あり!

# 気に入った場所:
1. 「金閣寺」-金ピカの外観が美しい。
2. 「清水寺」-木造の舞台からの眺めが最高。
3. 「伏見稲荷大社」-無数に続く鳥居をくぐる体験は唯一無二。

# 感想:
この旅行で、文化と自然の美しさを存分に感じることができました。特に、伝統とモダンが融合する京都の街並みは、忘れがたい印象を残しました。また、「友人宛にポストカードを送る」という体験も心に残っています 
。
次回訪れる際には、秋の「紅葉狩り」を計画しています。京都の美しさを再び体験できることを楽しみにしています。

料金

Haikuで実行した場合、以下のようになります。Sonnetに比べてHaikuは1/12なので、だいぶ安くなります。(トークン数はsonnetの場合を値を使って計算しています)

  • 文字起こし・Document Intelligence・Layout
    • 10$/1000ページ * 1ページ = 1.5円
  • 文字起こし・Claude3(sonnet)
    • 入力トークン:0.25$/1Mトークン * 744 = 0.03円
    • 出力トークン:1.25$/1Mトークン * 495 = 0.09円
    • (合計:0.12円)
  • 文字起こし・Claude3(sonnet)
    • 入力トークン:0.25$/1Mトークン * 1920 = 0.07円
    • 出力トークン:1.25$/1Mトークン * 502 = 0.09円
    • (合計:0.16円)
  • (合計:1.78円)

実装の方式

2つの処理を組み合わせる方式としては、以下の4つが考えられます(他にもあるかもしれません)。今回実装したのは方式1-1にあたります。他の3つについても同様に、より正確に文字起こしできることは確認していますので、別の記事で書きたいと思います。

1. 別々→統合(方式1)

LLMとOCR AIをそれぞれ別に実行し、2つの結果を統合するという方式です。統合は、別のプロンプトでLLMを呼び出して実行します。(LLM / OCR AI → LLMという順番)

  • 1.1. 統合時にもとの画像を含める(方式1-1)

    2回目のLLM呼び出し時に、LLMの文字起こし結果と、もとの画像を含める方法です。通常のLLMのmessagesの使い方に沿った書き方です

  • 1.2. 統合時にもとの画像を含めない(方式1-2)

    2回目のLLM呼び出し時に、LLMの文字起こしの結果のみを含める方法です。既に文字起こしした結果があり不要そうと考えられるため、その部分は省いて呼び出す(修正だけをさせる)方法です

2. OCR AI→LLM(方式2)

OCR AIを先に実行し、その結果をもとにLLMを実行するという方式です。LLMに渡す方法として、以下の2つがあると考えられます

  • 2.1. 文字で補足(方式2-1)

    OCR AIの結果のテキスト部分を、プロンプトに書き込み、LLMにわたす方法です

  • 2.2. bboxを画像に描く(方式2-2)

    OCR AIの結果には、テキストが書かれた位置(bbox)が含まれます(対応していないモデル・サービスもあります)。これを直接ドキュメントの画像に描画し、LLMに渡すという方法です

    ※ こちらの記事のように、Segmentation結果をアノテーションとして画像に描画してLLMに渡すという方法があるようで、これと同様の方法が使えそう、という考え方です。

    https://zenn.dev/kzykmyzw/articles/44c7eea2fd77dd

ざっくりと処理を書くと以下のとおりです。

方式1-1

# LLMを実行(文字起こし)

# OCR AIを実行

# LLMを実行(修正・会話履歴あり)

方式1-2

# LLMを実行(文字起こし)

# OCR AIを実行

# LLMを実行(修正・会話履歴なし=新規会話)

方式2-1

Document Intelligenceが出力するMarkdownをそのまま渡す

# OCR AIを実行

# LLMを実行(文字起こし)

方式2-2

# OCR AIを実行

# 画像にbboxを描画

# LLMを実行(文字起こし)