CLAUDE LABEN
SCIENCE — Claude Scienceがベータ公開され、研究ツールを統合し監査可能な成果物を生成するワークベンチが使えますMODEL — Claude Opus 4.7のfast modeが7月24日に廃止され、Opus 4.8のfast modeへの移行が必要になりますCODE — Claude Code v2.1.195でフルスクリーン時のマウス操作を無効化できるトグルが追加されましたCODE — ハイフン入りのhook matcherが部分一致ではなく厳密一致になるよう修正されましたAGENT — Claude Scienceは調整役エージェントが専門エージェントを呼び出し、引用や計算をレビューする多段構成ですCLOUD — ClaudeがMicrosoft Foundry(Azure)で一般提供され、Azureネイティブで利用できますSCIENCE — Claude Scienceがベータ公開され、研究ツールを統合し監査可能な成果物を生成するワークベンチが使えますMODEL — Claude Opus 4.7のfast modeが7月24日に廃止され、Opus 4.8のfast modeへの移行が必要になりますCODE — Claude Code v2.1.195でフルスクリーン時のマウス操作を無効化できるトグルが追加されましたCODE — ハイフン入りのhook matcherが部分一致ではなく厳密一致になるよう修正されましたAGENT — Claude Scienceは調整役エージェントが専門エージェントを呼び出し、引用や計算をレビューする多段構成ですCLOUD — ClaudeがMicrosoft Foundry(Azure)で一般提供され、Azureネイティブで利用できます
記事一覧/API & SDK
API & SDK/2026-07-01上級

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

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

Claude API97ドキュメント処理3構造化抽出2データ検証TypeScript19本番運用33コスト最適化22

プレミアム記事

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

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

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

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

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

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

どれも 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-06-17
Claude API のドキュメント抽出が「確信を持って間違える」とき — 検証層の設計メモ
Claude API で請求書や契約書を構造化抽出するとき、最も怖いのは例外ではなく「もっともらしく間違った JSON」です。スキーマ検証・算術整合・二重抽出の一致率で誤抽出を本番前に捕まえる検証層を、TypeScript の実装とともにまとめました。
API & SDK2026-06-21
service_tier で優先枠をユーザー応答だけに振り向ける
Priority Tier を契約しているのにピーク時だけユーザー向け応答が遅くなる——その原因を service_tier の auto と standard_only の挙動から切り分け、背景ジョブを優先枠から隔離する構成を、自動運用の実例とともにまとめました。
API & SDK2026-06-20
Cloudflare AI Gateway を Claude の手前に置くと、見えるはずの数字が見えなくなる — 運用で効いた計装メモ
Cloudflare AI Gateway を Claude API の前段に置いたあと、コスト按分・セマンティックキャッシュの誤ヒット・フォールバックの静かな品質低下・予算の実効化で実際につまずいた箇所と、その手当てをコード付きでまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →