実装ガイド ・ 証憑読み取り
AIで領収書・証憑を読み取り、
会計データに変換する
完全実装ガイド
画像を理解するAIモデルを使って、領収書やレシートを「会計ソフトに取り込めるデータ」へ変換する——その仕組みを、実際に動くコードとともに、設計思想から本番運用まですべて公開します。従来のOCRが越えられなかった壁を、なぜ今のAIが越えられるのか。精度をどう測り、間違いをどう検知し、人の確認をどこまで減らせるのか。手を動かせるレベルまで、一切ぼかさずに書きました。
エンジニアでない方へ
この記事にはプログラムのコードが出てきますが、コードは読み飛ばして大丈夫です。図と本文だけでも「AIが領収書をどう読むのか」「なぜ実務で使えるのか」が分かるように書いています。まずは、全体の流れを1枚の図で。
01
なぜ今、証憑の自動読み取りが現実になったのか
領収書やレシートをデータ化する試みは、20年以上前から存在します。スキャナ付属のOCRソフト、クラウド会計の自動読取機能、専用のAI-OCRサービス。それでも多くの会計事務所で「結局、人が全部打ち直している」のはなぜか。ここを理解しないと、実装の設計を誤ります。
従来のOCR(光学文字認識)は、「画像の中の文字を、文字列として拾う」技術でした。「¥1,320」という並びを見て「1320」という数字を返す。これは得意です。しかし証憑処理の本質は、文字を拾うことではありません。
実務で人がやっているのは、拾った情報を「会計の文脈」で解釈することです。この日付は令和何年で、西暦に直すと何年か。この店の「お茶菓子代」は会議費か交際費か。この税率8%は軽減税率の対象か。インボイスの登録番号は正しい形式(T+13桁)か。——これらは文字認識ではなく、判断です。従来OCRはここができなかった。だから文字は拾えても仕分けは人がやることになり、二度手間になった。
近年の画像を理解する大規模言語モデル(Vision対応LLM)が決定的に違うのは、この「判断」の部分を担えることです。単に「1320」と読むのではなく、「これは税込金額で、内訳は10%対象と8%対象に分かれていて、勘定科目は会議費が妥当」というところまで、文脈で処理できる。これが、20年変わらなかった風景を変える技術的な転換点です。
従来のOCR
¥1,320↓ 文字を拾うだけ
——数字は読めるが、それが何を意味するかは分からない。仕分けは人がやる。
今のAI
¥1,320↓ 意味で理解する
ひとことで言うと
昔のOCRは「文字を読む」だけの機械。今のAIは「読んで、さらに意味を考える」。この差が、20年変わらなかった"結局は人が打ち直す"を変えます。
この記事の前提
本記事のコードは Anthropic の Claude(Vision対応・構造化出力対応) を例に書いています。ただし設計思想——スキーマ駆動・推測抑制・自己検知・人の確認——は、他のVision対応LLMでも同じように適用できます。特定サービスの使い方ではなく、"証憑を読むシステムをどう設計するか"を主眼にしています。
02
全体アーキテクチャ:4つの層
個別のコードに入る前に、全体像を掴みます。証憑読み取りシステムは、次の4つの層で構成します。この分け方が、後の実装のしやすさと、運用の安全性を決めます。
┌─────────────────────────────────────────────┐ │ ① 入力層 画像 / PDF → APIに渡せる形式へ │ ├─────────────────────────────────────────────┤ │ ② 抽出層 Vision LLM × 抽出スキーマ │ │ 「何を・どの型で取り出すか」を定義 │ ├─────────────────────────────────────────────┤ │ ③ 検証層 構造化データを正規化・突合 │ │ 自己検知(要確認)を仕分け │ ├─────────────────────────────────────────────┤ │ ④ 出力/人間層 会計ソフト取込形式で出力 │ │ "要確認"だけを人がレビュー │ └─────────────────────────────────────────────┘
重要なのは、②の抽出層で「精度100%」を目指さない設計にすることです。どんなAIも100%にはなりません。だから③の検証層で「AIが自信のない項目」を仕分け、④で人はそこだけを見る。この「AIが自分の不確かさを申告する」仕組みこそが、実務投入できるかどうかの分かれ目です。単に読ませるだけのシステムは、間違いに気づけないので現場で使えません。
03
技術選定:なぜこの構成なのか
実装に使う3つの技術要素と、その選定理由を説明します。
(1) Vision対応LLM(画像を理解するモデル)
領収書は「非定型」です。店ごとにレイアウトが違い、手書きも混じり、写真は傾いたり影が入ったりする。定型フォーマット前提のOCRでは対応しきれません。Vision対応LLMは、レイアウトに依存せず「意味」で読むので、非定型に強い。ここは代替が効きません。
(2) 構造化出力(Structured Outputs / JSON Schema)
LLMに自由に文章で答えさせると、フォーマットが毎回ブレて、後段の処理が破綻します。出力を JSON Schema に強制する「構造化出力」を使うと、「必ずこのキーで、この型で返る」ことが保証されます。これが実装を劇的に楽にします。プログラムで扱うデータは、必ず構造化出力で取り出すのが鉄則です。
(3) 適応的思考(Adaptive Thinking)
税率の内訳計算や科目の判断は、単純な読み取りより「考える」工程が要ります。モデルに応じてthinking: {type: "adaptive"} を有効にすると、必要なときだけ内部で推論してから答えるので、判断を伴う抽出の精度が上がります。
よくある誤解
「プロンプトで "JSONで返して" と書けば十分では?」——不十分です。それはあくまで"お願い"で、モデルが従わない・キーがずれる・余計な説明文が混じる、が起きます。構造化出力(output_config.format)は、スキーマ違反を仕組みで防ぐので、信頼性が段違いです。プロダクションでは必ずこちらを使います。
04
実装①:画像・PDFを入力に変換する
まず、手元の画像やPDFを、APIに渡せる形(コンテンツブロック)に変換します。画像とPDFで渡し方が少し違う点に注意します。
import { readFile } from "node:fs/promises";
import { extname } from "node:path";
// 拡張子 → MIMEタイプの対応表
const MEDIA_TYPES: Record<string, string> = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".webp": "image/webp",
".pdf": "application/pdf",
};
// ファイルを、APIに渡すコンテンツブロックへ変換する
export async function toContentBlock(filePath: string) {
const ext = extname(filePath).toLowerCase();
const mediaType = MEDIA_TYPES[ext];
if (!mediaType) {
throw new Error(`未対応の拡張子: ${ext}`);
}
// バイナリを base64 文字列に(改行を含めないこと)
const data = (await readFile(filePath)).toString("base64");
// PDF は document ブロック、画像は image ブロックで渡す
if (mediaType === "application/pdf") {
return {
type: "document" as const,
source: { type: "base64", media_type: "application/pdf", data },
};
}
return {
type: "image" as const,
source: { type: "base64", media_type: mediaType, data },
};
}
ポイントは2つ。base64文字列に改行を混ぜないこと(一部の変換関数は76文字ごとに改行を入れるので注意)。そしてPDFと画像でブロックの種類を分けること。領収書はスマホ写真(画像)、請求書はPDFで届くことが多いので、両対応にしておくと実務でそのまま使えます。
05
実装②:抽出スキーマの設計(最重要)
ここがシステムの心臓部です。「何を、どの型で取り出すか」を定義するスキーマの質が、精度と運用のすべてを決めます。時間をかける価値があるのは、プロンプトより、まずここです。
設計原則1:読めない項目は null を返させる
最も大事な原則です。人間は「たぶんこうだろう」で埋めてしまいますが、AIにそれをやらせると「もっともらしいが間違った値」が混入し、これが一番危険です。だから各項目を「値 または null」を許容する型にし、「書類に無ければ必ず null」とルール化します。推測で埋めさせない。これが証憑処理の鉄則です。
設計原則2:全項目に「要確認」の自己申告欄をつける
AIに「自信のない項目を、理由つきで自己申告させる」フィールドを、全書類共通で持たせます。ここに挙がった項目だけ人が見る。これが④の人間レビューを最小化する鍵になります。
// 全書類に付ける、人のレビューに回すための共通項目
const REVIEW_FIELDS = {
uncertain_fields: {
type: "array",
description:
"読み取りに自信がない項目名と理由。確実な項目は含めない。",
items: {
type: "object",
properties: {
field: { type: "string", description: "項目名(スキーマのキー名)" },
reason: { type: "string", description: "なぜ自信がないか(不鮮明・手書き等)" },
},
required: ["field", "reason"],
additionalProperties: false,
},
},
notes: {
type: ["string", "null"],
description: "特記事項・矛盾があれば記載。無ければ null。",
},
};
次に、領収書そのもののスキーマです。日本の税務実務で必要な項目を、一つずつ意味を持たせて定義します。
const receiptSchema = {
type: "object",
properties: {
issuer: { type: ["string", "null"], description: "発行者・店名" },
// インボイス制度対応:T + 13桁。無ければ null
registration_number: {
type: ["string", "null"],
description: "インボイス登録番号(T+13桁)。無ければ null。",
},
// 日付は必ず西暦 YYYY-MM-DD に正規化させる(和暦→西暦はモデルに任せる)
issue_date: {
type: ["string", "null"],
description: "発行日。YYYY-MM-DD に正規化。和暦は西暦へ。",
},
total_amount: { type: ["number", "null"], description: "税込合計(円・数値のみ)" },
tax_amount: { type: ["number", "null"], description: "消費税額の合計(円)" },
// 税率ごとの内訳。軽減税率(8%)と標準(10%)が混在する領収書に対応
tax_breakdown: {
type: "array",
description: "税率ごとの内訳。記載が無ければ空配列。",
items: {
type: "object",
properties: {
rate: { type: "number", description: "税率(10 または 8)" },
taxable_amount: { type: ["number", "null"], description: "対象額(税抜)" },
tax_amount: { type: ["number", "null"], description: "消費税額" },
},
required: ["rate", "taxable_amount", "tax_amount"],
additionalProperties: false,
},
},
payment_method: { type: ["string", "null"], description: "支払方法" },
// 勘定科目の一次推定。判断できなければ null(無理に埋めさせない)
suggested_account: {
type: ["string", "null"],
description: "推定勘定科目(会議費・旅費交通費等)。不明なら null。",
},
...REVIEW_FIELDS, // ← 共通の要確認フィールドを合成
},
// 構造化出力の制約:全プロパティを required に、追加プロパティは禁止
required: [
"issuer", "registration_number", "issue_date",
"total_amount", "tax_amount", "tax_breakdown",
"payment_method", "suggested_account",
"uncertain_fields", "notes",
],
additionalProperties: false,
};
構造化出力の制約 ― ここでハマる
JSON Schema による構造化出力には制約があります。すべてのオブジェクトに additionalProperties: false と、全プロパティを列挙した required が必要です(「必須」の意味ではなく、スキーマを厳格化するための要求)。また minimum / maxLength などの数値・文字列制約や、再帰スキーマはサポートされません。制約を使いたい場合は、受け取った後にコード側で検証します。
「値 または null」を type: ["number", "null"] と書いているのがポイントです。これで「読めなければ null」がスキーマレベルで表現でき、モデルも従いやすくなります。tax_breakdown を配列にしているのは、コンビニの領収書のように1枚に10%対象と8%対象が混在するケースにインボイス制度下では正しく対応するためです。ここを単一の税率フィールドにすると、軽減税率が絡んだ瞬間に破綻します。
06
実装③:プロンプト設計 ― 推測させない技術
スキーマが「何を返すか」を定義するのに対し、プロンプトは「どう振る舞うか」を指示します。証憑処理のプロンプトで最重要なのは、「わからないことを、わからないと言わせる」——つまり推測を抑制することです。
export function buildPrompt(): string {
return [
"添付は日本の領収書です。記載内容をスキーマどおりに抽出してください。",
"",
"厳守事項:",
"1. 書類に書かれていない項目は、推測せず必ず null にする。",
" もっともらしい値を作らないこと。",
"2. 金額はカンマ・円記号・全角数字を除いた半角の数値にする。",
"3. 日付は西暦 YYYY-MM-DD に正規化する(和暦は西暦へ換算)。",
"4. 読み取りに少しでも不安がある項目は uncertain_fields に",
" 理由つきで入れる。迷ったら入れる方が安全。",
"5. 合計金額と明細の合計が合わない場合は、書類の記載どおりに",
" 抽出したうえで notes に矛盾を明記する。",
].join("\n");
}
各行に意味があります。1番が推測抑制の核。4番で「迷ったら要確認に入れろ」と促すことで、後段の人間レビューが機能します。ここを「自信のあるものだけ挙げろ」と書くと、AIは過信して見逃すので、あえて「迷ったら入れる」=安全側に倒す指示にします。5番が地味に効きます。合計と内訳が合わない領収書(手書きの計算ミス等)は現実に存在し、そのときAIが数字を"直して"しまうと改ざんになる。だから「記載どおりに抽出し、矛盾は notes に書け」と分離させます。
07
実装④:API呼び出しと堅牢化
入力・スキーマ・プロンプトが揃ったので、実際にAPIを呼びます。ここではエラー処理を省かないのが本番品質のポイントです。
import Anthropic from "@anthropic-ai/sdk";
import { toContentBlock } from "./input.js";
import { receiptSchema } from "./schemas.js";
import { buildPrompt } from "./prompt.js";
const MODEL = "claude-opus-4-8"; // 判断を伴うのでOpus級を推奨
export async function extractReceipt(filePath: string, client = new Anthropic()) {
const attachment = await toContentBlock(filePath);
const response = await client.messages.create({
model: MODEL,
max_tokens: 16000,
thinking: { type: "adaptive" }, // 判断工程のために思考を有効化
output_config: { // ← 構造化出力を強制
format: { type: "json_schema", schema: receiptSchema },
},
messages: [
{
role: "user",
content: [attachment, { type: "text", text: buildPrompt() }],
},
],
});
// ── 停止理由のチェック(省略しないこと)──
if (response.stop_reason === "max_tokens") {
throw new Error("出力が途中で打ち切られました。max_tokens を増やしてください。");
}
if (response.stop_reason === "refusal") {
throw new Error("モデルが応答を拒否しました。");
}
// ── テキストブロックを取り出して JSON.parse ──
const textBlock = response.content.find((b) => b.type === "text");
if (!textBlock || textBlock.type !== "text") {
throw new Error("テキストブロックが返りませんでした。");
}
// 構造化出力なので JSON は valid。ただし念のため try/catch
try {
return JSON.parse(textBlock.text) as Record<string, unknown>;
} catch {
throw new Error(`JSONとして解釈できませんでした: ${textBlock.text.slice(0, 300)}`);
}
}
最新APIの落とし穴 ― 古い書き方は動かない
この分野は変化が速く、少し前の書き方はエラーになります。①思考は thinking: {type: "adaptive"}(旧来の budget_tokens を指定する固定枠方式は最新モデルで廃止)。②出力形式は output_config.format(旧 output_format は非推奨)。③応答を JSON に誘導するための「アシスタント側の書きかけ(prefill)」は最新モデルで不可——構造化出力に置き換えます。④温度パラメータ(temperature 等)も最新モデルでは送るとエラー。挙動はプロンプトで制御します。
08
実装⑤:精度をどう測るか ― 自己検知率という指標
「AIで読めます」で終わらせないために、精度を数値で測ります。ただし——ここで多くの人が測る指標を間違えます。単純な「正解率」だけでは、実務に使えるかは判断できません。
本当に見るべきは2つ。正解率と、間違えたときにAIが自分で「要確認」に挙げられていたか=自己検知率です。なぜなら、実務での運用は「AIが確実な分は自動処理し、怪しい分だけ人が見る」だからです。極端に言えば、正解率が多少低くても、間違いを全部自己申告できていれば商品になる(怪しい分だけ人が見ればいい)。逆に、正解率が高くても間違いを自己申告できていなければ危険(見逃す)。
// 金額・日付・文字列を「表記ゆれを吸収して」比較する
function valuesMatch(expected: unknown, actual: unknown): boolean {
if (expected == null) return actual == null;
if (actual == null) return false;
// 数値:全角→半角、記号除去のうえ誤差なしで比較
if (typeof expected === "number") {
const a = normalizeNumber(actual);
return a !== null && Math.abs(a - expected) < 0.005;
}
// 日付:YYYY-MM-DD へ正規化して比較(表記ゆれ吸収)
if (looksLikeDate(expected)) {
return normalizeDate(expected) === normalizeDate(actual);
}
// 文字列:全角半角・記号・空白を落として比較
return normalizeText(String(expected)) === normalizeText(String(actual));
}
// ── 全体の集計 ──
export function summarize(fields: FieldResult[]) {
const wrong = fields.filter((f) => !f.match);
const right = fields.filter((f) => f.match);
return {
accuracy: right.length / fields.length, // 正解率
// ★ 間違えた項目のうち、AIが自己申告できていた割合
selfDetectionRate:
wrong.length === 0 ? null
: wrong.filter((f) => f.flagged).length / wrong.length,
// 正解なのに"要確認"に挙げた割合(人の手間になる=低い方がよい)
overFlagRate:
right.filter((f) => f.flagged).length / right.length,
};
}
比較関数で表記ゆれを吸収しているのが実務的なポイントです。「¥1,320」と「1320」、全角「1320」と半角、「令和6年6月15日」と「2024-06-15」——これらを"不一致"と数えると、精度を過小評価します。正規化してから比べることで、本当の意味での正誤が測れます。
| 指標 | 意味 | 判断 |
|---|---|---|
| 正解率 | 全項目のうち正しく読めた割合 | 高いほどよい(ただし単独では不十分) |
| 自己検知率 | 間違えた項目を自分で"要確認"に挙げられた割合 | ここが高いと商品になる(見逃さない) |
| 過剰申告率 | 正解なのに"要確認"に挙げた割合 | 低いほどよい(人の手間が減る) |
09
本番運用の設計 ― 人は"確認だけ"
ここまでで「1枚を読む」実装ができました。本番では、これを運用に載せます。設計のポイントを列挙します。まず、運用の全体像を1枚で。
これが「9割自動・1割確認」の姿です。人が全部を見るのではなく、AIが自分で「ここは怪しい」と挙げた所だけを見る。だから枚数が増えても、人の手間はほとんど増えません。以下、この運用を支える設計のポイントです。
(1) レビュー画面は"要確認"を先頭に
uncertain_fields が空の証憑は自動で確定キューへ、1つでもあればレビューキューへ回します。レビュー画面では、AIが挙げた項目だけをハイライトし、元画像を横に並べる。人は全項目ではなく、その項目だけを見て確定する。これで1枚あたりの確認時間が数十秒→数秒になります。
(2) 会計ソフト取込形式で出力
確定したデータは、freee・マネーフォワード・弥生などが取り込めるCSV形式に変換して書き出します。ここを「AIの出力 → そのまま取込ファイル」までつなぐと、二重入力が完全に消えます。各ソフトのインポート仕様に合わせた列マッピングを1度作れば、あとは使い回せます。
(3) 電子帳簿保存法への対応
スキャナ保存・電子取引データの保存要件(検索要件=取引年月日・金額・取引先で検索できること)は、抽出した issue_date / total_amount / issuer をそのままインデックスにできます。つまり読み取りと同時に、検索要件を満たすメタデータが手に入る。これは副次的だが大きな利点です。
(4) バッチ処理とコスト
月初にまとめて数百枚を処理するなら、並列度を絞って(レート制限に配慮して)順に流します。コストは1枚あたり数円〜十数円のオーダー。人件費(1枚あたり数十秒×時給)と比べれば桁違いに安い。急がない大量処理は、バッチAPI(半額)を使う手もあります。
import { readdir } from "node:fs/promises";
import { extractReceipt } from "./extract.js";
async function run(dir: string) {
const files = (await readdir(dir)).filter((f) => /\.(png|jpe?g|pdf)$/i.test(f));
const autoConfirm: unknown[] = []; // 自動確定キュー
const needsReview: unknown[] = []; // 人がレビュー
for (const f of files) {
const data = await extractReceipt(`${dir}/${f}`);
const flagged = (data.uncertain_fields as unknown[])?.length ?? 0;
// ★ AIが"要確認"を1つも挙げなければ自動確定へ
(flagged === 0 ? autoConfirm : needsReview).push({ file: f, data });
}
console.log(`自動確定 ${autoConfirm.length} 件 / 要レビュー ${needsReview.length} 件`);
return { autoConfirm, needsReview };
}
10
実例 ― 1枚の領収書が、こうデータになる
抽象論だけでは伝わりにくいので、具体的な1枚で示します。次のような領収書を入力したとします。
入力:ある領収書(会議室サービス)
発行者「有限会社みなと会議室サービス」、宛名「株式会社◯◯ 御中」、発行日「令和8年7月18日」、金額「¥28,160-」、内訳は会議室利用料15,000円(10%)と飲料・軽食9,600円(軽減8%)と資料印刷代2,000円(10%)、支払方法クレジット、登録番号「T7010401055512」。
この1枚を、これまでのスキーマ・プロンプトで処理すると、次のJSONが返ります。
{
"issuer": "有限会社みなと会議室サービス",
"registration_number": "T7010401055512",
"issue_date": "2026-07-18", // 令和8年 → 西暦に正規化
"total_amount": 28160, // 円記号・全角・カンマを除去
"tax_amount": 2468,
"tax_breakdown": [ // 10%と8%が正しく分離
{ "rate": 10, "taxable_amount": 17000, "tax_amount": 1700 },
{ "rate": 8, "taxable_amount": 9600, "tax_amount": 768 }
],
"payment_method": "クレジットカード",
"suggested_account": "会議費", // 科目を一次推定
"uncertain_fields": [], // ← 空=自動確定キューへ
"notes": null
}
注目すべき点がいくつもあります。令和8年 → 2026年の和暦換算、¥28,160- → 28160の記号除去、そして10%と8%が別々の行に分離されている。さらに「会議室利用料」という文脈から勘定科目を「会議費」と一次推定している。uncertain_fields が空なので、この1枚は人の確認を経ずに自動確定キューへ回ります。従来OCRが「28160」という数字を返すだけだったのに対し、ここまで"仕分けられた状態"で返るのが、決定的な差です。
一方、もし発行日が擦れて読めなければ、AIは次のように返します——値はnullのまま、理由つきで自己申告する。
{
// ... 他の項目 ...
"issue_date": null,
"uncertain_fields": [
{ "field": "issue_date",
"reason": "日付印が擦れており、7月か9月か判別できない" }
],
"notes": "発行日は要確認"
}
この1枚だけ、レビューキューに回る。人はissue_date の1項目だけを、元画像と照らして確定する。これが「9割自動・1割確認」の実際の姿です。
11
モデル選定 ― 精度とコストのバランス
どのモデルを使うかで、精度もコストも変わります。証憑処理は「単なる読み取り」ではなく「判断」を含むので、ここでのモデル選定は品質に直結します。
原則は「判断の重さで使い分ける」こと。日付や金額を拾うだけなら軽量モデルでも足りますが、税率区分・勘定科目の推定・矛盾の検知といった判断が絡む工程では、上位モデルの方が明確に精度が上がります。証憑処理は判断が絡むので、基本は上位モデルを推奨します。
| モデル階層 | 向く工程 | コスト感(相対) |
|---|---|---|
| 上位(Opus級) | 税率区分・科目推定・矛盾検知を含む本処理 | 高い(それでも1枚数円〜十数円) |
| 中位(Sonnet級) | 定型的で判断の少ない書類の一次処理 | 中 |
| 軽量(Haiku級) | 単純な分類・振り分け・前処理 | 低い |
実務的なおすすめは、本処理は上位モデルで精度を確保しつつ、大量の前処理(この画像は領収書か請求書かの振り分け等)は軽量モデルに任せる2段構えです。全部を最上位で回すより、判断の要らない所を軽量化することで、精度を落とさずコストを下げられます。
コスト最適化の順番
まず上位モデルで「実務に耐える精度が出るか」を確認してから、コスト最適化に進むこと。逆順(安いモデルで作り始める)は、精度が出ずに「AIは使えない」と誤った結論に至りがちです。精度が出ることを確認 → 落とせる工程だけ軽量化、が鉄則です。
12
セキュリティと法令 ― 会計データを扱う責任
証憑には、取引先名・金額・口座情報など機微な情報が含まれます。技術だけでなく、扱う責任の設計も本番システムの一部です。ここを軽視すると、精度がいくら高くても導入できません。
(1) 送信先とデータの扱いを確認する
画像をどのAPIに送るのか、そのデータが学習に使われないか、保持期間はどうか——利用するサービスのデータ利用ポリシーを必ず確認します。事業者向けのAPI利用は、一般に入力データを学習に使わない設計になっていることが多いですが、契約・設定レベルで明示的に確認するのが安全です。顧問先のデータを預かる士業なら、なおさら必須です。
(2) 保存と検索(電子帳簿保存法)
§09でも触れたとおり、抽出したissue_date / total_amount / issuer は、そのまま電帳法の検索要件(取引年月日・金額・取引先で検索可能)を満たすインデックスになります。読み取りと保存要件の充足を一度に済ませられるのは、正しく設計した場合の大きな副産物です。ただし、真実性の確保(タイムスタンプ等)の要件は別途あるため、保存システム側の対応も必要です。
(3) アクセス権と監査ログ
誰がどの証憑を見て、どう確定したか。確定操作の監査ログを残す設計にしておくと、後から「この仕訳は誰が確定したか」を追えます。AIが提案し、人が確定した——この責任分界を記録することが、実務での信頼につながります。
最重要 ― 有資格者の確認
勘定科目の最終決定、税務判断、法令適合の可否は、税理士等の有資格者が確認する前提で設計してください。AIの suggested_account はあくまで"一次推定"です。ここを「AIが決めた」ことにすると、税務・法令上の責任問題になります。AIは提案、確定は人(有資格者)——この線を、システムのフローに明示的に組み込みます。
13
コストの実額試算
「AIは高いのでは」という懸念に、実額で答えます。証憑1枚の処理コストは、入力(画像+プロンプト)と出力(JSON)のトークン量で決まります。領収書1枚なら、おおむね1枚あたり数円〜十数円のオーダーに収まります。
これを人件費と比べます。人が1枚を入力・仕分けするのに、仮に平均40秒かかるとします。時給1,500円なら、1枚あたりの人件費は約16.7円。AIの処理コストとほぼ同等かそれ以下で、しかもAIは数秒で処理し、深夜も繁忙期も一定です。
| 項目 | 人が手入力 | AI+確認 |
|---|---|---|
| 1枚あたり時間 | 約40秒 | 数秒(要確認分のみ人が確認) |
| 1枚あたりコスト | 約16.7円(人件費) | 数円〜十数円(API)+確認分の人件費 |
| 月5,000枚の処理時間 | 約55時間 | 大半が自動、確認は数時間 |
| 繁忙期・深夜 | 残業で対応 | 処理能力は一定 |
さらにコストを抑える手段が2つあります。①急がない大量処理はバッチAPIを使う(同期処理の半額になるサービスがあります)。月初にまとめて処理する証憑入力は、まさにバッチ向きです。②判断の要らない前処理を軽量モデルに回す(§11)。この2つで、コストはさらに下げられます。
結論として、コストはボトルネックになりません。人を1人増やす採用コスト(求人費・教育・社会保険・それでも辞めるリスク)と比べれば、方向性は明白です。ボトルネックになり得るのは、コストではなく「要確認をどれだけ減らせるか=自己検知の質」の方です。だから§05・§08の設計に時間をかける価値があります。
14
つまずきポイント9選と対策
実際に作ると必ず出会う落とし穴を、対策とともにまとめます。ここを先に知っているだけで、開発時間が大きく変わります。
| # | つまずき | 対策 |
|---|---|---|
| 1 | 全角数字・カンマ・円記号が混じる | プロンプトで「半角数値のみ」を指示+受け取り後に正規化 |
| 2 | 和暦(令和6年)が西暦に直らない | スキーマ description とプロンプトの両方で YYYY-MM-DD を明示 |
| 3 | 軽減税率(8%)と標準(10%)の取り違え | 税率を単一項目にせず tax_breakdown 配列で持つ |
| 4 | 読めない項目を"それっぽく"埋める | 「無ければ null・推測禁止」をプロンプト1番目に |
| 5 | JSON出力に説明文が混じって parse 失敗 | 構造化出力(output_config.format)を使う。プロンプト依存にしない |
| 6 | 末尾の改行や空白で文字列一致が崩れる | 比較時に trim/正規化。生の文字列一致で判定しない |
| 7 | 出力が途中で切れる | stop_reason を確認し max_tokens を上げる |
| 8 | 安いモデルで判断の精度が出ない | 判断(科目・税率)が絡む工程は上位モデルを使う |
| 9 | 古いAPIの書き方でエラー | prefill・budget_tokens・temperature を使わない(§07参照) |
15
できること・できないことの限界
誠実さのために、現時点の限界を明記します。これを知らずに導入すると「話が違う」になります。
◎ 現実的にできる
- 領収書・レシートの読み取り(写真から会計データへ)
- 税率区分・消費税額の判定
- インボイス登録番号の抽出
- 勘定科目の一次推定
- 不確かな項目の自己申告
- 会計ソフト取込形式の生成
△ まだ人が残る
- AIが"自信なし"とした項目の最終確認
- 初めての取引先の科目の最終判断
- 事務所ごとの特殊な処理ルール
- 極端に不鮮明・破損した画像
- そもそも証憑が提出されない問題
繰り返しますが、目指すのは「人ゼロ」ではなく「入力を9割減らし、残る1割を確認作業に変える」ことです。この現実的な線引きを最初から設計に組み込むのが、失敗しないコツです。
16
横展開:請求書・見積書・点呼記録簿へ
ここまでの設計の美点は、「スキーマとプロンプトを差し替えるだけ」で、他の書類にそのまま横展開できることです。入力層(§04)・API呼び出し(§07)・評価(§08)・運用(§09)は共通のまま、receiptSchema を別のスキーマに替えるだけ。
// 書類タイプごとにスキーマを持ち、共通エンジンで切り替える
export const SCHEMAS = {
receipt: receiptSchema, // 領収書(会計・記帳)
invoice: invoiceSchema, // 請求書(士業・共通)
estimate: estimateSchema, // 見積書(建設)
tenko: tenkoSchema, // 点呼記録簿(運送)
};
// 例:点呼記録簿なら、法令上の記載漏れも自動チェックできる
const tenkoSchema = {
type: "object",
properties: {
operator_name: { type: ["string", "null"] },
record_date: { type: ["string", "null"], description: "YYYY-MM-DD" },
entries: { type: "array", items: { /* 運転者・点呼時刻・アルコール値… */ } },
// ★ 法令上の記載漏れ疑いを自動で列挙させる
compliance_flags: {
type: "array",
description: "アルコール検知値の未記載・点呼執行者の欠落 等",
items: { type: "string" },
},
...REVIEW_FIELDS,
},
/* required / additionalProperties: false ... */
};
点呼記録簿の例では、compliance_flags を持たせることで「アルコール検知値が未記載」「点呼執行者名が抜けている」といった法令上の記載漏れを、読み取りと同時に検知できます。単なるデータ化を超えて、コンプライアンスのチェックまで一度に行える。これが「1エンジン・多書類」設計の強さです。士業の請求書、建設の見積書、運送の点呼記録——業種が違っても、土台は同じコードで回せます。
17
まとめ
証憑の自動読み取りは、もう「できるかどうか」の段階ではありません。正しく設計すれば、実務で使えるレベルで動きます。要点を振り返ります。
- 従来OCRとの本質的差は「文字認識」ではなく「文脈判断」ができること
- 心臓部は抽出スキーマ。「読めなければ null」「要確認の自己申告」を組み込む
- データは必ず構造化出力で取り出す。プロンプト依存にしない
- 精度は正解率だけでなく自己検知率で測る。見逃さないかが商品性を決める
- 運用は「人は要確認だけ見る」設計に。9割自動・1割確認
- スキーマ差し替えで請求書・見積書・点呼記録簿へ横展開できる
この記事のコードは、実際に動作を確認した実装をもとにしています。あなたの環境で試し、あなたの書類で精度を測ってみてください。そして——もし「自分で作る時間はないが、この仕組みを自社に導入したい」なら、下の診断から始められます。
所要3分・無料
この仕組みを、御社の業務で使うと
何時間・いくら減るか診断する
証憑入力・請求・点呼記録など、御社の業務量を入力すると、削減見込み額・回収期間・導入プラン・使える補助金の見込みまで、AIが1枚のレポートにまとめます。
無料AI診断をはじめる →
※ 本記事は実装の技術情報を提供するものであり、特定の成果・精度を保証するものではありません。コードは解説用に簡略化しています。実際の運用では、各APIの最新仕様・利用規約・セキュリティ要件をご確認ください。
※ 会計・税務・法令対応の適否は、有資格者(税理士等)の確認を受けてください。本記事は税務助言ではありません。
※ 補助金(デジタル化・AI導入補助金)の対象可否・補助率・上限は申請枠および審査により変動し、必ず受給できるものではありません。
発行:ジムレス(Jimuless) / 士業・建設・運送のためのAI自動化メディア