なぜ space-ocr は他の LLM OCR と違うのか:検証できる構造化抽出
LLM に直接 OCR をさせる場合との違い。space-ocr はフィールドをページ上の座標と verified 判定付きで返し、確認すべき項目は review の一覧に載せる。
領収書や請求書を GPT-4o、Gemini、Claude に渡して、合計金額・取引先・明細を読み取ってもらうことはできる。たいていはそれらしい JSON が返ってくる。問題が出てくるのは、これを大量処理で信頼しようとしたときだ。モデルが返すのは文字列であり、文字列には所在がない。合計が 48,200 と返ってきたとして、それはページのどのピクセルを読んだ結果なのか。その数字は実際に書類へ印字されていたのか、それともモデルがもっともらしい値を埋めただけなのか。LLM を素のまま呼び出すだけでは、自分でページを読み直さないかぎりこれには答えられない。
この差こそ、汎用 LLM を OCR ツールとして使うことと、space-ocr を使うことの違いのすべてだ。space-ocr は LLM を否定するものではない。内部では現在、OCR エンジン(Google Cloud Vision)と、構造化を担う Gemini を組み合わせている — これは実装の詳細であって、API の契約ではない。space-ocr が付け加えているのは、モデルの周りに組んだレイヤーだ。返す値は、OCR が実際にページ上で認識したものと照合され、判定が付き、シートにアップロードすればそのままクエリできる 1 行として保存される。検証できる値ごとの出所と、データベースを立てずにクエリできる構造化された出力。この 2 つこそ、素の LLM 呼び出しでは自分で用意しなければならない部分だ。
素の LLM OCR と space-ocr
| 素の LLM OCR(GPT-4o / Gemini / Claude) | space-ocr | |
|---|---|---|
| 値ごとの位置 | JSON 抽出の呼び出しで返るのはテキストで、別の OCR パスと突き合わせた出所座標は契約に含まれない | 位置が確定した値ごとにボックス(0–1000 グリッド上の xmin, ymin, xmax, ymax)と 4 点の傾き対応クアッド。確定しなかった値は review.flagged に nobox として載る |
| 値ごとの検証 | なし。文字列を信じるしかない | 判定は cells[path].verified、理由は review.reasons。照合そのものは evidence に入る — text_match、source、そして match_ratio(値の文字のうち、ページ上で検出されたシンボルの中に見つかった割合) |
| 宣言したルール | 検証は自分で書く | required・pattern・enum・min/max・near / not_near はリクエストと一緒に宣言するとサーバー側で判定され、違反は review.flagged に載る |
| 文脈での値の確認 | 自分で書類を読み直す | アプリでセルをクリックすると、元画像上でその領域が正確にハイライトされる |
| 出力の形 | プロンプト任せで、実行のたびに変わる JSON | 固定スキーマ。fields を一度定義するか autoFields に提案させれば、data.values はその形で返る |
| 保存とクエリ | 自分で作る | POST /ocr/fields はレスポンスで返すだけで画像を保存しない。シートにアップロードすれば 1 ページが 1 行になり、GET /view(where, sort, select, limit, offset)でクエリできる。OCR の再実行なし、課金なし |
| 文字種 | モデルとプロンプト次第 | 日本語・韓国語・中国語・英語ほかを自動判定。言語パラメータは不要 |
| セットアップ | プロンプト・リトライ・パース・検証のパイプラインを自作 | Bearer キー付きの HTTPS 呼び出し 1 回 |
「検証済み」が実際に何を意味するのかをはっきりさせておきたい。誇張しやすいところだからだ。言語モデルは座標を出力しない。返すのは各値のテキストと単語トークンのヒントだけで、そこからエンジンが、そのテキストを Google Cloud Vision がページ上で検出したシンボルと 1 文字ずつ照合する。実在するシンボルの上にボックスを合わせ、その照合の結果は cells[path].evidence に載る。文字照合そのものが text_match、値の文字のうち見つかった割合が match_ratio、ボックスをどう求めたかが source だ。verified はその上に立つ判定で、理由が 1 つでも付けば false、照合が走って何も立たなければ true、照合する相手が無ければ null になる。一致が弱ければ low_ratio のような理由が立ち、そのパスが review.flagged に並ぶ — 人の確認に回す一覧はこれだ。values はモデルが読んだ文字列であってバイト単位の複製ではないので、マスタと完全一致で突き合わせたいときは evidence.printed_text(その座標で OCR が読んだ文字)を使う。これはモデルが決して間違えないという約束ではない。トークンのヒントはずれることがあるし、二つのエンジンが同じ誤読で一致してしまうこともある。意味するのは、各値を鵜呑みにするのではなく、ページと照合して結果を知らせているということだ。
{
"status": "success",
"data": {
"values": {
"vendor": "ACME Trading Co."
},
"cells": {
"vendor": {
"box": { "xmin": 120, "ymin": 84, "xmax": 512, "ymax": 118 },
"quad": [
{ "x": 120, "y": 84 }, { "x": 512, "y": 84 },
{ "x": 512, "y": 118 }, { "x": 120, "y": 118 }
],
"verified": true,
"review": null,
"evidence": {
"text_match": true,
"source": "vision_symbol_match",
"match_ratio": 1.0,
"ocr_confidence": 0.97
}
}
},
"review": {
"unit": "field",
"declared": 1,
"returned": 1,
"boxed": 1,
"verified": 1,
"flagged": []
},
"image": { "width": 1654, "height": 2339 }
}
}ボックスは画像サイズに依存しない 0–1000 グリッド上で返るので、描画するときはピクセルにスケールする:pixel_x = xmin / 1000 * image_width。幅と高さは同じレスポンスの data.image から取る — これは実際に読み取ったページ(EXIF の回転を反映済み、大きな写真は縮小済み)であって、送ったファイルそのものではない。4 点のクアッドは傾いたり回転したりしたスキャンに追従し、左上・右上・右下・左下の順で並ぶ。何かが合わなければ、同じパスが data.review.flagged に { "path": "total", "reasons": ["text_mismatch"] } の形で載る。確認に回すのは、しきい値を決めて切るスコアではなく、そのまま辿れる一覧だ。汎用モデルへの JSON 抽出の呼び出しにこれらは付いてこないので、監査証跡が欲しければ自分で組み立てることになる。
値からクエリできるテーブルへ
素の LLM 呼び出しは JSON で終わる。それを自分で永続化しなければならず、「今四半期で 40,000 を超える請求書はどれか」と尋ねたくなった時点で、まずデータベースとクエリ層を作ることになる。POST /ocr/fields も JSON で終わる — レスポンスで返すだけで、画像は保存しない。違うのは、同じ抽出を保存レイヤーに通せることだ。列を決めてシートを作り(POST /create)、そこへページをアップロードすれば(POST /upload)、1 ページが同じ values・cells・review を持つ 1 行になる。あとはクエリが API 呼び出しで済む:GET /view に where=total>=40000、sort=-invoice_date、select=vendor,total、それにページングの limit と offset を渡す。処理はサーバー側で走り、OCR を再実行せず、課金もされない。シートは CSV にエクスポートできる(BOM 付き UTF-8 なので、日本語・韓国語・中国語のテキストや通貨も Excel で正しく開き、明細の配列はそれぞれ独立した行に展開される)。
素の LLM のほうが向いている場面
汎用 LLM が正しい選択になるのは、一度きりの読み取りや、ざっくりした要約、書類の意味を推論したいときだ。「この契約は何についてのものか」はモデル向きの問いであって、座標付き OCR の問いではない。space-ocr を持ち出すのは、書類を大量に処理していて、各値が検証でき、一貫して構造化され、クエリできる必要があるときだ。買掛金処理の自動化、経費の突き合わせ、名刺の CRM への取り込み、たまった領収書のデジタル化などがこれにあたる。正直に位置づけるなら、space-ocr は検証と保存のレイヤーを備えた LLM ベースの OCR であって、モデルそのものの競合ではない。
料金はスキャンごとの従量課金で、任意で月額プランも選べる。毎月一定数の無料スキャンが付き、失敗したスキャンには課金されない。現在の金額は料金ページに載せている。