fastdocparse 0.1.1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastdocparse-0.3.0/PKG-INFO +163 -0
- fastdocparse-0.3.0/README.md +130 -0
- fastdocparse-0.3.0/pyproject.toml +66 -0
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/__init__.py +33 -21
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/cache.py +8 -7
- fastdocparse-0.3.0/src/fastdocparse/cli.py +232 -0
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/grounding.py +23 -25
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/json_repair.py +11 -7
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/llm_client.py +5 -4
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/ocr_engine.py +12 -8
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/parser.py +40 -28
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/pdf_utils.py +29 -20
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/prompt_compiler.py +6 -4
- fastdocparse-0.3.0/src/fastdocparse/result.py +75 -0
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/schema.py +14 -12
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/schema_compiler.py +2 -2
- fastdocparse-0.3.0/src/fastdocparse.egg-info/PKG-INFO +163 -0
- fastdocparse-0.3.0/src/fastdocparse.egg-info/SOURCES.txt +33 -0
- fastdocparse-0.3.0/src/fastdocparse.egg-info/entry_points.txt +2 -0
- fastdocparse-0.3.0/src/fastdocparse.egg-info/top_level.txt +1 -0
- {fastdocparse-0.1.1 → fastdocparse-0.3.0}/tests/test_architecture.py +22 -17
- fastdocparse-0.3.0/tests/test_cli.py +241 -0
- {fastdocparse-0.1.1 → fastdocparse-0.3.0}/tests/test_grounding.py +2 -2
- {fastdocparse-0.1.1 → fastdocparse-0.3.0}/tests/test_parser.py +33 -33
- {fastdocparse-0.1.1 → fastdocparse-0.3.0}/tests/test_schema_compiler.py +4 -3
- {fastdocparse-0.1.1 → fastdocparse-0.3.0}/tests/test_severe_edge_cases.py +71 -11
- fastdocparse-0.1.1/PKG-INFO +0 -138
- fastdocparse-0.1.1/README.md +0 -119
- fastdocparse-0.1.1/pyproject.toml +0 -35
- fastdocparse-0.1.1/src/docextract/cli.py +0 -140
- fastdocparse-0.1.1/src/docextract/result.py +0 -42
- fastdocparse-0.1.1/src/fastdocparse.egg-info/PKG-INFO +0 -138
- fastdocparse-0.1.1/src/fastdocparse.egg-info/SOURCES.txt +0 -33
- fastdocparse-0.1.1/src/fastdocparse.egg-info/entry_points.txt +0 -2
- fastdocparse-0.1.1/src/fastdocparse.egg-info/top_level.txt +0 -1
- fastdocparse-0.1.1/tests/test_cli.py +0 -129
- {fastdocparse-0.1.1 → fastdocparse-0.3.0}/LICENSE +0 -0
- {fastdocparse-0.1.1 → fastdocparse-0.3.0}/setup.cfg +0 -0
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/config.py +0 -0
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/example_schemas.py +0 -0
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/py.typed +0 -0
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/schemas/invoice.json +0 -0
- {fastdocparse-0.1.1/src/docextract → fastdocparse-0.3.0/src/fastdocparse}/schemas/shipment_manifest.json +0 -0
- {fastdocparse-0.1.1 → fastdocparse-0.3.0}/src/fastdocparse.egg-info/dependency_links.txt +0 -0
- {fastdocparse-0.1.1 → fastdocparse-0.3.0}/src/fastdocparse.egg-info/requires.txt +0 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastdocparse
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Extract structured data from semi-structured documents using any OpenAI-compatible LLM, with per-field grounding and confidence.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/pranjalparmar/fastdocparse
|
|
7
|
+
Project-URL: Repository, https://github.com/pranjalparmar/fastdocparse
|
|
8
|
+
Project-URL: Issues, https://github.com/pranjalparmar/fastdocparse/issues
|
|
9
|
+
Project-URL: Documentation, https://github.com/pranjalparmar/fastdocparse/tree/main/docs
|
|
10
|
+
Keywords: document-extraction,llm,pdf,ocr,invoice-extraction,structured-data,openai,ollama,grounding
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: <3.13,>=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: pydantic>=2.0
|
|
24
|
+
Requires-Dist: openai>=1.0
|
|
25
|
+
Requires-Dist: pymupdf>=1.24
|
|
26
|
+
Requires-Dist: pillow>=10.0
|
|
27
|
+
Requires-Dist: rapidocr-onnxruntime>=1.3
|
|
28
|
+
Requires-Dist: typer>=0.12
|
|
29
|
+
Requires-Dist: PyYAML>=6.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# fastdocparse
|
|
35
|
+
|
|
36
|
+
[](https://pypi.org/project/fastdocparse/)
|
|
37
|
+
[](https://github.com/pranjalparmar/fastdocparse/actions/workflows/ci.yml)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
Extract structured data from semi-structured documents (invoices, bills, tax forms, resumes, bank statements, shipment manifests) using any OpenAI-compatible LLM (OpenAI, Ollama, vLLM, Groq, etc.), with **per-field grounding and confidence**, not just raw extraction.
|
|
41
|
+
|
|
42
|
+
## Why this, not just another parser
|
|
43
|
+
|
|
44
|
+
Most extractors give you a value and no way to know if it's real. This one tells you:
|
|
45
|
+
|
|
46
|
+
- **`grounded`**: the value was found verbatim (or near-verbatim) in the source document text.
|
|
47
|
+
- **`ungrounded`**: the value doesn't appear in the source, likely a hallucination. Flag for human review.
|
|
48
|
+
- **`missing_required`**: a field you marked required came back empty.
|
|
49
|
+
- **`invalid_format`**: the value doesn't match a pattern/enum constraint you declared (e.g. a shipment status outside the allowed list).
|
|
50
|
+
- **`failed_check`**: a custom cross-field rule failed (e.g. line items don't sum to the stated total).
|
|
51
|
+
|
|
52
|
+
No extra LLM call for any of this: it's deterministic, string/rule-based validation against text you already extracted.
|
|
53
|
+
|
|
54
|
+
**Where it fits:** semi-structured documents with recurring fields (invoices, bills, tax forms, resumes, statements), and prose documents where *proving* a value came from the source matters (contracts, legal clauses, insurance claims). It is not a vision-LLM pipeline. It works from extracted text (digital PDF text layer, or local OCR for scans/images), which is what keeps it fast, cheap, and usable with small local models. Messy handwritten forms or complex multi-column layouts are a known weaker spot (see [document-extractor-spec.md](document-extractor-spec.md)).
|
|
55
|
+
|
|
56
|
+
## How this compares
|
|
57
|
+
|
|
58
|
+
| Project | Approach | Where it beats fastdocparse | Where fastdocparse can beat it |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| **[Sparrow](https://github.com/katanaml/sparrow)** (katanaml) | Vision-LLM first (MLX/vLLM/Ollama/Mistral OCR), multi-service platform, API-first | Layout-aware (sees the page), mature, table templates for complex tables | No GPU required, single `pip install`, Pydantic-schema devex vs. raw JSON-string CLI args |
|
|
61
|
+
| **[LangExtract](https://github.com/google/langextract)** (Google) | Text-first, source-grounding (character-offset mapping), few-shot examples required | Real grounding/traceability, brand trust | Purpose-built for documents (OCR routing) vs. text-in/text-out |
|
|
62
|
+
|
|
63
|
+
**The honest edge:** *fast, cheap, local-model-friendly extraction for clean-to-moderate documents*, not "better than Sparrow at everything." This has not yet been validated with a real head-to-head benchmark ([tracking issue](https://github.com/pranjalparmar/fastdocparse/issues/19)). Treat it as a design goal, not a proven claim, until that lands.
|
|
64
|
+
|
|
65
|
+
## Two ways to use it
|
|
66
|
+
|
|
67
|
+
| | Who it's for | How |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| **CLI** | No coding needed | `fastdocparse extract <file> <schema.json>` / `fastdocparse --version` |
|
|
70
|
+
| **Python API** | Building it into your own app | `DocumentParser(client).extract(document_bytes, schema)` |
|
|
71
|
+
|
|
72
|
+
Defining *what* to extract also has two paths: hand-write a JSON/YAML schema file, or describe it in plain English and let the LLM draft the schema for you.
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install fastdocparse
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For local development instead:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
git clone https://github.com/pranjalparmar/fastdocparse
|
|
84
|
+
cd fastdocparse
|
|
85
|
+
python -m venv venv
|
|
86
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
87
|
+
pip install -e ".[dev]"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
You also need access to an LLM. Either:
|
|
91
|
+
- An OpenAI API key (`export OPENAI_API_KEY=...` or pass `--api-key`), or
|
|
92
|
+
- A local model via [Ollama](https://ollama.com/): no API key, no cloud, documents never leave your machine.
|
|
93
|
+
|
|
94
|
+
## Quickstart: CLI (no coding)
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# 1. Extract using one of the bundled example schemas
|
|
98
|
+
fastdocparse extract sample_invoice.png src/fastdocparse/schemas/invoice.json \
|
|
99
|
+
--model gpt-4o-mini --api-key sk-...
|
|
100
|
+
|
|
101
|
+
# Or with a local model via Ollama (no API key needed):
|
|
102
|
+
fastdocparse extract sample_invoice.png src/fastdocparse/schemas/invoice.json \
|
|
103
|
+
--model llama3.2 --base-url http://localhost:11434/v1 --api-key ollama
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Output is JSON, printed to stdout (or saved with `--output result.json`):
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"_meta": { "truncated": false, "truncation_reason": null },
|
|
111
|
+
"invoice_number": { "value": "INV-9011", "confidence": "high", "flags": ["grounded"] },
|
|
112
|
+
"total_price": { "value": 100.0, "confidence": "high", "flags": ["grounded"] }
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Don't want to write JSON at all? Describe the fields in plain English instead:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
fastdocparse schema-from-text \
|
|
120
|
+
"I want the invoice number, total price, and vendor name. Invoice number and total are required." \
|
|
121
|
+
--output my_invoice_schema.json
|
|
122
|
+
|
|
123
|
+
# review my_invoice_schema.json, then:
|
|
124
|
+
fastdocparse extract my_invoice.pdf my_invoice_schema.json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Quickstart: Python API
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from fastdocparse import Schema, Field, LLMClient, DocumentParser
|
|
131
|
+
|
|
132
|
+
schema = Schema(
|
|
133
|
+
name="Invoice",
|
|
134
|
+
fields=[
|
|
135
|
+
Field(name="invoice_number", description="The invoice number", required=True),
|
|
136
|
+
Field(name="total_price", description="Total amount due", type="number", required=True),
|
|
137
|
+
],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
client = LLMClient(model="gpt-4o-mini", api_key="sk-...")
|
|
141
|
+
# or: LLMClient(base_url="http://localhost:11434/v1", api_key="ollama", model="llama3.2")
|
|
142
|
+
|
|
143
|
+
parser = DocumentParser(client=client)
|
|
144
|
+
|
|
145
|
+
with open("invoice.pdf", "rb") as f:
|
|
146
|
+
result = parser.extract(f.read(), schema)
|
|
147
|
+
|
|
148
|
+
print(result["invoice_number"]) # {'value': 'INV-9011', 'confidence': 'high', 'flags': ['grounded']}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Full documentation
|
|
152
|
+
|
|
153
|
+
- [Getting Started](docs/getting-started.md): step-by-step install, CLI, and API walkthroughs
|
|
154
|
+
- [Schema Guide](docs/schema-guide.md): every field option (`type`, `required`, `pattern`, `enum`, `sub_fields`, few-shot `examples`), for JSON, YAML, and plain-English authoring
|
|
155
|
+
- [Output & Validation](docs/output-format.md): the full result shape, what each confidence flag means, and how to write custom cross-check rules
|
|
156
|
+
- [Architecture](docs/architecture.md): diagrams of the pipeline, the module dependency graph, and where to plug in a contribution
|
|
157
|
+
- [Project spec](document-extractor-spec.md): architecture, phased roadmap, honest competitive positioning
|
|
158
|
+
|
|
159
|
+
Want to contribute? Start with [docs/architecture.md](docs/architecture.md) for the map, then [CONTRIBUTING.md](CONTRIBUTING.md) for the process.
|
|
160
|
+
|
|
161
|
+
## Status
|
|
162
|
+
|
|
163
|
+
Core extraction, grounding, chunking, both CLI/API paths, and real packaging are implemented and tested (75 tests, `pytest -v`). Published on PyPI as [`fastdocparse`](https://pypi.org/project/fastdocparse/). `pip install fastdocparse` installs a working `fastdocparse` command and a proper `fastdocparse.*` import namespace, verified end to end with a clean-virtualenv install straight from the real public index. Not yet done: a hosted API. See [document-extractor-spec.md](document-extractor-spec.md) for the roadmap.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# fastdocparse
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/fastdocparse/)
|
|
4
|
+
[](https://github.com/pranjalparmar/fastdocparse/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Extract structured data from semi-structured documents (invoices, bills, tax forms, resumes, bank statements, shipment manifests) using any OpenAI-compatible LLM (OpenAI, Ollama, vLLM, Groq, etc.), with **per-field grounding and confidence**, not just raw extraction.
|
|
8
|
+
|
|
9
|
+
## Why this, not just another parser
|
|
10
|
+
|
|
11
|
+
Most extractors give you a value and no way to know if it's real. This one tells you:
|
|
12
|
+
|
|
13
|
+
- **`grounded`**: the value was found verbatim (or near-verbatim) in the source document text.
|
|
14
|
+
- **`ungrounded`**: the value doesn't appear in the source, likely a hallucination. Flag for human review.
|
|
15
|
+
- **`missing_required`**: a field you marked required came back empty.
|
|
16
|
+
- **`invalid_format`**: the value doesn't match a pattern/enum constraint you declared (e.g. a shipment status outside the allowed list).
|
|
17
|
+
- **`failed_check`**: a custom cross-field rule failed (e.g. line items don't sum to the stated total).
|
|
18
|
+
|
|
19
|
+
No extra LLM call for any of this: it's deterministic, string/rule-based validation against text you already extracted.
|
|
20
|
+
|
|
21
|
+
**Where it fits:** semi-structured documents with recurring fields (invoices, bills, tax forms, resumes, statements), and prose documents where *proving* a value came from the source matters (contracts, legal clauses, insurance claims). It is not a vision-LLM pipeline. It works from extracted text (digital PDF text layer, or local OCR for scans/images), which is what keeps it fast, cheap, and usable with small local models. Messy handwritten forms or complex multi-column layouts are a known weaker spot (see [document-extractor-spec.md](document-extractor-spec.md)).
|
|
22
|
+
|
|
23
|
+
## How this compares
|
|
24
|
+
|
|
25
|
+
| Project | Approach | Where it beats fastdocparse | Where fastdocparse can beat it |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| **[Sparrow](https://github.com/katanaml/sparrow)** (katanaml) | Vision-LLM first (MLX/vLLM/Ollama/Mistral OCR), multi-service platform, API-first | Layout-aware (sees the page), mature, table templates for complex tables | No GPU required, single `pip install`, Pydantic-schema devex vs. raw JSON-string CLI args |
|
|
28
|
+
| **[LangExtract](https://github.com/google/langextract)** (Google) | Text-first, source-grounding (character-offset mapping), few-shot examples required | Real grounding/traceability, brand trust | Purpose-built for documents (OCR routing) vs. text-in/text-out |
|
|
29
|
+
|
|
30
|
+
**The honest edge:** *fast, cheap, local-model-friendly extraction for clean-to-moderate documents*, not "better than Sparrow at everything." This has not yet been validated with a real head-to-head benchmark ([tracking issue](https://github.com/pranjalparmar/fastdocparse/issues/19)). Treat it as a design goal, not a proven claim, until that lands.
|
|
31
|
+
|
|
32
|
+
## Two ways to use it
|
|
33
|
+
|
|
34
|
+
| | Who it's for | How |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| **CLI** | No coding needed | `fastdocparse extract <file> <schema.json>` / `fastdocparse --version` |
|
|
37
|
+
| **Python API** | Building it into your own app | `DocumentParser(client).extract(document_bytes, schema)` |
|
|
38
|
+
|
|
39
|
+
Defining *what* to extract also has two paths: hand-write a JSON/YAML schema file, or describe it in plain English and let the LLM draft the schema for you.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install fastdocparse
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For local development instead:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/pranjalparmar/fastdocparse
|
|
51
|
+
cd fastdocparse
|
|
52
|
+
python -m venv venv
|
|
53
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
54
|
+
pip install -e ".[dev]"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
You also need access to an LLM. Either:
|
|
58
|
+
- An OpenAI API key (`export OPENAI_API_KEY=...` or pass `--api-key`), or
|
|
59
|
+
- A local model via [Ollama](https://ollama.com/): no API key, no cloud, documents never leave your machine.
|
|
60
|
+
|
|
61
|
+
## Quickstart: CLI (no coding)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# 1. Extract using one of the bundled example schemas
|
|
65
|
+
fastdocparse extract sample_invoice.png src/fastdocparse/schemas/invoice.json \
|
|
66
|
+
--model gpt-4o-mini --api-key sk-...
|
|
67
|
+
|
|
68
|
+
# Or with a local model via Ollama (no API key needed):
|
|
69
|
+
fastdocparse extract sample_invoice.png src/fastdocparse/schemas/invoice.json \
|
|
70
|
+
--model llama3.2 --base-url http://localhost:11434/v1 --api-key ollama
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Output is JSON, printed to stdout (or saved with `--output result.json`):
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"_meta": { "truncated": false, "truncation_reason": null },
|
|
78
|
+
"invoice_number": { "value": "INV-9011", "confidence": "high", "flags": ["grounded"] },
|
|
79
|
+
"total_price": { "value": 100.0, "confidence": "high", "flags": ["grounded"] }
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Don't want to write JSON at all? Describe the fields in plain English instead:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
fastdocparse schema-from-text \
|
|
87
|
+
"I want the invoice number, total price, and vendor name. Invoice number and total are required." \
|
|
88
|
+
--output my_invoice_schema.json
|
|
89
|
+
|
|
90
|
+
# review my_invoice_schema.json, then:
|
|
91
|
+
fastdocparse extract my_invoice.pdf my_invoice_schema.json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Quickstart: Python API
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from fastdocparse import Schema, Field, LLMClient, DocumentParser
|
|
98
|
+
|
|
99
|
+
schema = Schema(
|
|
100
|
+
name="Invoice",
|
|
101
|
+
fields=[
|
|
102
|
+
Field(name="invoice_number", description="The invoice number", required=True),
|
|
103
|
+
Field(name="total_price", description="Total amount due", type="number", required=True),
|
|
104
|
+
],
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
client = LLMClient(model="gpt-4o-mini", api_key="sk-...")
|
|
108
|
+
# or: LLMClient(base_url="http://localhost:11434/v1", api_key="ollama", model="llama3.2")
|
|
109
|
+
|
|
110
|
+
parser = DocumentParser(client=client)
|
|
111
|
+
|
|
112
|
+
with open("invoice.pdf", "rb") as f:
|
|
113
|
+
result = parser.extract(f.read(), schema)
|
|
114
|
+
|
|
115
|
+
print(result["invoice_number"]) # {'value': 'INV-9011', 'confidence': 'high', 'flags': ['grounded']}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Full documentation
|
|
119
|
+
|
|
120
|
+
- [Getting Started](docs/getting-started.md): step-by-step install, CLI, and API walkthroughs
|
|
121
|
+
- [Schema Guide](docs/schema-guide.md): every field option (`type`, `required`, `pattern`, `enum`, `sub_fields`, few-shot `examples`), for JSON, YAML, and plain-English authoring
|
|
122
|
+
- [Output & Validation](docs/output-format.md): the full result shape, what each confidence flag means, and how to write custom cross-check rules
|
|
123
|
+
- [Architecture](docs/architecture.md): diagrams of the pipeline, the module dependency graph, and where to plug in a contribution
|
|
124
|
+
- [Project spec](document-extractor-spec.md): architecture, phased roadmap, honest competitive positioning
|
|
125
|
+
|
|
126
|
+
Want to contribute? Start with [docs/architecture.md](docs/architecture.md) for the map, then [CONTRIBUTING.md](CONTRIBUTING.md) for the process.
|
|
127
|
+
|
|
128
|
+
## Status
|
|
129
|
+
|
|
130
|
+
Core extraction, grounding, chunking, both CLI/API paths, and real packaging are implemented and tested (75 tests, `pytest -v`). Published on PyPI as [`fastdocparse`](https://pypi.org/project/fastdocparse/). `pip install fastdocparse` installs a working `fastdocparse` command and a proper `fastdocparse.*` import namespace, verified end to end with a clean-virtualenv install straight from the real public index. Not yet done: a hosted API. See [document-extractor-spec.md](document-extractor-spec.md) for the roadmap.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fastdocparse"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
description = "Extract structured data from semi-structured documents using any OpenAI-compatible LLM, with per-field grounding and confidence."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.9,<3.13" # capped by rapidocr-onnxruntime's own upper bound (checked live against PyPI)
|
|
8
|
+
keywords = [
|
|
9
|
+
"document-extraction",
|
|
10
|
+
"llm",
|
|
11
|
+
"pdf",
|
|
12
|
+
"ocr",
|
|
13
|
+
"invoice-extraction",
|
|
14
|
+
"structured-data",
|
|
15
|
+
"openai",
|
|
16
|
+
"ollama",
|
|
17
|
+
"grounding",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"Topic :: Text Processing :: Linguistic",
|
|
28
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"pydantic>=2.0",
|
|
32
|
+
"openai>=1.0",
|
|
33
|
+
"pymupdf>=1.24",
|
|
34
|
+
"pillow>=10.0",
|
|
35
|
+
"rapidocr-onnxruntime>=1.3",
|
|
36
|
+
"typer>=0.12",
|
|
37
|
+
"PyYAML>=6.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
dev = ["pytest"]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/pranjalparmar/fastdocparse"
|
|
45
|
+
Repository = "https://github.com/pranjalparmar/fastdocparse"
|
|
46
|
+
Issues = "https://github.com/pranjalparmar/fastdocparse/issues"
|
|
47
|
+
Documentation = "https://github.com/pranjalparmar/fastdocparse/tree/main/docs"
|
|
48
|
+
|
|
49
|
+
[project.scripts]
|
|
50
|
+
fastdocparse = "fastdocparse.cli:app"
|
|
51
|
+
|
|
52
|
+
[build-system]
|
|
53
|
+
requires = ["setuptools>=68"]
|
|
54
|
+
build-backend = "setuptools.build_meta"
|
|
55
|
+
|
|
56
|
+
[tool.setuptools.packages.find]
|
|
57
|
+
where = ["src"]
|
|
58
|
+
|
|
59
|
+
[tool.setuptools.package-data]
|
|
60
|
+
fastdocparse = ["schemas/*.json", "py.typed"]
|
|
61
|
+
|
|
62
|
+
[tool.pytest.ini_options]
|
|
63
|
+
testpaths = ["tests"]
|
|
64
|
+
|
|
65
|
+
[tool.ruff.lint.per-file-ignores]
|
|
66
|
+
"src/fastdocparse/cli.py" = ["B008"]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""fastdocparse — extract structured data from semi-structured documents using any
|
|
2
2
|
OpenAI-compatible LLM, with per-field grounding and confidence.
|
|
3
3
|
|
|
4
|
-
from
|
|
4
|
+
from fastdocparse import Schema, Field, LLMClient, DocumentParser
|
|
5
5
|
|
|
6
6
|
schema = Schema(name="Invoice", fields=[Field(name="total", description="Grand total", type="number")])
|
|
7
7
|
client = LLMClient(model="gpt-4o-mini", api_key="sk-...")
|
|
@@ -10,43 +10,55 @@ OpenAI-compatible LLM, with per-field grounding and confidence.
|
|
|
10
10
|
|
|
11
11
|
from .cache import Cache, InMemoryCache
|
|
12
12
|
from .config import ExtractionConfig
|
|
13
|
-
from .grounding import
|
|
13
|
+
from .grounding import (
|
|
14
|
+
Issue,
|
|
15
|
+
check_substring,
|
|
16
|
+
cross_check,
|
|
17
|
+
date_parseable_rule,
|
|
18
|
+
numeric_sum_rule,
|
|
19
|
+
validate_field_constraints,
|
|
20
|
+
)
|
|
14
21
|
from .llm_client import LLMClient, LLMClientError
|
|
15
|
-
from .parser import
|
|
22
|
+
from .parser import (
|
|
23
|
+
DocumentParser,
|
|
24
|
+
EmptyDocumentError,
|
|
25
|
+
UnknownIngestionKindError,
|
|
26
|
+
register_default_ingestion_handler,
|
|
27
|
+
)
|
|
16
28
|
from .result import ExtractionMeta, ExtractionResult, FieldResult
|
|
17
29
|
from .schema import Field, Schema
|
|
18
30
|
from .schema_compiler import compile_schema_from_description
|
|
19
31
|
|
|
20
32
|
try:
|
|
33
|
+
from importlib.metadata import PackageNotFoundError
|
|
21
34
|
from importlib.metadata import version as _pkg_version
|
|
22
|
-
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
# in sync with pyproject.toml's [project] name if that ever changes again.
|
|
35
|
+
|
|
36
|
+
# Looks up by the PyPI *distribution* name (pyproject.toml's [project] name) — keep
|
|
37
|
+
# this string in sync with that if the distribution is ever renamed again.
|
|
26
38
|
__version__ = _pkg_version("fastdocparse")
|
|
27
|
-
except
|
|
39
|
+
except PackageNotFoundError:
|
|
28
40
|
__version__ = "0.0.0+unknown"
|
|
29
41
|
|
|
30
42
|
__all__ = [
|
|
31
|
-
"
|
|
32
|
-
"Field",
|
|
33
|
-
"LLMClient",
|
|
34
|
-
"LLMClientError",
|
|
43
|
+
"Cache",
|
|
35
44
|
"DocumentParser",
|
|
36
45
|
"EmptyDocumentError",
|
|
37
|
-
"UnknownIngestionKindError",
|
|
38
|
-
"register_default_ingestion_handler",
|
|
39
46
|
"ExtractionConfig",
|
|
40
|
-
"
|
|
41
|
-
"InMemoryCache",
|
|
47
|
+
"ExtractionMeta",
|
|
42
48
|
"ExtractionResult",
|
|
49
|
+
"Field",
|
|
43
50
|
"FieldResult",
|
|
44
|
-
"
|
|
51
|
+
"InMemoryCache",
|
|
45
52
|
"Issue",
|
|
53
|
+
"LLMClient",
|
|
54
|
+
"LLMClientError",
|
|
55
|
+
"Schema",
|
|
56
|
+
"UnknownIngestionKindError",
|
|
46
57
|
"check_substring",
|
|
58
|
+
"compile_schema_from_description",
|
|
47
59
|
"cross_check",
|
|
48
|
-
"validate_field_constraints",
|
|
49
|
-
"numeric_sum_rule",
|
|
50
60
|
"date_parseable_rule",
|
|
51
|
-
"
|
|
61
|
+
"numeric_sum_rule",
|
|
62
|
+
"register_default_ingestion_handler",
|
|
63
|
+
"validate_field_constraints",
|
|
52
64
|
]
|
|
@@ -4,19 +4,20 @@ Caching is skipped whenever custom `rules` are passed to extract(), since a rule
|
|
|
4
4
|
arbitrary callable that can't be safely fingerprinted — caching would risk returning a
|
|
5
5
|
result validated under a different rule than the one just requested.
|
|
6
6
|
"""
|
|
7
|
+
from __future__ import annotations
|
|
7
8
|
|
|
8
9
|
import hashlib
|
|
9
10
|
import json
|
|
10
11
|
from collections import OrderedDict
|
|
11
|
-
from typing import Any, Callable,
|
|
12
|
+
from typing import Any, Callable, Protocol
|
|
12
13
|
|
|
13
14
|
from .config import ExtractionConfig
|
|
14
15
|
from .schema import Schema
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class Cache(Protocol):
|
|
18
|
-
def get(self, key: str) ->
|
|
19
|
-
def set(self, key: str, value:
|
|
19
|
+
def get(self, key: str) -> dict[str, Any] | None: ...
|
|
20
|
+
def set(self, key: str, value: dict[str, Any]) -> None: ...
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class InMemoryCache:
|
|
@@ -27,19 +28,19 @@ class InMemoryCache:
|
|
|
27
28
|
process exits before the cache could grow unbounded.
|
|
28
29
|
"""
|
|
29
30
|
|
|
30
|
-
def __init__(self, max_size:
|
|
31
|
+
def __init__(self, max_size: int | None = None):
|
|
31
32
|
if max_size is not None and max_size <= 0:
|
|
32
33
|
raise ValueError(f"max_size must be positive, got {max_size}")
|
|
33
34
|
self._max_size = max_size
|
|
34
|
-
self._store:
|
|
35
|
+
self._store: OrderedDict[str, dict[str, Any]] = OrderedDict()
|
|
35
36
|
|
|
36
|
-
def get(self, key: str) ->
|
|
37
|
+
def get(self, key: str) -> dict[str, Any] | None:
|
|
37
38
|
if key not in self._store:
|
|
38
39
|
return None
|
|
39
40
|
self._store.move_to_end(key)
|
|
40
41
|
return self._store[key]
|
|
41
42
|
|
|
42
|
-
def set(self, key: str, value:
|
|
43
|
+
def set(self, key: str, value: dict[str, Any]) -> None:
|
|
43
44
|
self._store[key] = value
|
|
44
45
|
self._store.move_to_end(key)
|
|
45
46
|
if self._max_size is not None:
|