見出し画像

LLM(Chat-GPT/Gemini)をつかって、領収書から仕訳を自動作成してみる

割引あり

はじめに

わたしは、ながらく会計関係の仕事をしています。領収書から仕訳を起票することが仕事上、よくあります。簡単な内容ですと休憩をいれて、1時間あたり50仕訳ぐらいで入力できますが、正直、単純な割に、正確性が問われるので、かなり疲れる仕事です。このような仕事は、1仕訳〇〇円などと入力を代行する業者もあり、利用することもありますが、入力内容の確認や訂正は必要となります。

このような作業はコンピュータが勝手にやってくれるとよいと常々思っていたのですが、ChatGPTやGeminiなど画像認識ができるLLMの性能があがってきたので、領収書の画像から仕訳を自動生成するということをやってみました。

とりあえず、我が家の確定申告で利用することを想定しているので、利用している会計ソフト(マネーフォワードクラウド会計)で読み込みが可能な形式で仕訳を出力するようにしてみました。

領収書の画像から読み取ること

領収書の画像から読み取る項目は、以下のとおりです。

  • 支払いの日付

  • 支払金額

  • 消費税額

  • 支払いの相手先

  • 取引の内容

  • 登録番号

  • 勘定科目(借方)

また、通常、領収書からでは、現金払い、クレジットカード払い、預金口座からの支払いなどの支払い方法がわからないことが一般的です。そこで、ここでは、現金払いの領収書のみを読み込ませることを想定して仕訳を起票してみました。
クレジットカード払い、預金口座からの支払いは、それらの取引データが直接取得できることが多いのですが、現金払いはそのようなことができないので、この部分を自動化できるのはメリットが大きいと思います。

注意したこと

領収書には、個人名など、プライベートな情報が記載されています。そのような情報が、LLMのモデルの学習に使われてしまうことは避ける必要があります。ChatGPTの場合、API経由のデータはモデルの学習に利用されないとのことです(2024年末現在)。
Googleが提供するGeminiの場合、Google AI Studio と、Vertex AI Platform のの2つのプラットフォームでAPIを経由してGeminiが利用できます。また、Vertex AI Platform の場合、データはモデルの学習に利用されません(2024年末現在)。

今回は、上記のことを念頭に、

  • Chat-GPT、Gemini ともに、API経由で利用する

  • Google AI Studio の場合、データが学習に利用される可能性があるので注意する。

  • Gemini は、Vertex AI Platform を利用する。

こととしました。

また、今回、テストするデータは、私自身の領収書を使用しています。モデルの学習に利用されても問題はありませんが、実際に、業務で利用することを念頭に置く場合、上記の配慮が必要になると思います。

プログラミング言語は、pythonを使いました。AI関連でポピュラーな言語ですし、また、私自身が使い慣れているということもあります。

使用したプロンプト

実際にしようしたプロンプトは、例えば、以下のとおりです。プログラムでデータを受け取って、会計ソフトで読み込み可能な仕訳のファイルを書き出す予定なので、結果をJSON形式で取得するようにしました。

(システムプロンプト)
「あなたは会計事務所の職員です。 書類の内容の確認を手伝ってください。 JSON形式で日本語で返答してください。」

(ユーザプロンプト)
「この画像が領収書である場合、領収書の支払日、登録番号、支払の相手先、合計金額、合計金額に含まれる消費税の金額、支払内容、借方の勘定科目を教えて下さい。JSONのキーはdate, tax_code, payee, amount, tax_amount, contents, Dr としてください。dateは支払日、tax_codeは登録番号、payeeは支払の相手先、amountは合計金額、tax_amountは合計金額に含まれる消費税の金額、contentsは支払内容、Drは借方の勘定科目とします。 合計金額は税込みの金額でお願いします。 支払内容が複数ある場合は空白で区切って連結して下さい。支払日は[西暦]/[月]/[日]という形式で出力して下さい。勘定科目は「売上高」「雑収入」「租税公課」「荷造運賃」「水道光熱費」「旅費交通費」「通信費」「広告宣伝費」「接待交際費」「損害保険料」「修繕費」「消耗品費」「減価償却費」「法定福利費」「給料賃金」「利子割引料」「車両費」「支払手数料」「新聞図書費」「会議費」「諸会費」「雑費」から選択してください。この画像が領収書でない場合は、空欄のJSON形式を返してください。」

勘定科目は、実際に使っている科目から主要なものをピックアップして、それらの中から選択するようにしてみました。今回は、仕訳の生成を主眼としているので、勘定科目が限定的になっています。「工具器具備品」と「消耗品費」の区分など、金額により資産科目と損益科目の区別ができるかなどについては、あらためてチャレンジしてみようと思います。ただ、AIのほうでそれらを判定してくれればすばらいですが、金額が読み取れればプログラムのほうでも処理が可能かなとも思います。

Geminiの場合、上記の「システムプロンプト」と「ユーザプロンプト」をつなげたものをユーザプロンプトとしています。(Geminiでも「システム指示」という指示方法があるので、今後は、そちらでも試してみようと思います。)

結果

いくつかのモデルに次の領収書を読み込ませてみました。モデル自体の性能を確認したかったので、PDFファイルを作成する際に、画像のOCR化はおこなっていません。

  • 郵便物を出したのときの領収書

  • ガソリンを入れたときの領収書

  • コインパーキングに駐車したときの領収書

Gemini2.0Flash

Vertex AI Platform 経由で、モデルとしてGemini2.0Flash(gemini-2.0-flash-exp)を使用した際のレスポンスです。

  • 領収書1(郵便物を出しだときの領収書)
    {
    "date": "2024/01/25",
    "tax_code": "T1010001112577",
    "payee": "日本郵便株式会社",
    "amount": "300",
    "tax_amount": "27",
    "contents": "第一種定形外(規格内) 特殊取扱 特定記録",
    "Dr": "通信費"
    }
    ※日付、金額、内容など、すべての項目で正しく読み込まれています。勘定科目も適切に選択されています。

  • 領収書2(ガソリンを入れたときの領収書)
    {
    "date": "2024/02/03",
    "tax_code": "T2010001126403",
    "payee": "出光リテール販売(株)",
    "amount": "2000円",
    "tax_amount": "182円",
    "contents": "レギュラーガソリン",
    "Dr": "車両費"
    }
    ※同様に、すべての項目で正しく読み込まれています。勘定科目も適切に選択されています。

  • 領収書3(コインパーキングに駐車したときの領収書)
    {
    "date": "2024/02/09",
    "tax_code": null,
    "payee": "北川駐車場",
    "amount": "100",
    "tax_amount": null,
    "contents": "駐車料金",
    "Dr": "車両費"
    }
    ※領収書に登録番号の記載がないため、nullが返ってきています。また、消費税額の記載もないため、nullとなっています。税額を自動で推定してくれるわけではないようです。

Gemini 1.5 Pro

Vertex AI Platform 経由で、モデルとしてGemini1.5Pro(gemini-1.5-pro)を使用した際のレスポンスは以下の通りです。

  • 領収書1
    {"date": "2024/1/25", "tax_code": "T1010001112577", "payee": "日本郵便株式会社", "amount": "300", "tax_amount": "27", "contents": "第一種定形外(規格内) 特殊取扱 特定記録", "Dr": "郵送料"}

    ※Drが指定した選択肢にない「郵送料」となっています。

  • 領収書2
    {"date": "2024/2/3", "tax_code": "T2010001126403", "payee": "出光リテール販売(株)", "amount": 2000, "tax_amount": 182, "contents": "レギュラーガソリン", "Dr": "車両費"}

    ※特に問題なく処理されています。

  • 領収書3
    {"date": "2024/02/09", "tax_code": null, "payee": "北川駐車場", "amount": 100, "tax_amount": null, "contents": "駐車料金", "Dr": "車両費"}

    ※特に問題なく処理されています。

Gemini1.5Flash

ちなみに、Vertex AI Platform 経由で、モデルとしてGemini1.5Flash(gemini-1.5-flash)を使用した際のレスポンスは以下の通りです。レスポンスをそのまま貼り付けています。Gemini1.5Flashではと異なり、jsonの各項目の間に改行がないようなので、少し見づらいです。

  • 領収書1
    {"date": "2024/1/25", "tax_code": "T1010001112577", "payee": "日本郵便株式会社", "amount": "300", "tax_amount": "27", "contents": "第一種定形外(規格内) 98.5g @140 1通 特殊取扱(内訳) 特定記録 郵便物引受合計通数 1通 課税計(10%) (内消費税等(10%) 非課税計", "Dr": "雑費"}

    ※科目が、「雑費」になってしまっています。内容は、記載されているものがそのままという感じです。これをみると、Gemini2.0-flash-expのほうは内容がある程度要約されているのではとも思います。

  • 領収書2
    {"date": "2024/2/3", "tax_code": "T2010001126403", "payee": "出光リテール販売(株)", "amount": "2000", "tax_amount": "182", "contents": "レギュラーガソリン P-13(内) 0166.0 2000円 12.05 L 01200. 00", "Dr": "雑収入"}

    ※同様に科目が「雑収入」となってしまっています。

  • 領収書3
    {"date": "2024/02/09", "tax_code": "03", "payee": "北川駐車場", "amount": "100", "tax_amount": null, "contents": "駐車場", "Dr": "雑費"}

    ※科目が、「雑費」になってしまっています。登録番号が「03」と誤った値になっています。駐車番号を登録番号として取得してしまったようです。

ChatGPT-4o

モデルとしてChatGPT-4oを使用した際のレスポンスは以下の通りです。

  • 領収書1
    {
    "date": "2024/01/25",
    "tax_code": "T10110001112577",
    "payee": "日本郵便株式会社",
    "amount": "300",
    "tax_amount": "27",
    "contents": "郵便物引受合計通数",
    "Dr": "通信費"
    }
    ※取引内容が読み取ったテキストそのままという感じはありますがなんとか許容範囲内かなとう感じです。

    • 領収書2
      {
      "date": "2024/2/3",
      "tax_code": "T201000126403",
      "payee": "出光リテール販売株式会社",
      "amount": "2000円",
      "tax_amount": "182円",
      "contents": "レギュラーガソリン",
      "Dr": "車両費"
      }
      ※ChatGPTでは、PDFファイルから画像を抽出するのですが、その際に埋め込まれたの画像の解像度と同じ解像度にもどさないと、うまく認識されないことがありますので、注意が必要です。

      • 領収書3
        res_text {
        "date": "2012/02/09",
        "tax_code": "02",
        "payee": "北川駐車場",
        "amount": "100",
        "tax_amount": "",
        "contents": "駐車料金",
        "Dr": "車両費"
        }

コストを比較してみる

PDFファイルは、ScanSnapで領収書をスキャンして作成しています。

ChatGPT-4o

3つのPDFファイルで2395トークン(入力:2174トークン出力:221トークン)でした。ChatGPT-4oの料金は、
$2.50/1M 入力トークン
$10.00/1M 出力トークン
なので、1ファイルあたり0.0025483ドル(0.400円,157円/ドル)という計算になります。

Gemini 2.0 Flash

現在、試験運用期間ですので無料です。

Gemini 1.5Pro(Vertex AI)

128K入力トークン以下の場合、
画像入力 $0.00032875 / 画像
テキスト入力 $0.0003125 / 1,000 文字
テキスト出力 $0.00125 / 1,000 文字
となっています。

3つのファイルで、
入力:3画像、1854文字、1062トークン
出力:1989トークン
となっています。
出力トークンは1トークン約4文字なので、
($0.00032875*3+$0.0003125/1000*1854+$0.00125/1000*1989*4)/3=$0.003836875 となり、
1ファイルあたり0.0038368ドル(0.602円,157円/ドル)という計算になります。今回の例では、ChatGPT-4oより高くなりました。ただ、画像数ベースでの課金計算が入っているので、もう少し大きめの画像になるとChatGPT-4oより安くなるケースもあると思います。

Gemini 1.5Flash(Vertex AI)

128K入力トークン以下の場合、
画像入力 $0.00002 / 画像
テキスト入力 $0.00001875 / 1,000 文字
テキスト出力 $0.000075 / 1,000 文字
となっています。

3つのファイルで、
入力:3画像、1854文字、1062トークン
出力:2024トークン
となっています。
同様の計算で、1ファイルあたり0.0002339ドル(0.0367円,157円/ドル)という計算になります。
Gemini 1.5Proより桁1つ分安いです。性能が多少劣っても安価なほうがよいというケースもあると思うので、用途によっては使い道があるのではと思います。

ちなみに私(人間)が入力作業をたのまれると・・・・

1時間で領収書50枚(ファイル)を処理したとすると、仮に時給1000円(個人的にはもっと高いほうがよいです・・・)として計算すると、1ファイルあたり20円になります。しかも、今回は計測していませんが、LLMの場合、50枚の処理にかかっても数分(場合によって数秒)でしょうから、処理速度も桁が2つくらい違います。
例えば、GPT-4oで処理すると、上記の50分の1のコストで処理できるわけです。もちろん、領収書のスキャンや確認などの作業があるので、単純に50分の1というわけにはいきませんが、人間が仕訳を入力するという場面は数枚の伝票のみを入力する場合を除いて、遅かれ早かれなくなっていくのかなと思います。

最近は、生成AIをつかって文章の作成、作曲、画像の作成などもできるようになってきています。ただ、これらの分野は、AIでなく、人自身が創作したということやその人の人生を踏まえたストーリー自体に価値を見出すことができる分野であると思います。また、創作すること自体が「楽しい」分野であるとも思います。なので、囲碁や将棋の棋士が、AIが発達してもAIを参考にしてより新しい戦い方を進化させているのと同様に、これらの分野は人の創作自身もなくならないし、AIと共生していくことが可能な分野でもあると思います。

ただ、正直、領収書の入力は、そのような付加価値や楽しさという側面とは程遠い分野なので、AIで効率的に低コストでおこなって、人間は余った時間を他の活動にあてることが通常は望ましいし、また、社会的にもそのような方向に進んでいく分野であると思います。

使用したコード

以下は、上記の実験に使用したコード(python3.11)です。APIキーの取得や必要なモジュールのインストールなどを行ってから実行して下さい。わかりやすさのため、APIキーはプログラムの最初の部分に記入するようにしていますが、業務で使用する場合にはレジストリから読み込むなど、よりセキュアな方法が望ましいと思います。
pythonのプログラムがあるフォルダの下に「receipt_samples_pdf」
というフォルダを作って、その下に上記の3つのPDFファイルを配置して実行しています。
ChatGPT-4oとGemini(Google AI Studio経由)、Gemini(Vertex AI 経由)のそれぞれについて、自分のPC上にあるPDFファイルを分析する方法の参考にもなると思います。

ChatGPT-4o

from openai import OpenAI 
import os
import base64
from io importBytesIO
from PIL import Image
from pdf2image import convert_from_path
import json
import time

YOUR_OPENAI_API_KEY="OpenAI API KEY"

def pil_to_base64(img, format="png"):
    buffer = BytesIO()
    img.save(buffer, format)
    return base64.b64encode(buffer.getvalue()).decode("utf-8")

def get_gpt_response(model,system_prompt,user_prompt,base64_image):
    client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", YOUR_OPENAI_API_KEY))

    response = client.chat.completions.create(
        model=model,
        response_format={"type": "json_object"},
        messages=[
            {"role": "system", "content": system_prompt },
            {"role": "user", "content": [
                {"type": "text", "text": user_prompt },
                {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{base64_image}"}}
            ]}
        ],
        temperature=0.0,
    )

    return response

def get_pdf_json_response_chatgpt(pdf_path,prompt):
    #pdfの1ページ目を問い合わせ
    system_prompt = prompt[0]
    user_prompt = prompt[1]
    model_id ="gpt-4o"

    res =None

    # PDFファイルを画像に変換
    pages = convert_from_path(pdf_path, dpi=96)
    #1ページ目を分析
    for i, page in enumerate(pages):
        base64_image = pil_to_base64(page, format="png")
        try:
            response = get_gpt_response(model_id,system_prompt,user_prompt,base64_image)
        except Exception as e:
            print("error Exception:",e)
            break

        if response.choices[0].message.content is None:
            print("No response")
            break

        res_text = response.choices[0].message.content
        #print("res_text",res_text)
        res = json.loads(res_text)
        break

    return res

def disp_image_info_in(folder_path,prompt):

    for pathname, dirnames, filenames in os.walk(folder_path):
        for filename in filenames:
            if filename.endswith('.pdf') and (not filename.startswith('-')):
                pdf_path = os.path.join(pathname,filename)
                res = get_pdf_json_response_chatgpt(pdf_path,prompt)
                print(res)
                time.sleep(1)
    return


def get_user_prompt(prompt_info):

    user_prompt = f"この画像が{prompt_info['doc_type']}である場合、{prompt_info['doc_type']}に記載されている"

    first_item = True
    for key ,value in prompt_info['kv'].items():
        if not first_item :
            user_prompt += '、'
        user_prompt += value
        first_item = False

    user_prompt += "の内容を教えて下さい。JSONのキーは"

    first_item = True
    for key ,value in prompt_info['kv'].items():
        if not first_item :
            user_prompt += ','    
        user_prompt += key
        first_item = False

    user_prompt += "としてください。"

    first_item = True
    for key ,value in prompt_info['kv'].items():
        if not first_item :
            user_prompt += '、'
        user_prompt += f"{key}は{value}"
        first_item = False

    user_prompt += "とします。"

    user_prompt += prompt_info['comment']
    user_prompt += f"この画像が{prompt_info['doc_type']}でない場合は、空欄のJSON形式を返してください。"

    return user_prompt
    
def main():

    #検索対象のフォルダを指定
    iDir = os.path.abspath(os.path.dirname(__file__))
    folder_name ="receipt_samples_pdf"
    folder_path = os.path.join(iDir,folder_name)
    print(folder_path)

    system_prompt = """
    あなたは会計事務所の職員です。
    書類の内容の確認を手伝ってください。
    JSON形式で日本語で返答してください。
    """
    print("system_prompt",system_prompt)

    prompt_info={'doc_type':'領収書',
        'kv':{'date':'支払日','tax_code':'登録番号','payee':'支払の相手先','amount':'合計金額','tax_amount':'合計金額に含まれる消費税の金額','contents':'支払内容','Dr':'借方の勘定科目'},
        'comment':"合計金額は税込みの金額でお願いします。支払内容が複数ある場合は空白で区切って連結して下さい。支払日は[西暦]/[月]/[日]という形式で出力して下さい。勘定科目は「売上高」「雑収入」「租税公課」「荷造運賃」「水道光熱費」「旅費交通費」「通信費」「広告宣伝費」「接待交際費」「損害保険料」「修繕費」「消耗品費」「減価償却費」「法定福利費」「給料賃金」「利子割引料」「車両費」「支払手数料」「新聞図書費」「会議費」「諸会費」「雑費」から選択してください。"}
    user_prompt = get_user_prompt(prompt_info)
    print("user_prompt",user_prompt)

    prompt =[system_prompt,user_prompt]
    disp_image_info_in(folder_path,prompt)

if __name__ == "__main__":
    main()

Gemini2.0Flash(Google AI Studio 経由)

後半の方は上記とコードが重複しますが、わかりやすさのため、そのままのせておきます。grpcioモジュールのバージョンによってはワーニングメッセージが出る場合があるようです。model_idの部分を変えれば、Gemini1.5Flashでも利用できます。

import os
import json
import time
import google.generativeai as genai

YOUR_GOOGLE_AI_STUDIO_API_KEY="Gooogle AI Studio API KEY"
genai.configure(api_key=YOUR_GOOGLE_AI_STUDIO_API_KEY)

def get_pdf_json_response_gemini_ai(pdf_path,prompt):

    system_prompt = prompt[0]
    user_prompt = prompt[1]
    model_id = "gemini-2.0-flash-exp"
    #model_id = "gemini-1.5-flash"

    model = genai.GenerativeModel(model_id,
            system_instruction=system_prompt,
            generation_config={"response_mime_type": "application/json"})
    
    sample_pdf = genai.upload_file(pdf_path)    
    print("uploading files...")
    print(pdf_path)

    try:
        #receipt
        response = model.generate_content([user_prompt, sample_pdf])
        print(response.text)
        
    except Exception as e:
        print("Error:",e)
        sample_pdf.delete()
        return None

    res = json.loads(response.text)
    sample_pdf.delete()

    return res

def disp_image_info_in(folder_path,prompt):

    for pathname, dirnames, filenames in os.walk(folder_path):
        for filename in filenames:
            if filename.endswith('.pdf') and (not filename.startswith('-')):
                pdf_path = os.path.join(pathname,filename)
                res = get_pdf_json_response_gemini_ai(pdf_path,prompt)
                #print(res)
                time.sleep(1)
    return


def get_user_prompt(prompt_info):

    user_prompt = f"この画像が{prompt_info['doc_type']}である場合、{prompt_info['doc_type']}に記載されている"

    first_item = True
    for key ,value in prompt_info['kv'].items():
        if not first_item :
            user_prompt += '、'
        user_prompt += value
        first_item = False

    user_prompt += "の内容を教えて下さい。JSONのキーは"

    first_item = True
    for key ,value in prompt_info['kv'].items():
        if not first_item :
            user_prompt += ','    
        user_prompt += key
        first_item = False

    user_prompt += "としてください。"

    first_item = True
    for key ,value in prompt_info['kv'].items():
        if not first_item :
            user_prompt += '、'
        user_prompt += f"{key}は{value}"
        first_item = False

    user_prompt += "とします。"

    user_prompt += prompt_info['comment']
    user_prompt += f"この画像が{prompt_info['doc_type']}でない場合は、空欄のJSON形式を返してください。"

    return user_prompt
    
def main():

    #検索対象のフォルダを指定
    iDir = os.path.abspath(os.path.dirname(__file__))
    folder_name ="receipt_samples_pdf"
    folder_path = os.path.join(iDir,folder_name)
    print(folder_path)

    system_prompt = """
    あなたは会計事務所の職員です。
    書類の内容の確認を手伝ってください。
    JSON形式で日本語で返答してください。
    """
    print("system_prompt",system_prompt)

    prompt_info={'doc_type':'領収書',
        'kv':{'date':'支払日','tax_code':'登録番号','payee':'支払の相手先','amount':'合計金額','tax_amount':'合計金額に含まれる消費税の金額','contents':'支払内容','Dr':'借方の勘定科目'},
        'comment':"合計金額は税込みの金額でお願いします。支払内容が複数ある場合は空白で区切って連結して下さい。支払日は[西暦]/[月]/[日]という形式で出力して下さい。勘定科目は「売上高」「雑収入」「租税公課」「荷造運賃」「水道光熱費」「旅費交通費」「通信費」「広告宣伝費」「接待交際費」「損害保険料」「修繕費」「消耗品費」「減価償却費」「法定福利費」「給料賃金」「利子割引料」「車両費」「支払手数料」「新聞図書費」「会議費」「諸会費」「雑費」から選択してください。"}
    user_prompt = get_user_prompt(prompt_info)
    print("user_prompt",user_prompt)

    prompt =[system_prompt,user_prompt]
    disp_image_info_in(folder_path,prompt)

if __name__ == "__main__":
    main()


Gemini2.0Flash(Vertex AI 経由)

Vertex AI を利用するには、事前にプロジェクトの設定(プロジェクトの設定、課金、APIの有効化)と環境設定(認証の設定)が必要になります。プロジェクトのIDを取得してそのIDを記入してから実行して下さい。こちらも後半の方は上記とコードが重複しますが、わかりやすさのため、そのままのせておきます。model_idの部分を変えれば、Gemini1.5ProやGemini1.5Flashでも利用できます。モデルのリージョンは現在Geini2.0Flashの公開されているus-central1としています。

import os
import json
import time
import vertexai
from vertexai.generative_models import GenerativeModel, Part

PROJECT_ID = "YOUR PROJECT ID"
vertexai.init(project=PROJECT_ID, location="us-central1")

def get_pdf_json_response_gemini(pdf_path,prompt):

    system_prompt = prompt[0]
    user_prompt = prompt[1]
    model_id = "gemini-2.0-flash-exp"
    #model_id = "gemini-1.5-pro"
    #model_id = "gemini-1.5-flash"
    model = GenerativeModel(model_id,
            system_instruction=system_prompt,
            generation_config={"response_mime_type": "application/json"})
    
    with open(pdf_path, 'rb') as pdf_file:
        pdf_data = pdf_file.read()
        sample_pdf = Part.from_data(pdf_data,  "application/pdf")

    try:
        #receipt
        response = model.generate_content([user_prompt, sample_pdf])
        print(response.text)
        
    except Exception as e:
        print("Error:",e)
        return None

    res = json.loads(response.text)
    return res

def disp_image_info_in(folder_path,prompt):

    for pathname, dirnames, filenames in os.walk(folder_path):
        for filename in filenames:
            if filename.endswith('.pdf') and (not filename.startswith('-')):
                pdf_path = os.path.join(pathname,filename)
                res = get_pdf_json_response_gemini(pdf_path,prompt)
                #print(res)
                time.sleep(1)
    return


def get_user_prompt(prompt_info):

    user_prompt = f"この画像が{prompt_info['doc_type']}である場合、{prompt_info['doc_type']}に記載されている"

    first_item = True
    for key ,value in prompt_info['kv'].items():
        if not first_item :
            user_prompt += '、'
        user_prompt += value
        first_item = False

    user_prompt += "の内容を教えて下さい。JSONのキーは"

    first_item = True
    for key ,value in prompt_info['kv'].items():
        if not first_item :
            user_prompt += ','    
        user_prompt += key
        first_item = False

    user_prompt += "としてください。"

    first_item = True
    for key ,value in prompt_info['kv'].items():
        if not first_item :
            user_prompt += '、'
        user_prompt += f"{key}は{value}"
        first_item = False

    user_prompt += "とします。"

    user_prompt += prompt_info['comment']
    user_prompt += f"この画像が{prompt_info['doc_type']}でない場合は、空欄のJSON形式を返してください。"

    return user_prompt
    
def main():

    #検索対象のフォルダを指定
    iDir = os.path.abspath(os.path.dirname(__file__))
    folder_name ="receipt_samples_pdf"
    folder_path = os.path.join(iDir,folder_name)
    print(folder_path)

    system_prompt = """
    あなたは会計事務所の職員です。
    書類の内容の確認を手伝ってください。
    JSON形式で日本語で返答してください。
    """
    print("system_prompt",system_prompt)

    prompt_info={'doc_type':'領収書',
        'kv':{'date':'支払日','tax_code':'登録番号','payee':'支払の相手先','amount':'合計金額','tax_amount':'合計金額に含まれる消費税の金額','contents':'支払内容','Dr':'借方の勘定科目'},
        'comment':"合計金額は税込みの金額でお願いします。支払内容が複数ある場合は空白で区切って連結して下さい。支払日は[西暦]/[月]/[日]という形式で出力して下さい。勘定科目は「売上高」「雑収入」「租税公課」「荷造運賃」「水道光熱費」「旅費交通費」「通信費」「広告宣伝費」「接待交際費」「損害保険料」「修繕費」「消耗品費」「減価償却費」「法定福利費」「給料賃金」「利子割引料」「車両費」「支払手数料」「新聞図書費」「会議費」「諸会費」「雑費」から選択してください。"}
    user_prompt = get_user_prompt(prompt_info)
    print("user_prompt",user_prompt)

    prompt =[system_prompt,user_prompt]
    disp_image_info_in(folder_path,prompt)

if __name__ == "__main__":
    main()

まとめ

ChatGPT-4oとGmini2.0Flashは、勘定科目を含めて、いずれも画像から必要な情報を抽出できているように思いました。この例では、個人的には、Gmini2.0FlashのほうがPDFファイルの扱いが簡単であり、また、取引内容を適切に表現できているように感じました。

この実験で、実用的なレベルで仕訳の基礎となるデータをえられることがわかったので、次に、コストの比較と、実際にスキャンした領収書をまとめて仕訳ファイルにするプログラムの作成にとりかかることにしました。

領収書から仕訳を出力する

上記の結果をもとに、我が家の確定申告で利用を念頭に、特定のフォルダに入っている複数の領収書のPDFファイルから、マネーフォワードクラウド会計で読み込みが可能なCSVファイルをを出力するプログラムを作成してみました。現金支払いの領収書の仕訳を出力することを想定しています。プログラムを一部変更することにより、弥生会計のファイルも出力できるようにしてみました。以下からダウンロードいただけます。

ダウンロード

プログラムは以下からダウンロードいただけます。言語は、python3を利用しています。

  • 個人事業主で、確定申告で領収書の入力がめんどうな方

  • 会計事務所の方

  • 法人の経理をされている方

は使ってみて下さい。

コード自体はMITライセンスで提供しますので、著作権表示(著作者名と、このサイトへのリンクの記載)とMITライセンスの全文の表示を残しておいてして頂ければ、商用利用を含めて、それぞれの用途のために、自由に修正して、ご利用いただいて結構です。

私自身はGemini(Vertex AI)を利用して実際の仕訳を作成しました。プログラム中のコメントを外すことにより、ChatGPTやGemini(Google AI Studio)でも利用できるようになっています。ぜひ、ご活用ください。気に入ってくださったらぜひ「スキ」をお願いいたします。

以下、このプログラムの使い方やご自身で利用されるにあたってのカスタマイズの方法についてご説明いたします。ご自分の業務にご利用されるにあたって、参考になるのではと思います。

また、実際にご利用されて、「バイトの人を1人1時間雇うよりは役たったな」思われたら、ぜひ記事の購入をお願いいたします。将来的にも利用できるものなので、それなにりお役にたてるのではないかと思います。

追記(2025/09/14)Google Gen AI SDK対応

Vertex AI SDK の生成 AI モジュールは非推奨になり、2026 年 6 月 24 日以降は使用できなくなることとなりました。代わりに、従来のVertex AI SDK の機能を含むGoogle Gen AI SDKの利用が推奨されています。
そこで、Google Gen AI SDKを利用したプログラムを作成しました。Vertex AIを利用される方は、こちらをご利用下さい。

k1nk/receipt_to_siwake: 現金払いの領収書から会計ソフトでインポート可能な仕訳ファイルを作成

ここから先は

7,232字 / 5画像

Amazon Payで支払うと最大2%還元のチャンス! 9/30まで

この記事が気に入ったらチップで応援してみませんか?