TalentX Tech Blog

Tech Blog

Amazon BedrockのConverse APIにてPDFを利用する時の注意点

MyTalentという採用MAサービスの開発を担当している、バックエンドエンジニアの樋口です。

今回はAmazon BedrockのConverse APIにてPDFを扱う際に、期待していた動作と違う部分があったので紹介します。 使用したモデルはAnthropic Claude 3.5 Sonnetで、2024年11月29日に動作確認した内容です。

はじめに - Converse APIのドキュメント機能について

Amazon BedrockのConverse APIでは、Knowledge Baseを使用せずに各種ドキュメントファイル(PDF、Excel、Word等)を用いたチャットが可能です。

対応モデルは下記AWS公式ドキュメントのDocument Chat欄で確認できます。 docs.aws.amazon.com

PDFファイル利用時の動作検証

検証方法

「田中太郎さんはプログラマーです。」という文章を含むWordファイルを3つの方式で変換し、「田中太郎さんは何をやってる人?」という質問でテストを実施しました。

検証コード

package main

import (
    "context"
    "fmt"
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
    "github.com/aws/aws-sdk-go-v2/service/bedrockruntime/types"
    "github.com/myrefer/crm/cerror"
    "os"
    "path/filepath"
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO(),
        config.WithRegion("ap-northeast-1"),
        config.WithSharedConfigProfile("dev"),
    )
    if err != nil {
        panic(err)
    }

    bedrockRuntimeClient := bedrockruntime.NewFromConfig(cfg)

    f, err := os.Open(ファイル名)
    if err != nil {
        fmt.Printf("failed to open file: %v", err)
        return
    }

    fi, err := f.Stat()
    if err != nil {
        fmt.Printf("failed to get file info: %v", err)
        return
    }
    size := fi.Size()
    data := make([]byte, size)
    if _, err = f.Read(data); err != nil {
        fmt.Printf("failed to read file: %v", err)
        return
    }

    converseInput := &bedrockruntime.ConverseInput{
        ModelId: aws.String("anthropic.claude-3-5-sonnet-20240620-v1:0"),
    }

    ext := filepath.Ext(f.Name())
    var content types.ContentBlock
    switch ext {
    case ".jpg", ".jpeg", ".png":
        content = &types.ContentBlockMemberImage{
            Value: types.ImageBlock{
                Format: extToAWSImageFormatType(ext),
                Source: &types.ImageSourceMemberBytes{
                    Value: data,
                },
            },
        }
    case ".pdf", ".docx", ".doc", ".xlsx", ".xls":
        content = &types.ContentBlockMemberDocument{
            Value: types.DocumentBlock{
                Format: extToAWSDocumentFormatType(ext),
                Name: aws.String("file"),
                Source: &types.DocumentSourceMemberBytes{
                    Value: data,
                },
            },
        }
    }

    converseInput.Messages = []types.Message{
        {
            Role: types.ConversationRoleUser,
            Content: []types.ContentBlock{
                &types.ContentBlockMemberText{
                    Value: "田中太郎さんは何をやってる人?",
                },
                content,
            },
        },
    }

    output, err := bedrockRuntimeClient.Converse(context.Background(), converseInput)
    if err != nil {
        fmt.Printf("failed to converse: %v", err)
        return
    }

    res, ok := output.Output.(*types.ConverseOutputMemberMessage)
    if !ok {
        fmt.Printf("failed to get response: %v", cerror.ServerError)
        return
    }
    responseContentBlock := res.Value.Content[0]
    text, ok := responseContentBlock.(*types.ContentBlockMemberText)
    if !ok {
        fmt.Printf("failed to get text: %v", cerror.ServerError)
        return
    }

    fmt.Printf("response text: %v", text.Value)
}

func extToAWSImageFormatType(ext string) types.ImageFormat {
    switch ext {
    case ".jpg", ".jpeg":
        return types.ImageFormatJpeg
    case ".png":
        return types.ImageFormatPng
    }
    return ""
}

func extToAWSDocumentFormatType(ext string) types.DocumentFormat {
    switch ext {
    case ".pdf":
        return types.DocumentFormatPdf
    case ".docx":
        return types.DocumentFormatDocx
    case ".doc":
        return types.DocumentFormatDoc
    case ".xlsx":
        return types.DocumentFormatXlsx
    case ".xls":
        return types.DocumentFormatXls
    }
    return ""
}

検証結果

  • PDF変換したファイル
    下記のような実行結果になり、WordからPDFに変換する際に文字列情報が残っているため正しく回答を生成できています。
$ go run main.go
response text: 田中太郎さんはプログラマーです。
  • PDF → JPEG変換したファイル
    下記のような実行結果になり、 画像のOCR結果から正しく回答を生成できています。
$ go run main.go
response text: 画像によると、田中太郎さんはプログラマーです。
  • PDF → JPEG → PDF変換したファイル
    下記のような実行結果になり、PDFからJPEGに変換する際に文字列情報が失われているため正しく回答を生成できなくなっています。
$ go run main.go
response text: 申し訳ありませんが、提供された文書には田中太郎さんに関する情報が含まれていません。文書の内容が空であるため、この質問に答えるための情報がありません。田中太郎さんについて何か具体的な情報をお持ちでしたら、それを共有していただければ、お答えできるかもしれません。

検証結果の考察

今回の検証で、Amazon BedrockのConverse APIではPDFの場合はOCRを行わず文字列情報を抽出し回答を生成し、画像の場合にはOCR結果の文字列情報から回答を生成していることがわかりました。

私たちが想定していた用途では、スキャナーで読み取って作成されたPDF(以下、スキャンPDF)の利用が見込まれており、スキャンPDFには文字列情報が含まれていない場合や、精度の低いOCR機能付きのスキャナーでのスキャンPDFでは適切でない文字列が含まれていることがあります。

検証結果よりスキャンPDFへの対応が必要なことがわかったため、スキャンPDFを利用する際のアプローチを次節に記載します。

スキャンPDFを利用する際のアプローチ

アプローチ1:PDFを画像に変換して利用

PDFを画像に変換して利用することで、利用している生成AIのモデルのOCR機能を利用することができます。 生成AIのモデルのOCR精度に依存しますが、スキャナーのOCR機能精度など不確定な要素を排除してConverse APIを利用することができます。

この方法の欠点として、WordなどからPDFに変換されたデータに含まれる作成者が入力した文字列情報が消えてしまうことです。

アプローチ2:PDFを利用し期待した値が返ってこなかった際に画像に変換して利用する

PDFから抽出するデータが決まっている際には、期待した値が抽出できなかった場合に画像に変換して利用することで、WordなどからPDFに変換されたデータに含まれる作成者が入力した文字列情報を利用することができます。 スキャンPDFなどで文字列情報が含まれていない場合にはデータを抽出できないため、そういった際には画像に変換して利用することで、生成AIモデルのOCR機能による抽出した情報を利用することができます。

この方法の欠点として、精度の低いOCR機能付きのスキャナーのOCR結果の文字列情報を利用することがあることです。

アプローチ3:PDFに含まれる画像の表示割合が一定以上の際に画像に変換して利用する

スキャンPDFの場合には、PDFに含まれる画像の表示割合が100%に近くなることが多いです。PDFに含まれる画像の表示割合が一定以上の際に画像に変換することで、スキャンPDFである可能性が高い時のみ生成AIモデルのOCR機能による抽出した情報を利用することができます。

この方法の欠点として、装飾用の画像が多く含まれるPDFの場合、WordなどからPDFに変換されたデータの元の文字情報を失う可能性があることです。

おわりに

本稿では、Amazon BedrockのConverse APIを用いたPDF処理の検証を通じて、スキャンPDFを扱う際の課題と対応アプローチを探りました。先に述べた3つのアプローチはそれぞれ一長一短があるため、全体のPDFに含まれるスキャンPDFの割合や、画像に変換した際のOCR精度からどのアプローチを選択するか判断する必要があります。

TalentXでは一緒に働く仲間を募集しており、カジュアル面談も行っていますので気になる方はぜひご応募いただければ幸いです! i-myrefer.jp