ocr-llm 1.0.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.
- ocr_llm-1.0.0/LICENSE +21 -0
- ocr_llm-1.0.0/MANIFEST.in +10 -0
- ocr_llm-1.0.0/PKG-INFO +308 -0
- ocr_llm-1.0.0/README.md +269 -0
- ocr_llm-1.0.0/llm_ocr/__init__.py +18 -0
- ocr_llm-1.0.0/llm_ocr/config/__init__.py +0 -0
- ocr_llm-1.0.0/llm_ocr/config/settings.py +29 -0
- ocr_llm-1.0.0/llm_ocr/core/__init__.py +0 -0
- ocr_llm-1.0.0/llm_ocr/core/base.py +48 -0
- ocr_llm-1.0.0/llm_ocr/core/processor.py +224 -0
- ocr_llm-1.0.0/llm_ocr/prompts/__init__.py +0 -0
- ocr_llm-1.0.0/llm_ocr/prompts/markdown.py +33 -0
- ocr_llm-1.0.0/llm_ocr/providers/__init__.py +4 -0
- ocr_llm-1.0.0/llm_ocr/providers/gemini.py +87 -0
- ocr_llm-1.0.0/llm_ocr/providers/openai.py +123 -0
- ocr_llm-1.0.0/ocr_llm.egg-info/PKG-INFO +308 -0
- ocr_llm-1.0.0/ocr_llm.egg-info/SOURCES.txt +22 -0
- ocr_llm-1.0.0/ocr_llm.egg-info/dependency_links.txt +1 -0
- ocr_llm-1.0.0/ocr_llm.egg-info/requires.txt +15 -0
- ocr_llm-1.0.0/ocr_llm.egg-info/top_level.txt +1 -0
- ocr_llm-1.0.0/pyproject.toml +84 -0
- ocr_llm-1.0.0/requirements-dev.txt +23 -0
- ocr_llm-1.0.0/requirements.txt +7 -0
- ocr_llm-1.0.0/setup.cfg +4 -0
ocr_llm-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Shehryar Sohail
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ocr_llm-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ocr-llm
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Convert PDFs to markdown using Large Language Models (LLMs) with vision capabilities
|
|
5
|
+
Author-email: Shehryar Sohail <hafizshehryar88@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Shehryar718/llm-ocr
|
|
8
|
+
Project-URL: Repository, https://github.com/Shehryar718/llm-ocr
|
|
9
|
+
Project-URL: Issues, https://github.com/Shehryar718/llm-ocr/issues
|
|
10
|
+
Keywords: ocr,pdf,markdown,llm,vision,gemini,openai,ai
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
20
|
+
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: pdf2image>=1.16.0
|
|
25
|
+
Requires-Dist: Pillow>=11.0.0
|
|
26
|
+
Requires-Dist: google-genai>=1.0.0
|
|
27
|
+
Requires-Dist: openai>=2.0.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio>=1.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=6.5.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: isort>=6.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: flake8>=7.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.10.0; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# LLM OCR
|
|
41
|
+
|
|
42
|
+
[](https://pypi.org/project/ocr-llm/)
|
|
43
|
+
[](https://pypi.org/project/ocr-llm/)
|
|
44
|
+
[](https://github.com/Shehryar718/llm-ocr/blob/main/LICENSE)
|
|
45
|
+
|
|
46
|
+
Convert PDFs to markdown using Large Language Models (LLMs) with vision capabilities.
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- 🔍 High-quality OCR using vision-capable LLMs
|
|
51
|
+
- 📄 Batch processing of multiple PDF pages
|
|
52
|
+
- 🔌 Multiple provider support (Gemini, OpenAI)
|
|
53
|
+
- ⚙️ Configurable processing settings
|
|
54
|
+
- 🔄 Automatic retry logic for transient errors
|
|
55
|
+
- 📝 Clean markdown output
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install ocr-llm
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### System Dependencies
|
|
64
|
+
|
|
65
|
+
You also need to install poppler (required for PDF processing):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# macOS
|
|
69
|
+
brew install poppler
|
|
70
|
+
|
|
71
|
+
# Ubuntu/Debian
|
|
72
|
+
sudo apt-get install poppler-utils
|
|
73
|
+
|
|
74
|
+
# Fedora/RHEL
|
|
75
|
+
sudo yum install poppler-utils
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Dependencies
|
|
79
|
+
|
|
80
|
+
The library requires:
|
|
81
|
+
|
|
82
|
+
- **System**: `poppler-utils` for PDF processing
|
|
83
|
+
- **Python**:
|
|
84
|
+
- `google-genai` for Gemini provider
|
|
85
|
+
- `openai` for OpenAI provider
|
|
86
|
+
- `pdf2image` and `Pillow` for PDF processing
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
### Using OpenAI
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import asyncio
|
|
94
|
+
from llm_ocr import LLMOCR, OpenAI
|
|
95
|
+
|
|
96
|
+
async def main():
|
|
97
|
+
# Initialize OpenAI provider
|
|
98
|
+
provider = OpenAI(
|
|
99
|
+
api_key="your-api-key", # Or set OPENAI_API_KEY env var
|
|
100
|
+
model=OpenAI.GPT_4O_MINI
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Create OCR processor
|
|
104
|
+
async with LLMOCR(provider) as ocr:
|
|
105
|
+
# Convert PDF to markdown
|
|
106
|
+
markdown = await ocr.convert(
|
|
107
|
+
"document.pdf",
|
|
108
|
+
output_path="output.md"
|
|
109
|
+
)
|
|
110
|
+
print(markdown)
|
|
111
|
+
|
|
112
|
+
asyncio.run(main())
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Using Gemini
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
import asyncio
|
|
119
|
+
from llm_ocr import LLMOCR, Gemini
|
|
120
|
+
|
|
121
|
+
async def main():
|
|
122
|
+
# Initialize Gemini provider
|
|
123
|
+
provider = Gemini(
|
|
124
|
+
api_key="your-api-key", # Or set GEMINI_API_KEY env var
|
|
125
|
+
model=Gemini.FLASH_2_5 # Or Gemini.PRO_2_5 for best quality
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Create OCR processor
|
|
129
|
+
async with LLMOCR(provider) as ocr:
|
|
130
|
+
# Convert PDF to markdown
|
|
131
|
+
markdown = await ocr.convert(
|
|
132
|
+
"document.pdf",
|
|
133
|
+
output_path="output.md"
|
|
134
|
+
)
|
|
135
|
+
print(markdown)
|
|
136
|
+
|
|
137
|
+
asyncio.run(main())
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Available Models
|
|
141
|
+
|
|
142
|
+
### OpenAI
|
|
143
|
+
|
|
144
|
+
- `OpenAI.GPT_4O`
|
|
145
|
+
- `OpenAI.GPT_4O_MINI` (default)
|
|
146
|
+
|
|
147
|
+
Additional models: `O1`, `O3`, `O4_MINI`, `GPT_5`, `GPT_5_MINI`, `GPT_4_1`, and more.
|
|
148
|
+
|
|
149
|
+
> See `llm_ocr/providers/openai.py` for the complete list.
|
|
150
|
+
|
|
151
|
+
### Gemini
|
|
152
|
+
|
|
153
|
+
- `Gemini.PRO_2_5`
|
|
154
|
+
- `Gemini.FLASH_2_5` (default)
|
|
155
|
+
|
|
156
|
+
Additional models: `PRO_2_0`, `FLASH_2_0`.
|
|
157
|
+
|
|
158
|
+
> See `llm_ocr/providers/gemini.py` for the complete list.
|
|
159
|
+
|
|
160
|
+
## Configuration
|
|
161
|
+
|
|
162
|
+
Customize the OCR processing with `OCRConfig`:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from llm_ocr import LLMOCR, OpenAI, OCRConfig
|
|
166
|
+
|
|
167
|
+
config = OCRConfig(
|
|
168
|
+
dpi=300, # Higher DPI for better quality
|
|
169
|
+
max_pages=10, # Limit number of pages to process
|
|
170
|
+
llm_batch_size=2, # Send 2 pages to LLM at once
|
|
171
|
+
convert_to_grayscale=True, # Convert images to grayscale
|
|
172
|
+
max_retries=3, # Retry failed requests
|
|
173
|
+
retry_delay=1.0, # Wait 1 second between retries
|
|
174
|
+
include_page_markers=True, # Add page markers in output
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
provider = OpenAI()
|
|
178
|
+
ocr = LLMOCR(provider, config=config)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Configuration Options
|
|
182
|
+
|
|
183
|
+
| Option | Default | Description |
|
|
184
|
+
| ---------------------- | ------- | ------------------------------------------ |
|
|
185
|
+
| `dpi` | 200 | DPI for PDF to image conversion (72-600) |
|
|
186
|
+
| `max_pages` | None | Maximum number of pages to process |
|
|
187
|
+
| `batch_size` | 5 | PDF to image conversion batch size |
|
|
188
|
+
| `llm_batch_size` | 1 | Number of pages to send to LLM at once |
|
|
189
|
+
| `thread_count` | 4 | Number of threads for PDF conversion |
|
|
190
|
+
| `convert_to_grayscale` | False | Convert images to grayscale |
|
|
191
|
+
| `optimize_png` | True | Optimize PNG compression |
|
|
192
|
+
| `use_cropbox` | True | Use PDF cropbox for conversion |
|
|
193
|
+
| `max_retries` | 3 | Maximum retry attempts for failed requests |
|
|
194
|
+
| `retry_delay` | 1.0 | Delay between retries in seconds |
|
|
195
|
+
| `include_page_markers` | False | Add page markers in markdown output |
|
|
196
|
+
|
|
197
|
+
## Advanced Usage
|
|
198
|
+
|
|
199
|
+
### Custom Provider Parameters
|
|
200
|
+
|
|
201
|
+
Pass additional parameters to the LLM provider:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
# OpenAI with custom parameters
|
|
205
|
+
provider = OpenAI(
|
|
206
|
+
model=OpenAI.GPT_4O,
|
|
207
|
+
max_tokens=4000,
|
|
208
|
+
temperature=0.0,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Gemini with custom parameters
|
|
212
|
+
provider = Gemini(
|
|
213
|
+
model=Gemini.PRO_2_5,
|
|
214
|
+
temperature=0.0,
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Processing Multiple Documents
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
import asyncio
|
|
222
|
+
from pathlib import Path
|
|
223
|
+
from llm_ocr import LLMOCR, OpenAI
|
|
224
|
+
|
|
225
|
+
async def process_documents():
|
|
226
|
+
provider = OpenAI()
|
|
227
|
+
|
|
228
|
+
async with LLMOCR(provider) as ocr:
|
|
229
|
+
pdf_files = Path("pdfs").glob("*.pdf")
|
|
230
|
+
|
|
231
|
+
for pdf_file in pdf_files:
|
|
232
|
+
output_file = pdf_file.with_suffix(".md")
|
|
233
|
+
await ocr.convert(pdf_file, output_path=output_file)
|
|
234
|
+
print(f"Converted {pdf_file.name} -> {output_file.name}")
|
|
235
|
+
|
|
236
|
+
asyncio.run(process_documents())
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Without Context Manager
|
|
240
|
+
|
|
241
|
+
If you prefer not to use the context manager:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
import asyncio
|
|
245
|
+
from llm_ocr import LLMOCR, OpenAI
|
|
246
|
+
|
|
247
|
+
async def main():
|
|
248
|
+
provider = OpenAI()
|
|
249
|
+
ocr = LLMOCR(provider)
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
markdown = await ocr.convert("document.pdf")
|
|
253
|
+
print(markdown)
|
|
254
|
+
finally:
|
|
255
|
+
await ocr.aclose() # Don't forget to close!
|
|
256
|
+
|
|
257
|
+
asyncio.run(main())
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Environment Variables
|
|
261
|
+
|
|
262
|
+
Set API keys via environment variables:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# For OpenAI
|
|
266
|
+
export OPENAI_API_KEY="your-openai-api-key"
|
|
267
|
+
|
|
268
|
+
# For Gemini
|
|
269
|
+
export GEMINI_API_KEY="your-gemini-api-key"
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Then use providers without passing API keys:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
# API key read from environment variable
|
|
276
|
+
provider = OpenAI() # Uses OPENAI_API_KEY
|
|
277
|
+
# or
|
|
278
|
+
provider = Gemini() # Uses GEMINI_API_KEY
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Error Handling
|
|
282
|
+
|
|
283
|
+
The library uses a fail-fast approach with automatic retries:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
import asyncio
|
|
287
|
+
from llm_ocr import LLMOCR, OpenAI, OCRConfig
|
|
288
|
+
|
|
289
|
+
async def main():
|
|
290
|
+
provider = OpenAI()
|
|
291
|
+
config = OCRConfig(
|
|
292
|
+
max_retries=5, # Retry up to 5 times
|
|
293
|
+
retry_delay=2.0, # Wait 2 seconds between retries
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
async with LLMOCR(provider, config) as ocr:
|
|
297
|
+
try:
|
|
298
|
+
markdown = await ocr.convert("document.pdf")
|
|
299
|
+
print(markdown)
|
|
300
|
+
except Exception as e:
|
|
301
|
+
print(f"Failed to process document: {e}")
|
|
302
|
+
|
|
303
|
+
asyncio.run(main())
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## License
|
|
307
|
+
|
|
308
|
+
See LICENSE file for details.
|
ocr_llm-1.0.0/README.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# LLM OCR
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/ocr-llm/)
|
|
4
|
+
[](https://pypi.org/project/ocr-llm/)
|
|
5
|
+
[](https://github.com/Shehryar718/llm-ocr/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
Convert PDFs to markdown using Large Language Models (LLMs) with vision capabilities.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 🔍 High-quality OCR using vision-capable LLMs
|
|
12
|
+
- 📄 Batch processing of multiple PDF pages
|
|
13
|
+
- 🔌 Multiple provider support (Gemini, OpenAI)
|
|
14
|
+
- ⚙️ Configurable processing settings
|
|
15
|
+
- 🔄 Automatic retry logic for transient errors
|
|
16
|
+
- 📝 Clean markdown output
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install ocr-llm
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### System Dependencies
|
|
25
|
+
|
|
26
|
+
You also need to install poppler (required for PDF processing):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# macOS
|
|
30
|
+
brew install poppler
|
|
31
|
+
|
|
32
|
+
# Ubuntu/Debian
|
|
33
|
+
sudo apt-get install poppler-utils
|
|
34
|
+
|
|
35
|
+
# Fedora/RHEL
|
|
36
|
+
sudo yum install poppler-utils
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Dependencies
|
|
40
|
+
|
|
41
|
+
The library requires:
|
|
42
|
+
|
|
43
|
+
- **System**: `poppler-utils` for PDF processing
|
|
44
|
+
- **Python**:
|
|
45
|
+
- `google-genai` for Gemini provider
|
|
46
|
+
- `openai` for OpenAI provider
|
|
47
|
+
- `pdf2image` and `Pillow` for PDF processing
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### Using OpenAI
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import asyncio
|
|
55
|
+
from llm_ocr import LLMOCR, OpenAI
|
|
56
|
+
|
|
57
|
+
async def main():
|
|
58
|
+
# Initialize OpenAI provider
|
|
59
|
+
provider = OpenAI(
|
|
60
|
+
api_key="your-api-key", # Or set OPENAI_API_KEY env var
|
|
61
|
+
model=OpenAI.GPT_4O_MINI
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Create OCR processor
|
|
65
|
+
async with LLMOCR(provider) as ocr:
|
|
66
|
+
# Convert PDF to markdown
|
|
67
|
+
markdown = await ocr.convert(
|
|
68
|
+
"document.pdf",
|
|
69
|
+
output_path="output.md"
|
|
70
|
+
)
|
|
71
|
+
print(markdown)
|
|
72
|
+
|
|
73
|
+
asyncio.run(main())
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Using Gemini
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import asyncio
|
|
80
|
+
from llm_ocr import LLMOCR, Gemini
|
|
81
|
+
|
|
82
|
+
async def main():
|
|
83
|
+
# Initialize Gemini provider
|
|
84
|
+
provider = Gemini(
|
|
85
|
+
api_key="your-api-key", # Or set GEMINI_API_KEY env var
|
|
86
|
+
model=Gemini.FLASH_2_5 # Or Gemini.PRO_2_5 for best quality
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Create OCR processor
|
|
90
|
+
async with LLMOCR(provider) as ocr:
|
|
91
|
+
# Convert PDF to markdown
|
|
92
|
+
markdown = await ocr.convert(
|
|
93
|
+
"document.pdf",
|
|
94
|
+
output_path="output.md"
|
|
95
|
+
)
|
|
96
|
+
print(markdown)
|
|
97
|
+
|
|
98
|
+
asyncio.run(main())
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Available Models
|
|
102
|
+
|
|
103
|
+
### OpenAI
|
|
104
|
+
|
|
105
|
+
- `OpenAI.GPT_4O`
|
|
106
|
+
- `OpenAI.GPT_4O_MINI` (default)
|
|
107
|
+
|
|
108
|
+
Additional models: `O1`, `O3`, `O4_MINI`, `GPT_5`, `GPT_5_MINI`, `GPT_4_1`, and more.
|
|
109
|
+
|
|
110
|
+
> See `llm_ocr/providers/openai.py` for the complete list.
|
|
111
|
+
|
|
112
|
+
### Gemini
|
|
113
|
+
|
|
114
|
+
- `Gemini.PRO_2_5`
|
|
115
|
+
- `Gemini.FLASH_2_5` (default)
|
|
116
|
+
|
|
117
|
+
Additional models: `PRO_2_0`, `FLASH_2_0`.
|
|
118
|
+
|
|
119
|
+
> See `llm_ocr/providers/gemini.py` for the complete list.
|
|
120
|
+
|
|
121
|
+
## Configuration
|
|
122
|
+
|
|
123
|
+
Customize the OCR processing with `OCRConfig`:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from llm_ocr import LLMOCR, OpenAI, OCRConfig
|
|
127
|
+
|
|
128
|
+
config = OCRConfig(
|
|
129
|
+
dpi=300, # Higher DPI for better quality
|
|
130
|
+
max_pages=10, # Limit number of pages to process
|
|
131
|
+
llm_batch_size=2, # Send 2 pages to LLM at once
|
|
132
|
+
convert_to_grayscale=True, # Convert images to grayscale
|
|
133
|
+
max_retries=3, # Retry failed requests
|
|
134
|
+
retry_delay=1.0, # Wait 1 second between retries
|
|
135
|
+
include_page_markers=True, # Add page markers in output
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
provider = OpenAI()
|
|
139
|
+
ocr = LLMOCR(provider, config=config)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Configuration Options
|
|
143
|
+
|
|
144
|
+
| Option | Default | Description |
|
|
145
|
+
| ---------------------- | ------- | ------------------------------------------ |
|
|
146
|
+
| `dpi` | 200 | DPI for PDF to image conversion (72-600) |
|
|
147
|
+
| `max_pages` | None | Maximum number of pages to process |
|
|
148
|
+
| `batch_size` | 5 | PDF to image conversion batch size |
|
|
149
|
+
| `llm_batch_size` | 1 | Number of pages to send to LLM at once |
|
|
150
|
+
| `thread_count` | 4 | Number of threads for PDF conversion |
|
|
151
|
+
| `convert_to_grayscale` | False | Convert images to grayscale |
|
|
152
|
+
| `optimize_png` | True | Optimize PNG compression |
|
|
153
|
+
| `use_cropbox` | True | Use PDF cropbox for conversion |
|
|
154
|
+
| `max_retries` | 3 | Maximum retry attempts for failed requests |
|
|
155
|
+
| `retry_delay` | 1.0 | Delay between retries in seconds |
|
|
156
|
+
| `include_page_markers` | False | Add page markers in markdown output |
|
|
157
|
+
|
|
158
|
+
## Advanced Usage
|
|
159
|
+
|
|
160
|
+
### Custom Provider Parameters
|
|
161
|
+
|
|
162
|
+
Pass additional parameters to the LLM provider:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# OpenAI with custom parameters
|
|
166
|
+
provider = OpenAI(
|
|
167
|
+
model=OpenAI.GPT_4O,
|
|
168
|
+
max_tokens=4000,
|
|
169
|
+
temperature=0.0,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Gemini with custom parameters
|
|
173
|
+
provider = Gemini(
|
|
174
|
+
model=Gemini.PRO_2_5,
|
|
175
|
+
temperature=0.0,
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Processing Multiple Documents
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
import asyncio
|
|
183
|
+
from pathlib import Path
|
|
184
|
+
from llm_ocr import LLMOCR, OpenAI
|
|
185
|
+
|
|
186
|
+
async def process_documents():
|
|
187
|
+
provider = OpenAI()
|
|
188
|
+
|
|
189
|
+
async with LLMOCR(provider) as ocr:
|
|
190
|
+
pdf_files = Path("pdfs").glob("*.pdf")
|
|
191
|
+
|
|
192
|
+
for pdf_file in pdf_files:
|
|
193
|
+
output_file = pdf_file.with_suffix(".md")
|
|
194
|
+
await ocr.convert(pdf_file, output_path=output_file)
|
|
195
|
+
print(f"Converted {pdf_file.name} -> {output_file.name}")
|
|
196
|
+
|
|
197
|
+
asyncio.run(process_documents())
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Without Context Manager
|
|
201
|
+
|
|
202
|
+
If you prefer not to use the context manager:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
import asyncio
|
|
206
|
+
from llm_ocr import LLMOCR, OpenAI
|
|
207
|
+
|
|
208
|
+
async def main():
|
|
209
|
+
provider = OpenAI()
|
|
210
|
+
ocr = LLMOCR(provider)
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
markdown = await ocr.convert("document.pdf")
|
|
214
|
+
print(markdown)
|
|
215
|
+
finally:
|
|
216
|
+
await ocr.aclose() # Don't forget to close!
|
|
217
|
+
|
|
218
|
+
asyncio.run(main())
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Environment Variables
|
|
222
|
+
|
|
223
|
+
Set API keys via environment variables:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# For OpenAI
|
|
227
|
+
export OPENAI_API_KEY="your-openai-api-key"
|
|
228
|
+
|
|
229
|
+
# For Gemini
|
|
230
|
+
export GEMINI_API_KEY="your-gemini-api-key"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Then use providers without passing API keys:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
# API key read from environment variable
|
|
237
|
+
provider = OpenAI() # Uses OPENAI_API_KEY
|
|
238
|
+
# or
|
|
239
|
+
provider = Gemini() # Uses GEMINI_API_KEY
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Error Handling
|
|
243
|
+
|
|
244
|
+
The library uses a fail-fast approach with automatic retries:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
import asyncio
|
|
248
|
+
from llm_ocr import LLMOCR, OpenAI, OCRConfig
|
|
249
|
+
|
|
250
|
+
async def main():
|
|
251
|
+
provider = OpenAI()
|
|
252
|
+
config = OCRConfig(
|
|
253
|
+
max_retries=5, # Retry up to 5 times
|
|
254
|
+
retry_delay=2.0, # Wait 2 seconds between retries
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
async with LLMOCR(provider, config) as ocr:
|
|
258
|
+
try:
|
|
259
|
+
markdown = await ocr.convert("document.pdf")
|
|
260
|
+
print(markdown)
|
|
261
|
+
except Exception as e:
|
|
262
|
+
print(f"Failed to process document: {e}")
|
|
263
|
+
|
|
264
|
+
asyncio.run(main())
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
See LICENSE file for details.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""llm-ocr: Convert PDFs to markdown using LLMs."""
|
|
2
|
+
|
|
3
|
+
from .config.settings import OCRConfig
|
|
4
|
+
from .core.base import LLMProvider
|
|
5
|
+
from .core.processor import LLMOCR
|
|
6
|
+
|
|
7
|
+
# Import providers
|
|
8
|
+
from .providers.gemini import Gemini
|
|
9
|
+
from .providers.openai import OpenAI
|
|
10
|
+
|
|
11
|
+
__version__ = "1.0.0"
|
|
12
|
+
__all__ = [
|
|
13
|
+
"LLMOCR",
|
|
14
|
+
"OCRConfig",
|
|
15
|
+
"LLMProvider",
|
|
16
|
+
"Gemini",
|
|
17
|
+
"OpenAI",
|
|
18
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class OCRConfig:
|
|
7
|
+
"""Configuration for OCR processing."""
|
|
8
|
+
|
|
9
|
+
dpi: int = 200
|
|
10
|
+
max_pages: Optional[int] = None
|
|
11
|
+
batch_size: int = 5 # PDF to image conversion batch size
|
|
12
|
+
llm_batch_size: int = 1 # Number of pages to send to LLM at once
|
|
13
|
+
thread_count: int = 4
|
|
14
|
+
convert_to_grayscale: bool = False
|
|
15
|
+
optimize_png: bool = True
|
|
16
|
+
use_cropbox: bool = True
|
|
17
|
+
max_retries: int = 3
|
|
18
|
+
retry_delay: float = 1.0
|
|
19
|
+
output_format: str = "markdown"
|
|
20
|
+
include_page_markers: bool = False
|
|
21
|
+
|
|
22
|
+
def validate(self):
|
|
23
|
+
"""Validate configuration."""
|
|
24
|
+
if self.dpi < 72 or self.dpi > 600:
|
|
25
|
+
raise ValueError("DPI must be between 72 and 600")
|
|
26
|
+
if self.batch_size < 1:
|
|
27
|
+
raise ValueError("Batch size must be at least 1")
|
|
28
|
+
if self.llm_batch_size < 1:
|
|
29
|
+
raise ValueError("LLM batch size must be at least 1")
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class LLMProvider(ABC):
|
|
6
|
+
"""Abstract base class for LLM providers."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, api_key: str, model: str, **kwargs):
|
|
9
|
+
"""
|
|
10
|
+
Initialize the LLM provider.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
api_key: API key for the provider
|
|
14
|
+
model: Model identifier/name
|
|
15
|
+
**kwargs: Additional provider-specific parameters
|
|
16
|
+
"""
|
|
17
|
+
self.api_key = api_key
|
|
18
|
+
self.model = model
|
|
19
|
+
self.kwargs = kwargs
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
async def generate_content(
|
|
23
|
+
self,
|
|
24
|
+
prompt: str,
|
|
25
|
+
image_bytes_list: List[bytes],
|
|
26
|
+
) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Generate content from images and prompt.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
prompt: The instruction prompt
|
|
32
|
+
image_bytes_list: List of PNG image bytes (one or more pages)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Generated text content
|
|
36
|
+
"""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
async def aclose(self):
|
|
41
|
+
"""Close and cleanup resources."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def name(self) -> str:
|
|
47
|
+
"""Return the provider name."""
|
|
48
|
+
pass
|