◉CLAUDE LABEN
●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/API & SDK
⬡ API & SDK/2026-07-01上級

Claude API のドキュメント抽出が「自信満々に間違える」とき — 沈黙する誤りを不変条件で捕まえる運用メモ

請求書や契約書の構造化抽出で一番怖いのは、クラッシュではなく「スキーマも通り信頼度も高いのに値だけ間違っている」沈黙の誤りです。不変条件・二段抽出・フィールド単位の誤り率で運用する実装メモを共有します。

Claude API128ドキュメント処理3構造化抽出データ検証TypeScript24本番運用36コスト最適化26

✦ プレミアム記事

請求書を読ませるパイプラインを動かしていて、一番ヒヤリとした瞬間を先に書きます。バリデーションは通っていました。Zod のスキーマも通り、モデルが返した confidence は 0.95 でした。それでも total の桁が一つずれていたのです。クラッシュも例外も何も出ません。集計の段になって、ある取引先の月次合計だけが妙に小さいことに気づいて、ようやく遡って見つけました。

構造化抽出の本当の敵は、エラーで止まる失敗ではありません。止まる失敗はログに残り、リトライで拾えます。怖いのは、形式的にはすべて正しく、値だけが静かに間違っている抽出です。ここでは Claude API でドキュメント抽出を本番運用したときに効いた、「沈黙する誤り」を捕まえるための設計を、動くコードとともに共有します。

なぜ「スキーマ検証」では足りないのか

スキーマ検証が保証してくれるのは「形」だけです。total が数値であること、issueDate が文字列であることは確認できますが、その数値や文字列がドキュメントの内容と一致しているかは一切見ていません。

抽出が静かに間違う典型は、おおむね次の3つに収れんします。

ひとつめは桁とカンマの読み違いです。1,250,000 を 1250.00 と読む、¥ と小数点の混在で桁が動く、といったものです。ふたつめはフィールドの取り違えで、issueDate(発行日)の欄に dueDate(支払期限)を入れてしまう、subtotal と total を逆に拾うようなケースです。みっつめは「もっともらしい補完」で、ドキュメントに書かれていない税率を一般的な値で埋めてしまう類です。

どれも confidence は高く出ます。モデルが自己申告する信頼度は「自分の読み取りに対する自信」であって、「正解との一致」ではないからです。ここを混同すると、一番信頼度が高いフィールドが一番危ない、という事故が起きます。

不変条件 — ドキュメント自身に検算させる

外部の正解データがなくても、ドキュメントの中には検算に使える関係が必ず潜んでいます。請求書なら明細の合計が小計に一致し、小計と税の和が総額に一致します。日付なら発行日は支払期限以前です。これらを**不変条件(invariant)**として明文化し、抽出直後に機械的に検算します。

Zod の superRefine を使うと、型検証と不変条件の検証を一枚のスキーマにまとめられます。

// src/schema/invoice.ts
import { z } from "zod";
 
const Money = z.number().finite().nonnegative();
 
const LineItem = z.object({
  description: z.string().min(1),
  quantity: z.number().positive().optional(),
  unitPrice: Money.optional(),
  amount: Money,
});
 
// 許容誤差: 端数処理のずれを 1 通貨単位まで許す
const EPS = 1;
 
export const InvoiceSchema = z
  .object({
    invoiceNumber: z.string().optional(),
    issueDate: z.string().optional(),   // ISO 8601 を期待
    dueDate: z.string().optional(),
    currency: z.string().default("JPY"),
    lineItems: z.array(LineItem).min(1),
    subtotal: Money.optional(),
    tax: Money.optional(),
    total: Money,
  })
  .superRefine((inv, ctx) => {
    // 不変条件1: 明細の合計 ≒ 小計
    if (inv.subtotal !== undefined) {
      const sum = inv.lineItems.reduce((s, li) => s + li.amount, 0);
      if (Math.abs(sum - inv.subtotal) > EPS) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ["subtotal"],
          message: `lineItems合計(${sum})と小計(${inv.subtotal})が不一致`,
        });
      }
    }
 
    // 不変条件2: 小計 + 税 ≒ 総額
    if (inv.subtotal !== undefined && inv.tax !== undefined) {
      const expected = inv.subtotal + inv.tax;
      if (Math.abs(expected - inv.total) > EPS) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ["total"],
          message: `小計+税(${expected})と総額(${inv.total})が不一致`,
        });
      }
    }
 
    // 不変条件3: 発行日 <= 支払期限
    if (inv.issueDate && inv.dueDate) {
      if (new Date(inv.issueDate) > new Date(inv.dueDate)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ["dueDate"],
          message: "発行日が支払期限より後になっている",
        });
      }
    }
 
    // 不変条件4: 各明細の quantity × unitPrice ≒ amount
    inv.lineItems.forEach((li, i) => {
      if (li.quantity !== undefined && li.unitPrice !== undefined) {
        const expected = li.quantity * li.unitPrice;
        if (Math.abs(expected - li.amount) > EPS) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["lineItems", i, "amount"],
            message: `数量×単価(${expected})と金額(${li.amount})が不一致`,
          });
        }
      }
    });
  });
 
export type Invoice = z.infer<typeof InvoiceSchema>;

ポイントは、不変条件違反を例外で握り潰さずに、どのフィールドが (path) どう矛盾したかを構造化して残すことです。path を後段のルーティングや再抽出にそのまま使えるようにしておきます。safeParse で受け、error.issues を持ち回します。

// src/validate.ts
import { InvoiceSchema, type Invoice } from "./schema/invoice";
 
export type FieldFault = { path: string; message: string };
 
export function validateInvoice(raw: unknown): {
  ok: boolean;
  data?: Invoice;
  faults: FieldFault[];
} {
  const result = InvoiceSchema.safeParse(raw);
  if (result.success) return { ok: true, data: result.data, faults: [] };
 
  const faults: FieldFault[] = result.error.issues.map((iss) => ({
    path: iss.path.join("."),
    message: iss.message,
  }));
  return { ok: false, faults };
}

ここまでで、桁の読み違いやフィールドの取り違えの多くは「形式は正しいのに検算が合わない」として表面化します。total が一桁ずれていれば、小計+税との突き合わせでほぼ確実に落ちます。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦スキーマ検証を通過した抽出値が「静かに間違う」典型パターンと、ドキュメント自身に検算させる不変条件の書き方
✦安価なモデルで全体抽出し、不変条件に落ちたフィールドだけ上位モデルで再抽出する二段構成の実装とコスト勘所
✦document単位の成功率ではなくfield単位の誤り率で運用する、計測と人手レビューの振り分け基準
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

⬡ API & SDK2026-05-04
Bun × Claude API で本番運用するエッジAIサービス — Node.js移行の判断軸と落とし穴を回避する実装パターン
Claude APIをBun上で本番運用するための実装ガイド。Node.jsからの移行判断軸・組み込みSQLite/WebSocketの活用・ストリーミング最適化・典型的な落とし穴の回避策まで、動作するコードと実測値で解説します。
⬡ API & SDK2026-08-01
会話の途中でツールを差し替える — プロンプトキャッシュを守るツールレジストリの設計
ターン間でツールを追加・削除できるようになった一方、ツール定義はキャッシュプレフィックスの最前列にあります。12種類の変更を指紋で実測し、安定コアと揮発テールに分けるレジストリ設計とガード実装をまとめました。
⬡ API & SDK2026-05-24
Claude API のレスポンスを Cloudflare R2 にアーカイブする実装メモ — 監査・再現性・障害分析の3軸設計
Claude API のリクエストとレスポンスを Cloudflare R2 に保存して、監査・障害分析・本番リプレイの3つに使う設計を、Workers の waitUntil パターンと D1 メタデータインデックス込みで実装メモにまとめました。