Invoices, contracts, support transcripts, inspection reports — most organizations sit on document piles that never reach the warehouse because nobody wants to hand-key them. LLM extraction with structured outputs is the new "ELT for unstructured": documents in, typed rows out.
Tier: Core · Workflow area: Unstructured data processing
The difference between a demo and a production pipeline is everything around the model call — the schema contract, the validation gate, and the sampled human QA that tells you the field-level accuracy you're actually shipping.
Don't use this for one-off extractions of ten documents (do it by hand or in a chat session) or for documents where layout is the data (complex nested tables — see the PDF/table extraction playbook).
Write the schema as a Pydantic contract, including allowed values. Every enum you can close, close:
from pydantic import BaseModel
from typing import Literal, Optional
class InvoiceRecord(BaseModel):
vendor_name: str
invoice_number: str
invoice_date: str # ISO 8601
currency: Literal["INR", "USD", "EUR", "GBP"]
subtotal: float
tax: float
total: float
payment_terms: Optional[str]
confidence: float # model's own 0-1 estimate
Hand-label a 30-document golden sample first. Before any pipeline code, extract 30 docs manually into the same schema. This is your accuracy yardstick and your prompt-iteration test set.
Extract with structured outputs, looping over the corpus. Batch processing is a loop over the API with retries and checkpointing — persist every result as you go so a crash doesn't restart the corpus:
import anthropic
client = anthropic.Anthropic()
for doc in corpus: # checkpoint: skip docs already extracted
response = client.messages.parse(
model="claude-opus-4-8",
max_tokens=2048,
thinking={"type": "adaptive"},
messages=[{
"role": "user",
"content": (
"Extract the invoice fields from this document. "
"Use null for fields not present; never guess amounts.\n\n"
f"{doc.text}"
),
}],
output_format=InvoiceRecord,
)
save_raw_result(doc.id, response.parsed_output)
Expected output per document: a validated InvoiceRecord instance in response.parsed_output — the SDK raises if the model's output doesn't match the schema, which is itself a signal worth logging.
Run the validation gate before anything lands in the warehouse. Four layers, in order:
parse(); log and route any schema failures to review rather than retrying blindly.subtotal + tax == total within a tolerance; invoice_date not in the future; amounts non-negative.staging.invoice_extractions.def gate(rec: InvoiceRecord) -> str:
if rec.currency not in {"INR", "USD", "EUR", "GBP"}: return "review"
if abs(rec.subtotal + rec.tax - rec.total) > 0.01: return "review"
if rec.confidence < 0.85: return "review"
return "accept"
Measure field-level accuracy on the golden sample. Run the pipeline over your 30 labeled docs and score per field: vendor_name 100%, total 97%, payment_terms 83%. Publish this table — it tells consumers which columns to trust and tells you where to iterate on the prompt.
Land accepted rows with lineage columns. Every warehouse row carries source_doc_id, extraction_model, prompt_version, extracted_at, and gate_status. When the prompt changes, you can re-extract selectively and diff.
Treat every extracted value as untrusted input until it clears the gate. The most dangerous failure mode is not a wrong value — it is a plausible wrong value that passes schema validation, which is why the business-rule checks (totals reconcile, dates sane) exist.
Client documents are usually the most sensitive artifacts on an engagement — invoices name vendors and amounts, contracts name terms. Before any extraction, confirm the client's AI policy in writing: which provider, which region, whether a zero-retention agreement is in place. Prefer running inside the client's own cloud account (their API keys, their warehouse) so documents and extractions never touch your infrastructure. If no external LLM is approved, the identical pipeline — schema contract, gate, golden sample, review queue — runs against a self-hosted model (vLLM on client GPUs) or the client warehouse's native AI functions; expect lower accuracy and budget a larger review queue. Redact or hash direct identifiers in anything you take off-site, including the accuracy report.