見出し画像

GPT4 Tools(Function Calling)を使ってインボイスの構造化データを抽出する

前々回はVisonモデルを用いて請求書のOCR、前回はJSON modeを使った請求書からJsonフォーマットでの情報抽出を行いました。
Visionモデルは帳票OCRとしての利用は難しく、JSON modeはスキーマを固定できないことがネックであることがわかりました。

今回はOpenAI API 1.0で追加されたAssistant APIの機能であるToolsのFunction callingを用いて請求書の構造化データ抽出をやってみようと思います。
Function callingは以前からある機能ですが、Toolsに統合されたこともありAPI 1.0ではこれまでのコードでは動かず、手を加える必要があります。

旧Function callingでの請求書の構造化データ抽出は逆瀬川さんの記事が大変参考になります。
今回はこの記事で紹介されているコードをベースに、新ToolsのGPT4に対応させ、ついでに10月から開始しているインボイス(適格請求書)にも対応させようと思います。

下準備


今回もfreeeの配布しているインボイスサンプルを利用します。伏せ字が多いと正しく認識しない恐れがあるので、ありそうな名前に修正しています。

インボイスのサンプル

https://www.freee.co.jp/kb/template/invoice/template-1/

インボイス要件に対応したJSONフォーマット

JSONフォーマットは下記のように定義しました。インボイス対応として、事業者登録番号、アイテムごとの取引日と適用税率、適用税率をグループした合計額・消費税合計額について抽出ができるようにしています。

tools = [
    {
        "type": "function",
        "function": {
    
        "name": "invoice_information_extraction",
        "description": """これは請求書のpdfをOCRにかけたものから情報を抽出するための処理です。
        OCRで抽出されたテキストは以下の形式に従います
        (x座標, y座標): {OCRで抽出されたテキスト}

        また、請求書は以下の配置ルールがあります。
        - 銀行名、支店名、口座種類、口座番号は近い位置にある
        - 住所、会社名、名前、電話番号、メールアドレスは近い位置にある
        - 郵便番号、住所は近い位置にある
        - 請求先は「御中」や「様」などの左に書かれる
        - 登録番号はTに続く13桁の数字である
        """,
        "parameters": {
            "type": "object",
            "properties": {
                "invoice_date": {
                    "type": "string",
                    "description": "請求日"
                },
                "invoice_number": {
                    "type": "string",
                    "description": "請求番号"
                },
                "invoice_to": {
                    "type": "string",
                    "description": "請求先 (御中とか様は除外する)"
                },
                "invoice_items": {
                    "type": "array",
                    "description": "請求品目",
                    "items": {
                        "type": "object",
                        "properties": {
                            "item_trade_date": {
                                "type": "string",
                                "description": "取引日"
                            },
                            "item_name": {
                                "type": "string",
                                "description": "請求品目名"
                            },
                            "item_count": {
                                "type": "number",
                                "description": "品目数量"
                            },
                            "item_unit_name": {
                                "type": "string",
                                "description": "品目単位名"
                            },
                            "item_unit_price": {
                                "type": "number",
                                "description": "品目単価"
                            },
                            "item_price": {
                                "type": "number",
                                "description": "品目金額"
                            },
                            "item_tax_rate": {
                                "type": "number",
                                "description": "適用税率"
                            }
                        }
                    }
                },
                "total_amount": {
                    "type": "number",
                    "description": "総額"
                },
                
                "10_percent_total": {
                    "type": "number",
                    "description": "10%対象の合計額"
                },
                "8_percent_total": {
                    "type": "number",
                    "description": "8%対象の合計額"
                },
                "10_percent_tax_total": {
                    "type": "number",
                    "description": "10%対象の消費税合計額"
                },
                "8_percent_tax_total": {
                    "type": "number",
                    "description": "8%対象の消費税合計額"
                },

                "bank_name": {
                    "type": "string",
                    "description": "銀行名"
                },
                "branch_name": {
                    "type": "string",
                    "description": "支店名"
                },
                "account_type": {
                    "type": "string",
                    "enum": ["普通", "定期", "当座"],
                    "description": "口座種類"
                },
                "account_number": {
                    "type": "string",
                    "description": "口座番号"
                },
                "zipcode": {
                    "type": "string",
                    "description": "請求書発行元郵便番号"
                },
                "address": {
                    "type": "string",
                    "description": "請求書発行元住所"
                },
                "company_name": {
                    "type": "string",
                    "description": "請求書発行元会社名"
                },
                "invoice_number": {
                    "type": "string",
                    "description": "請求書発行元登録番号"
                },
                "name": {
                    "type": "string",
                    "description": "請求書発行元担当者または部署名"
                },
                "phone_number": {
                    "type": "string",
                    "description": "請求書発行元電話番号"
                },
                "email_address": {
                    "type": "string",
                    "description": "請求書発行元メールアドレス"
                },
                "remarks": {
                    "type": "string",
                    "description": "備考"
                }
            },
            "required": ["invoice_date"]
        }}}
]

OpenAI API 1.0、gpt-4-1106-previewへの対応

Chat Completions APIの呼び出しかたが変わり、パラメータのfunctionsが廃止されtoolsになっているため、コードを修正します。

import openai
response = openai.chat.completions.create(
    model="gpt-4-1106-preview",
    messages=messages,
    tools=tools,
    tool_choice="auto",  # auto is default, but we'll be explicit
    temperature=0,
    max_tokens=512,
)

実行

インボイスのPDFファイルを提供し、PaddleOCRで内容を読み取ります。文字列にはX,Yの座標が与えられており、印字の物理的な距離を含んだ意味抽出ができると思われます。読み取った結果は下記のとおりです。

(719.0, 185.0): 請求書
(1103.0, 283.0): 発行日
(1209.0, 283.0): 2024/1/1
(1088.0, 322.0): 請求番号
(1209.0, 326.0): 0000001
(163.0, 360.0): 神奈川商事株式会社
(500.0, 368.0): 御中
(1093.0, 373.0): 登録番号
(1209.0, 373.0): T0123456789012
(167.0, 412.0): 1231-0021
(162.0, 453.0): 神奈川県横浜市中区日本大通0丁目1-2
(992.0, 453.0): 埼玉フードサービス株式会社
(993.0, 499.0): 7330-9301
(1063.0, 536.0): 埼玉県さいたま市浦和区
(165.0, 551.0): 下記の通り
(281.0, 551.0): ご請求申し上げます.
(994.0, 556.0): 住所
(1068.0, 565.0): 高砂0丁目15-1
(406.0, 602.0): ご請求金額 (税込)
(994.0, 604.0): 電話
(1061.0, 602.0): 01-1234-5678
(1002.0, 648.0): ール
(1058.0, 648.0): saitamagexample.com
(677.0, 663.0): 119,800
(263.0, 782.0): 茨城銀行
(347.0, 782.0): 栃木支店
(450.0, 782.0): 普通
(495.0, 782.0): 0123456
(175.0, 806.0): 振込先
(263.0, 833.0): カナガワショウジ(カ
(165.0, 887.0): 振込期E
(263.0, 884.0): 2024年2月末
(165.0, 936.0): 振込手数料は御社のご負担にてお願いいたします
(212.0, 1023.0): 日付
(544.0, 1023.0): 内容
(832.0, 1028.0): 軽減税率
(928.0, 1028.0): 数量
(999.0, 1020.0): 単位
(1081.0, 1028.0): 単価 (税抜
(1226.0, 1028.0): 税牽
(1300.0, 1023.0): 金額 (税抜)
(185.0, 1072.0): 2023/12/1
(315.0, 1072.0): 調理器具
(1019.0, 1077.0): -」
(1142.0, 1070.0): 50,000
(1250.0, 1074.0): 10%
(1368.0, 1070.0): 50,000
(177.0, 1118.0): 2023/12/15
(318.0, 1118.0): 食用油
(960.0, 1121.0): 30
(1012.0, 1121.0): 個
(1154.0, 1118.0): 2,000
(1255.0, 1121.0): 8%
(1366.0, 1118.0): 60,000
(160.0, 1779.0): ×は軽減税率対象です
(1181.0, 1781.0): 小計
(1356.0, 1781.0): 110,000
(188.0, 1824.0): 税率区分
(354.0, 1827.0): 消費税
(482.0, 1830.0): 金額(税抜)
(1174.0, 1827.0): 消費税
(1381.0, 1830.0): 9,800
(194.0, 1876.0): 10%対象
(409.0, 1879.0): 5,000
(544.0, 1876.0): 50,000
(1184.0, 1874.0): 合計
(1354.0, 1876.0): 119,800
(199.0, 1922.0): 8%対象
(409.0, 1925.0): 4,800
(544.0, 1922.0): 60,000
(778.0, 2047.0): 備考

細かく見ると先頭や末尾、一文字の項目がうまく読み取れていない場合があるようですが、おおむねよく読み取れていると思います。
OCRから読み取った文字列と座標軸をGPT4に与えて構造化データを作成させます。
作成されたJSONフォーマットデータは下記のとおりです。

{
  "invoice_date": "2024/1/1",
  "invoice_number": "T0123456789012",
  "invoice_to": "神奈川商事株式会社",
  "invoice_items": [
    {
      "item_trade_date": "2023/12/1",
      "item_name": "調理器具",
      "item_count": null,
      "item_unit_name": null,
      "item_unit_price": 50000,
      "item_price": 50000,
      "item_tax_rate": 10
    },
    {
      "item_trade_date": "2023/12/15",
      "item_name": "食用油",
      "item_count": 30,
      "item_unit_name": "個",
      "item_unit_price": 2000,
      "item_price": 60000,
      "item_tax_rate": 8
    }
  ],
  "total_amount": 119800,
  "10_percent_total": 50000,
  "8_percent_total": 60000,
  "10_percent_tax_total": 5000,
  "8_percent_tax_total": 4800,
  "bank_name": "茨城銀行",
  "branch_name": "栃木支店",
  "account_type": "普通",
  "account_number": "0123456",
  "zipcode": "1231-0021",
  "address": "神奈川県横浜市中区日本大通0丁目1-2",
  "company_name": "神奈川商事株式会社",
  "name": "埼玉フードサービス株式会社",
  "phone_number": "01-1234-5678",
  "email_address": "saitamagexample.com",
  "remarks": "振込期限 2024年2月末\n振込手数料は御社のご負担にてお願いいたします"
}

驚くほどよく抽出、構造化できています。
事業者番号、品目ごとの取引日と適用税率、適用税率ごとの合計額と消費税額についても適切に抽出されています。
そのほかも概ねよく抽出できていますが、発行元会社(埼玉フードサービス)の名前が入ってほしいところが宛先会社(神奈川商事)と同じになってしまいました。ここはJSONの定義とプロンプトの工夫で改善できるのではないでしょうか。

Function callingは非常に強力な機能で、システムの一部としてGPTを機能させるには最適解であると感じました。引き続きFunction callingを中心に会計業務への応用を検討してみます。

今回はここまでです。
最後までお読みいただきありがとうございました。


この記事を書いている人

兎耳山ルカ

会計Vtuber/会計修士/公認会計士/公認情報システム監査人CISA/公認内部監査人CIA/AFP/G検定/元銀行員/大手監査法人でAIを用いた業務変革に取り組んでいました/メタバースやYouTubeにおいて会計の魅力を発信する会計Vtuberとして活動しています。
X: @TomiyamaLuca


会計人コースに記事を執筆しました🎉

会計人コースWebでは、会計プロフェッションを目指している若い方々向けて、生成AI時代の会計人材に必要とされるスキルについて記事を執筆しました。ぜひ御覧ください!

【未来予想図2035】監査現場と会計人に必要とされるスキルとは | 会計人コースWeb

この記事が気に入ったらサポートをしてみませんか?