Invoice Extraction Service
An asynchronous FastAPI microservice that turns messy, unstructured documents into a guaranteed, schema-validated invoice record. Resilience comes from routing across models, not vendors: a free primary model cascades to a second provider on rate limits, timeouts, or schema exceptions, at zero marginal cost.
Problem
Accounts-payable teams receive invoices as OCR dumps, forwarded email threads, till receipts, and foreign-language PDFs. A raw LLM call returns prose, fenced code blocks, or a JSON object with the wrong shape, and a single provider outage stops the whole intake queue.
Architecture
Requests are validated by Pydantic v2 before any model is called. The dispatcher walks an ordered model chain with strict json_schema constrained decoding at temperature 0. Each attempt runs under a hard timeout and translates SDK exceptions into one error taxonomy. Raw output is stripped of fences and reasoning preambles, parsed, then normalised by validators that recompute line totals to the cent and map currency symbols to ISO-4217. The service also looks after its own chain: when a free model is withdrawn, it re-tests the free catalogue with seven sample documents and fills the gap using a published ordering rule.
- Python 3.12
- FastAPI
- Pydantic v2
- OpenRouter
- httpx
- Pytest
- GitHub Actions
- Render
Metrics
21 / 21
fully correct, primary model, dated benchmark
measured10
provider failure classes covered offline
measured0
network calls permitted in the test suite
measured$0.00
marginal cost per request on the default chain
measured
Proof points
- Ten failure classes exercised offline in the cascade tests: 429, 503, 404, timeout, unexpected exception, invalid JSON, schema mismatch, empty response, in-band upstream error, and truncated output.
- The test suite cannot reach the network by construction. Every outbound path is closed in conftest, and a test that reaches for one fails immediately.
- Dated benchmark snapshot (7 October 2026): 3 passes of 7 documents per model, every call saved as JSONL so the figures can be re-derived without calling a model.
- Every figure on the project page is checked against the recordings by a test. A sentence that is not derived from the data fails the build.
- Logging hygiene: the model and timing are logged, never the vendor name or invoice number above DEBUG.
Request pipeline
- 01
Ingest
ExtractRequest, 10 to 50k chars, 1 MB body ceiling refused before read
- 02
Harden
Prompt injection defanging, request-id middleware, call budget with Retry-After
- 03
Primary model
Strict json_schema, temperature 0, hard timeout
- 04branch
Fallback
Different provider, triggered only on a failed call
- 05
Recover
Strip fences, drop reasoning preamble, decode JSON
- 06
Validate
InvoiceData.model_validate, recalc totals, ISO-4217


























