Claude Media
契約書PDFから収益認識の履行義務をClaude APIで抜き出す手順

契約書PDFから収益認識の履行義務をClaude APIで抜き出す手順

契約書PDFをClaude APIに渡し、IFRS 15の5ステップに沿って履行義務・取引価格・履行時点の候補をJSONで返させる実装手順。スキーマ設計と注意点を示します。

履行義務の抽出はIFRS 15の5ステップのどこまでAPIに任せられるか

契約書PDFを読んで「何を約束した契約か」「いくらか」「いつ収益になりそうか」の候補を表にする作業は、PDFの読み取りと構造化が中心です。Claude APIのPDF対応とstructured outputs(スキーマに沿ったJSON出力)を組み合わせれば、この下準備を機械化できます。会計処理としての最終判断は、経理担当者と公認会計士が行う前提です。

収益認識の枠組みとして、IFRS 15は次の5ステップを定めています。

  1. 顧客との契約を識別する
  2. 契約に含まれる履行義務を識別する(履行義務は、別個の財またはサービスを顧客に移転する約束)
  3. 取引価格を算定する(変動対価があれば見積りが要る)
  4. 取引価格を各履行義務へ、独立販売価格の比率で配分する
  5. 履行義務を充足したときに収益を認識する(一時点か、期間にわたってか)

契約書のテキストだけから拾える材料は、ステップごとに差があります。

ステップ契約書から拾える材料APIでの扱い
1契約の識別契約書から拾える材料当事者、契約期間、変更契約との関係APIでの扱い抽出できる。変更契約の結合判断は人が行う
2履行義務の識別契約書から拾える材料約束された成果物・サービスの列挙APIでの扱い候補の列挙まで。別個かどうかは理由付きで出させ、人が判定する
3取引価格契約書から拾える材料固定報酬、従量・成果報酬などの変動条件APIでの扱い条項の抜き出しまで。変動対価の見積りは対象外
4配分契約書から拾える材料独立販売価格は契約書にほぼ書かれないAPIでの扱い契約書だけでは足りない。別資料と人の判断
5充足時点契約書から拾える材料検収条項、提供期間、継続提供の記述APIでの扱い「一時点/期間」の候補と根拠条項の提示まで

本記事の範囲は、ステップ1〜3と5の「候補」を、根拠の条項番号と原文付きで取り出すところまでです。ステップ4は契約書の外にある情報が要るので、出力の open_questions に残して人に渡します。

契約書PDFの渡し方は3通り — 機密文書なら既定はbase64

PDFをAPIへ渡す方法は3つあります。

  • ホスト済みPDFのURLを指定する
  • base64でエンコードして document ブロックに入れる
  • Files APIの file_id で参照する

契約書は機密性が高いので、方式の違いは取り扱い条件に直結します。2点を押さえておきます。

1つ目はデータ保持です。PDF対応はZDR(Zero Data Retention)の対象です。一方、Files APIはZDRの対象外です。ZDRを前提にした契約で動く環境なら、Files APIにアップロードする方式は選べません。base64でリクエストに直接入れる方式なら、PDF対応側の条件で済みます。

2つ目はアクセス範囲です。Files APIにアップロードしたファイルは、ユーザーや会話ではなくワークスペース単位でアクセスできます。同じワークスペースのAPIキーなら、誰のファイルでも参照できます。顧客ごとに契約書を扱うシステムでは、file_id を利用者の入力から受け取らず、サーバー側で管理する設計が要ります。Files APIの使い方そのものはClaude Files APIでPDFを処理する方法で扱っています。

base64方式のリクエストサイズは、PDF対応の要件表で32MBまでです。ページ数の上限は600ページで、リクエストのコンテキストウィンドウが1Mトークン未満なら100ページに下がります。サイズとページ数はどちらも、PDF以外の内容を含むリクエスト全体に対する上限です。基本契約書に別紙を束ねた長大なPDFは、この上限に当たりやすい文書です。400エラーの切り分けは「Could not process PDF」400エラーの原因と対処法にまとめています。

抽出スキーマは5ステップの順に組む

structured outputsは、output_config.format にJSON Schemaを渡すと、そのスキーマに沿ったJSONを返す機能です。Pydanticのモデルを client.messages.parse() の output_format に渡すと、SDKがスキーマへの変換と応答の検証、パース済みオブジェクトの返却までを引き受けます。

スキーマの設計で効くのは、複雑さの制限です。required に入れないパラメータ(任意項目)は、1リクエストで合計24個までです。anyOf や ["string", "null"] のようなユニオン型は16個までに制限されています。任意項目は文法のコンパイルを重くするため、できるだけ required にするのが対処法です。そこで、契約書に記載がない項目はnullではなく「記載なし」の文字列で返させ、全項目を必須にします。

また、minimum や maxLength のような数値・文字列の制約は使えません。金額は数値型にせず、通貨記号付きの文字列として原文のまま返させます。換算や合計は、返ってきた後にコード側で行う前提です。

from typing import Literal
from pydantic import BaseModel
 
 
class Obligation(BaseModel):
    description: str        # 約束された財・サービス
    clause_ref: str         # 根拠の条項番号(例: 第3条2項)
    distinct_reason: str    # 別個の履行義務と考える理由、または考えにくい理由
    timing: Literal["over_time", "point_in_time", "unclear"]
    timing_basis: str       # 時点の候補の根拠(検収条項・提供期間など)
    evidence_quote: str     # 根拠となる契約書の原文(逐語)
 
 
class ContractExtract(BaseModel):
    parties: str            # 当事者
    contract_term: str      # 契約期間
    obligations: list[Obligation]
    fixed_price: str        # 固定対価(原文の表記のまま)
    variable_consideration: str  # 成果報酬・従量・返金条件など
    open_questions: list[str]    # 契約書だけでは決められない論点

全フィールドが必須です。timing を unclear も含む列挙にしているのは、判断材料が足りない契約で無理に二択へ寄せさせないためです。

実装 — 契約書1通につき1リクエスト

次のコードは、base64でPDFを渡し、messages.parse() で上のモデルに直接パースさせる形です。PDF送信の例とstructured outputsの例を組み合わせた構成で、モデル名は例に合わせています。

import base64
import anthropic
 
client = anthropic.Anthropic()
 
with open("contract.pdf", "rb") as f:
    pdf_data = base64.standard_b64encode(f.read()).decode("utf-8")
 
PROMPT = """この契約書から、収益認識の検討材料を抽出してください。
- obligations: 契約が約束している財・サービスを1件ずつ列挙する
- evidence_quote は契約書の原文を一字一句そのまま写す。要約しない
- 契約書に記載がない項目は「記載なし」と書く。推測で埋めない
- 契約書だけでは決められない論点は open_questions に入れる
会計上の最終判断はしないでください。"""
 
response = client.messages.parse(
    model="claude-opus-5-5",
    max_tokens=4096,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {
                        "type": "base64",
                        "media_type": "application/pdf",
                        "data": pdf_data,
                    },
                },
                {"type": "text", "text": PROMPT},
            ],
        }
    ],
    output_format=ContractExtract,
)
 
if response.stop_reason in ("refusal", "max_tokens"):
    raise RuntimeError(f"出力が不完全です: {response.stop_reason}")
 
result = response.parsed_output
for ob in result.obligations:
    print(ob.clause_ref, ob.timing, ob.description)

PDFをテキストより前に置いているのは、「パフォーマンス改善」の指針に従ったものです。読みやすいフォント、正しい向きのページ、PDFビューアーの論理ページ番号をプロンプトで使うことも、精度に効きます。「32ページ目の別紙2」のように指示すると、PDF内の印字ページ番号とのずれを避けられます。

実行はAPIキーを環境変数に置いた上で、通常のPythonスクリプトとして回せます。

pip install anthropic pydantic
export ANTHROPIC_API_KEY="..."
python extract_obligations.py

stop_reason の確認は、structured outputsの注意点に対応しています。安全上の理由でClaudeが拒否した場合は、stop_reason が refusal で、ステータスは200のまま返り、スキーマに合わないことがあります。出力が max_tokens で途切れた場合も同様です。履行義務が多い契約では、max_tokens を余裕を持って設定します。

返ってきた候補を信用する前に確かめること

evidence_quoteを原文と機械的に突き合わせる

evidence_quote に原文を逐語で書かせたのは、幻覚の検出を人の目だけに頼らないためです。PDFから抽出した全文テキストを手元に持ち、返ってきた引用がその中に部分一致するかをコードで検査します。一致しない引用を含む履行義務は、確認待ちの列に回します。この検査は、スキャン画像のPDFでは使えません。テキスト層がないため、突き合わせる全文が手元に作れないからです。

Citationsとの併用はできない

引用の位置情報が欲しくなってCitationsを有効にすると、structured outputsと併用できず400エラーになります。引用ブロックと本文が交互に出る形式が、JSONスキーマの制約とぶつかるためです。この条件はCitationsとstructured outputsが併用できない理由と400エラー条件で詳しく扱っています。ここで述べた逐語引用フィールドは、その代わりの工夫です。ページ番号までは保証しないので、clause_ref の条項番号と原文で人が該当箇所を探す運用になります。

スキーマに契約の中身を書かない

structured outputsのスキーマは、最後の利用から最大24時間キャッシュされます。PHI(保護対象の医療情報)をプロパティ名・enum値・const・pattern に含めないことが、データ保持の条件です。契約の相手先名や案件名も同様に、スキーマ側へ書かず、プロンプト側に置くのが安全です。上のモデルは項目の枠だけを定義しているので、この点は満たしています。

判断を求めない

プロンプトの最後に「会計上の最終判断はしないでください」と入れているのは、履行義務が別個かどうかの判定、変動対価の見積り、契約変更の扱いが、いずれも専門家の判断領域だからです。distinct_reason を理由付きで出させると、判定に至る材料が見えるので、会計士のレビューが速くなります。

複数の契約書をまとめて処理するときのコストと設計

1通ずつ別リクエストにする

PDF1ページあたりのトークン数は、テキスト分だけで1,500〜3,000トークンが目安です。ページは画像としても処理されるため、画像のトークン計算も別途かかります。たとえば40ページの契約書は、テキスト分だけでも60,000〜120,000トークン(40ページ×1,500〜3,000)です。複数の契約書を1リクエストにまとめるとページ数の上限を共有してしまうので、1通ずつ別リクエストにするほうが、失敗の切り分けも簡単です。実際の消費量は、token counting(トークン数の事前計測)で契約書ごとに見積もれます。

Message Batches APIで半額にする

契約書が数十〜数百通ある棚卸しの場面では、結果をすぐ返す必要がありません。Message Batches APIは、全利用分が通常料金の50%になります。バッチは大半が1時間以内に終わり、24時間以内に完了しなければ期限切れになります。1バッチの上限は10万リクエストまたは256MBの早いほうです。structured outputsもバッチと併用でき、50%割引の対象です。実装はClaudeのMessage Batches SDKをPython・TypeScript・Ruby・Javaで実装するの手順に、上のスキーマを載せる形です。

バッチでは、拒否された応答が成功扱いで返る点に注意してください。stop_reason の確認はバッチ側でも必要です。詳細はBatch APIの拒否は成功扱いになる落とし穴にあります。

同じ契約書に何度も質問する運用では、PDFのブロックに cache_control を付けるプロンプトキャッシュが使えます。履行義務の抽出に続けて、解約条項や返金条件など別の観点を同じPDFへ問い直す場合に使えます。

会計基準側の論点の地図を作りたいとき

IFRS 15とJ-GAAPで差が出る論点の整理は、PDFの抽出とは別の作業です。基準そのものの差分を調べる手順はClaudeでJ-GAAPとIFRS差分チェックを行うプロンプト設計で扱っています。契約書の条項を法務の観点でレビューしたい場合は、Claudeに契約書を読ませてわかりにくい条項を確認する方法が出発点になります。

まとめ

履行義務の抽出で価値が出るのは、判断そのものではなく、「契約書のどこにその約束が書かれているか」を原文付きで一覧にする部分です。スキーマを5ステップに合わせ、根拠の逐語引用を必須にすれば、会計士のレビューは「探す」作業から「判断する」作業に寄ります。機密性の高い契約書では、まずZDRとワークスペースの条件からPDFの渡し方を決め、その後にスキーマとバッチの設計へ進む順番が安全です。

この記事を共有:XはてブLinkedIn