The pattern
A Pydantic schema and a large language model can take any unstructured document (a scanned PDF, a photographed receipt, free-form text) and turn it into a typed, validated record in seconds. No OCR step, no per-vendor templates, and a data-entry clerk you don’t need to hire. The example I’ll walk through is a property management vendor invoice, but the same pattern works for any document shape you can describe.
The approach
Defining the data contract
The Pydantic model tells the LLM how to parse the invoice. Required fields are required. Categorical fields are constrained to the firm’s actual GL category list. Capex flag is explicit.

Let the model fill the contract, not hallucinate one
Instructor (with Pydantic) gives the model the schema as a tool definition. The model has to fit its output to that schema. Any value that doesn’t match gets rejected, or (optionally) lands in an additional notes section.
Failures are handled, not hidden
When the model returns values that don’t match our predefined schema — wrong type, missing field, invalid GL category — Instructor catches the validation error and runs again, up to a configurable number of tries. Once retries are exhausted the failed document can be routed to quarantine, the model won’t force it through or pass nulls if not allowed in our Pydantic schema.
Demo: scanned PDF → typed Python
Below is a synthetic invoice, generated for the demo and processed with the language-model pipeline I described above.
Input — a rasterized PDF with zero selectable text. This is the format real trade vendor invoices arrive in: scanned, faxed, or photographed. A traditional OCR-and-regex pipeline is much more brittle: one set of hardcoded rules per vendor, rewritten every time their template changes.

Output — same PropertyInvoice schema as the typed-text demos. Property address, unit, dates, line items, totals — all in the right fields, all type-validated. From this point, the record is ready to be pushed to an accounting platform or database.

The classification and mapping work because the schema and prompt are both teaching the model: the Literal type tells the model what’s allowed and the field description describes what the value means. The same pattern works for any document type — define the schema you want, hand a PDF or text to the model, and what comes back is a typed Python object the rest of your code can use directly.
Reference architecture
The parser from the demo above sits inside a larger event-driven pipeline at enterprise scale. Component names below are vendor-neutral, with AWS service names in parens — Pub/Sub + Cloud Functions + Cloud SQL on GCP, or Service Bus + Functions + Azure SQL on Azure, are direct analogs.
flowchart LR
subgraph Intake["Document intake"]
A1[Email gateway]
A2[SFTP]
A3[Portal / API]
end
S3[("Object storage
S3 / GCS")]
Q[Queue
SQS / Pub/Sub]
L["Serverless function
Pydantic + Instructor"]
LLM[Claude API
Anthropic / Bedrock]
DB[("OLTP datastore
Postgres / RDS")]
DLQ[Dead-letter queue]
HR[Human review UI]
WH[("Data warehouse
optional")]
O[Observability
OTel / Datadog]
A1 --> S3
A2 --> S3
A3 --> S3
S3 -->|object-created event| Q
Q --> L
L <-->|inference| LLM
L -->|validated record| DB
L -->|retries exhausted| DLQ
DLQ --> HR
HR -->|corrected record| DB
DB -.->|sync / CDC| WH
L -.->|logs, metrics, traces| O
The path a document takes:
- Documents land in object storage. Whether they arrive via email gateway, SFTP, or portal upload, the first stop is blob storage (S3 / GCS / Azure Blob).
- Object-created events fan into a queue. The queue (SQS / Pub/Sub / Service Bus) decouples ingestion from processing — stopping bursty input from overwhelming the parser. A dead-letter queue catches anything that fails after a user-defined number of attempts.
- A serverless function runs the parser. This is where the code from the demo above actually lives. It fetches the PDF, calls Claude, runs the Pydantic + Instructor validation loop, and produces either a validated record or a structured failure.
- Records land in your database. Failures land in a queue. Validated records flow to the OLTP datastore (Postgres / RDS / Cloud SQL) — the structured store your downstream systems pull from. Failures route to a human-review queue where someone resolves the document by hand and reposts.
Watch the pipeline. Per-document inference time, cost, retry count, and validation-failure rate by field go into your monitoring stack. The metric worth watching closely is failure rate by field. A sudden spike usually means a real-world case your schema didn’t anticipate — a vendor billing in two currencies, an attached credit memo, a new fee line. The fix is usually just adding a field to the schema with a few lines of Pydantic, not a major overhaul to the parser.
The alternatives
The hand-rolled pattern isn’t always the right answer. There are three categories worth knowing about: traditional, platform-native, and off-the-shelf.
Where OCR still wins
Honest limits to keep us grounded in where AI is at in 2026:
- Cost at extreme volume. Templates have near-zero marginal cost once they’re built. LLM calls cost real money per document, and API pricing isn’t fixed in stone.
- Standardized, high-volume formats. When you’re processing millions of copies of a single fixed layout (same fields, same positions, same vendor), a template-based OCR pipeline gives bit-perfect reproducibility and predictable cost. LLMs shine on variety, OCR on uniformity.
- Data residency. Cloud LLMs send your documents to a third party. Not acceptable for PHI or PII (unless you use an on-prem model or a compliant deployment like Bedrock with a BAA). OCR running locally keeps everything on your hardware.
Where managed services win
Databricks recently shipped ai_parse_document — a SQL function that parses PDFs inside the platform, with Unity Catalog governance and Spark-scaled processing built in. Snowflake has the direct equivalent: AI_PARSE_DOCUMENT in Cortex — same function name, same SQL-inside-the-platform approach. The buy decision becomes obvious in a few situations:
- You’re already on the platform. Pipeline, governance, observability, and billing all land in one place — no extra infrastructure to maintain.
- Millions of documents per month. Built-in Spark scale, retry logic, and checkpointing beat anything you’d hand-roll at that volume.
- Tables, figures, RAG/search rather than typed records. When the downstream isn’t a single fixed schema (you’re feeding documents into vector search, summarization, or BI dashboards), managed services give you the whole document in one structured blob. Schema-driven extraction throws everything outside your schema away.
- Governance comes with the platform. Unity Catalog (Databricks) and Horizon Catalog (Snowflake) handle permissions and lineage natively — no extra tooling to string together.
Where the off-the-shelf SaaS wins

Rossum (recently acquired by Coupa) is the representative example: an AI-first platform for transactional documents (invoices, POs, bills of lading, and similar), built for finance/AP teams to operate end-to-end without engineering involvement. It reads documents, validates data, hands off to a human for edge cases, and writes back to the ERP — out of the box. This category wins when:
- AP/finance owns the workflow, not engineering. Some teams prefer a product they can operate and tune themselves, rather than something that requires a developer to update.
- ERP integration is the bottleneck. Pre-built connectors for SAP, NetSuite, Dynamics, etc. — a custom solution you’d otherwise need to build and own.
The bottom line
Build it yourself when the schema is yours to own, you’re not already living inside a platform or paying for a SaaS, your volume is in the thousands a month rather than the millions, and you want to swap LLMs whenever a better one ships.