LlamaIndex reader for IRS tax form extraction via Azure Document Intelligence — supports Form 1040, W-2, Schedule C/E/K-1, 1065, 1120, 1120-S
Project description
llama-index-readers-azure-tax-forms
A LlamaIndex reader that extracts structured key-value pairs from IRS tax form PDFs using Azure Document Intelligence.
Built and production-tested at Callisto Tech as part of a financial aid advising platform processing real tax documents at scale.
Supported Forms
| Form | Description |
|---|---|
| Form 1040 | Individual income tax return |
| W-2 | Wage and tax statement |
| Schedule C | Profit or loss from business |
| Schedule E | Supplemental income and loss |
| Schedule K-1 | Partner's / shareholder's share of income |
| Form 1065 | U.S. return of partnership income |
| Form 1120 / 1120-S | Corporate income tax return |
Installation
pip install llama-index-readers-azure-tax-forms
Quick Start
from llama_index_readers_azure_tax_forms import AzureTaxFormReader
reader = AzureTaxFormReader(
endpoint="https://my-resource.cognitiveservices.azure.com/",
api_key="YOUR_AZURE_DI_KEY",
max_concurrent=12,
)
# Single file
docs = reader.load_data("path/to/1040.pdf")
# Multiple files — processed concurrently, gate limits Azure DI calls
docs = reader.load_data(["1040.pdf", "w2.pdf", "schedule_c.pdf"])
# From raw bytes (S3, blob storage, database, etc.)
docs = reader.load_data_from_bytes([
("1040.pdf", open("1040.pdf", "rb").read()),
("w2.pdf", open("w2.pdf", "rb").read()),
])
Real Extraction Example
The following output was produced by running this reader against official IRS Form 1040 and W-2 templates filled with fictional test data.
Input — Form 1040 (filled with fake data)
docs = reader.load_data("samples/f1040_filled.pdf")
doc = docs[0]
print(doc.text)
print(doc.metadata)
Output — doc.text (key | value per line)
Your first name and middle initial | James
Last name | Harrington
Your social security number | XXX-XX-1234
Home address | 742 Evergreen Terrace
City, town, or post office | Springfield
State | IL
ZIP code | 62701
Wages, salaries, tips, etc. | 82000
Ordinary dividends | 1200
Total income | 83200
Adjusted gross income | 83200
Standard deduction | 13850
Taxable income | 69350
Tax | 11500
Total tax | 11500
Federal income tax withheld from Form(s) W-2 | 13200
Total payments | 13200
Amount of line 33 you want refunded to you | 1700
Output — doc.metadata
{
"document_id": "samples/f1040_filled.pdf",
"form_type": "1040",
"kv_count": 19,
"stage": "STAGE-0",
"di_calls": 1,
"az_di_ms": 1843,
"total_ms": 1921,
"error": null
}
Input — W-2 (filled with fake data)
Employee's social security number | XXX-XX-1234
Employer identification number (EIN) | 12-3456789
Employer's name, address, and ZIP code | Acme Corporation
Employee's first name and initial | James
Employee's last name | Harrington
Wages, tips, other compensation | 82000
Federal income tax withheld | 13200
Social security wages | 82000
Social security tax withheld | 5084
Medicare wages and tips | 82000
Medicare tax withheld | 1189
Use in a LlamaIndex RAG Pipeline
from llama_index_readers_azure_tax_forms import AzureTaxFormReader
from llama_index.core import VectorStoreIndex
reader = AzureTaxFormReader(
endpoint="https://my-resource.cognitiveservices.azure.com/",
api_key="YOUR_AZURE_DI_KEY",
)
# Load and index tax documents
docs = reader.load_data(["1040.pdf", "w2.pdf", "schedule_c.pdf"])
index = VectorStoreIndex.from_documents(docs)
# Query across all forms
query_engine = index.as_query_engine()
response = query_engine.query("What is the adjusted gross income?")
print(response)
# → "The adjusted gross income reported on Form 1040 is $83,200."
response = query_engine.query("How much federal tax was withheld?")
print(response)
# → "Federal income tax withheld as shown on the W-2 is $13,200."
Key Features
Concurrency Gate
A shared asyncio.Semaphore limits concurrent Azure DI calls so parallel
extractions never trigger 429 rate-limit responses.
Documents submitted Azure DI calls in flight
doc-1 ──┐ ┌── slot 1
doc-2 ──┤ Semaphore(12) ├── slot 2
doc-3 ──┤ ───────────── ├── slot 3
... │ max 12 at once │ ...
doc-20 ─┘ └── queued until slot free
Tune max_concurrent to your tier:
- F0 free tier →
max_concurrent=1 - S0 paid tier →
max_concurrent=12(safe empirically)
4-Stage Recovery Chain
Every document goes through a recovery chain before accepting an empty result:
| Stage | What it does | When triggered |
|---|---|---|
| Stage 0 | Direct Azure DI call on original bytes | Always first |
| Stage 1 | Split into page chunks, analyse in parallel | Stage 0 empty or oversize |
| Stage 2 | Re-render at 300 DPI (rasterise) | Stage 1 empty |
| Stage 3 | Rotation block: as-is → 90° → 180° → 270° | After Stage 2 |
429 Retry Back-off
Exponential back-off with ±20% jitter on Azure DI rate limit responses.
Honors Retry-After header when present.
attempt 1 → wait 1s ± 200ms
attempt 2 → wait 2s ± 400ms
attempt 3 → wait 4s ± 800ms
attempt 4 → wait 8s ± 1.6s
attempt 5 → wait 16s ± 3.2s (or propagate)
Field Normalisation
Corrects known Azure DI output quirks automatically:
| Raw key from Azure DI | Normalised |
|---|---|
"Wages/Salary/Tips - HHA " |
"Wages/Salary/Tips - HHA" (trailing space) |
"SeconD Read" |
"Second Read" (typo) |
"Based Year> Tuition Paid" |
"Based Year - Tuition Paid" (> separator) |
'"75000"' (quoted numeric) |
"75000" (unquoted) |
Configuration
| Parameter | Default | Description |
|---|---|---|
max_concurrent |
12 |
Max simultaneous Azure DI calls |
pages_per_chunk |
10 |
Pages per chunk in Stage 1 split |
poll_timeout_seconds |
120 |
Per-call Azure DI timeout |
rate_limit_max_retries |
5 |
Max 429 retry attempts |
rate_limit_initial_delay_ms |
1000 |
Initial back-off delay (ms) |
rate_limit_max_delay_ms |
32000 |
Maximum back-off delay (ms) |
enable_audit_log |
True |
Write extraction audit to file |
audit_log_dir |
"logs" |
Directory for audit log |
FERPA / PII Compliance
This library processes documents that may contain sensitive taxpayer information. The following safeguards are built in:
| Concern | Safeguard |
|---|---|
| Audit logs contain document names | Written to file only — never stdout or console |
| Credentials in CI | Stored as GitHub Secrets, never in code or logs |
| Sample data in repo | All samples use fictional data — no real SSNs or names |
| PDF files in repo | samples/*.pdf is gitignored — no documents committed |
| Log content | Only document_id, kv_count, stage, timing — no field values logged |
Caller responsibility: The extracted KvEntry.value fields may contain
SSNs, income figures, and other PII. Handle them according to your organisation's
data governance policy (FERPA, GLBA, or applicable regulations).
# Example: strip SSN fields before indexing
docs = reader.load_data("1040.pdf")
for doc in docs:
doc.text = "\n".join(
line for line in doc.text.split("\n")
if "social security" not in line.lower()
)
Azure Setup
- Create an Azure Document Intelligence resource (S0 paid tier recommended)
- Copy the endpoint URL and API key from Azure portal → Keys and Endpoint
- The
prebuilt-documentmodel is used by default — no custom training required
Development
git clone https://github.com/zavera/llama-index-readers-azure-tax-forms
cd llama-index-readers-azure-tax-forms
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
# Run unit tests (no credentials needed — Azure DI is mocked)
pytest tests/ -v
# Generate fake sample PDFs for manual testing
pip install reportlab
cd samples && python generate_samples.py
License
MIT License — Copyright (c) 2026 Callisto Tech / Ambreen Zaver
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file llama_index_readers_azure_tax_forms-0.1.0.tar.gz.
File metadata
- Download URL: llama_index_readers_azure_tax_forms-0.1.0.tar.gz
- Upload date:
- Size: 17.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.4.1 CPython/3.14.5 Darwin/25.3.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08b4d47a7010ee8ee724ba5a0e1aaa94653fe928dc16d63bd607001dc7a5131a
|
|
| MD5 |
382e5badf17cfb15970830026de95cc3
|
|
| BLAKE2b-256 |
3787bbe24491d5ec2abf79f924787505961d5e0854e6f772bea101d58c748b4b
|
File details
Details for the file llama_index_readers_azure_tax_forms-0.1.0-py3-none-any.whl.
File metadata
- Download URL: llama_index_readers_azure_tax_forms-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.4.1 CPython/3.14.5 Darwin/25.3.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f1ae02565bdb774fb05de90791cca3546fa7ac34da5ef4d5db1568c9f067503
|
|
| MD5 |
68ba94b2a569525feebd9be9192a8e9c
|
|
| BLAKE2b-256 |
0f58a9c209a110a16371385ffeceb6e17570705c0af6f5a62f98663f606c2bda
|