Extraction turns a document into data your code can check and store, so start by deciding exactly what that data looks like: a schema for each document type. A credit agreement summary might have the borrower, the lenders, the commitment amount, closing and maturity dates, the interest benchmark and margin, the fees and the financial covenants.
Give every field a type and make units explicit. Dates as ISO dates (2030-03-31), amounts as numbers with a separate currency field, percentages in one agreed form. "4.25%" could be stored as 4.25, 0.0425 or 425 basis points; pick one, put it in the field name (margin_bps) or its description, and stick to it. Use a fixed list of values (an enum) where the answers are known: lien position is first lien, second lien or unsecured, not whatever wording the document used.
Repeated things are arrays: the lenders, the tenants in a rent roll, an invoice's lines. A short description on each field ("the margin over the benchmark for SOFR loans, not the default rate") goes into the prompt and helps the model as much as it helps the next engineer. Most model providers can hold the output to a JSON schema; check your model's docs for how.
A missing value is null, never 0 or an empty string. A rent of 0 means a vacant unit; a rent of null means the extraction couldn't find it. Mixing them up turns a reading problem into a wrong number.
Then validate, because financial documents carry their own checks. Totals should reconcile (lines add up to the total, assets equal liabilities plus equity); dates should be in order (maturity after closing) and in range; percentages should be plausible; a value that appears twice should agree.
deal = {"closing_date": "2025-03-31", "maturity_date": "2024-03-31", "commitment": 12_500_000, "margin_bps": 425}
problems = []
if deal["maturity_date"] <= deal["closing_date"]:
problems.append("maturity before closing")
if not 0 < deal["margin_bps"] < 2000:
problems.append("margin out of range")
print(problems)
Prints
['maturity before closing']
ISO dates compare correctly as plain text, which is one more reason to use them. When a document disagrees with itself (the summary page says $12.5 million, section 2.01 says $12.0 million) don't let the model pick one. Keep both values with their sources and send the field to a person: an amendment, a typo or a misread are all possible, and only someone reading the document can tell.
Practice
The model can't find the prepayment fee in an agreement. What should the field hold?
More info
Quick reference
Schema
The fields, types and units for one document type.
Units
Fixed by the schema: ISO dates, numbers plus currency, one form for rates.
Enum
A fixed list of allowed values.
null
Not found. Different from 0.
Reconcile
Parts add up to totals; assets = liabilities + equity.
Conflict
Two sources disagree: keep both, send to a person.