Skip to content

Commit 51e63fe

Browse files
bzsantimojobytes
andauthored
Add oxidize-pdf integration (#510)
Adds integrations/oxidize-pdf.md for haystack-oxidize-pdf, a Data Ingestion converter (OxidizePdfConverter) backed by the Rust-powered oxidize-pdf engine, with element-disjoint RAG chunking and native ByteStream support. Published on PyPI as haystack-oxidize-pdf. Co-authored-by: Bytaro <[email protected]>
1 parent 0dbe5c3 commit 51e63fe

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

integrations/oxidize-pdf.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
layout: integration
3+
name: oxidize-pdf
4+
description: Convert PDFs into Haystack Documents with a fast Rust engine and element-disjoint RAG chunking; accepts paths and ByteStreams
5+
authors:
6+
- name: Santiago Fernández Muñoz
7+
socials:
8+
github: bzsanti
9+
pypi: https://pypi.org/project/haystack-oxidize-pdf
10+
repo: https://github.com/bzsanti/oxidize-pdf-integrations/tree/main/haystack
11+
type: Data Ingestion
12+
report_issue: https://github.com/bzsanti/oxidize-pdf-integrations/issues
13+
version: Haystack 2.0
14+
toc: true
15+
---
16+
### **Table of Contents**
17+
- [Overview](#overview)
18+
- [Installation](#installation)
19+
- [Usage](#usage)
20+
- [License](#license)
21+
22+
## Overview
23+
24+
[`haystack-oxidize-pdf`](https://pypi.org/project/haystack-oxidize-pdf/) is a Haystack
25+
converter backed by [oxidize-pdf](https://github.com/bzsanti/oxidize-python), a
26+
Rust-powered PDF engine with first-class RAG primitives. The parser runs natively (no
27+
system dependencies — it ships as a wheel for Linux, macOS and Windows) and exposes
28+
element-disjoint semantic chunking, so PDFs become retrieval-ready `Document` objects
29+
without any post-processing.
30+
31+
The chunking contract is enforced by regression tests: no chunk's text is a substring of
32+
another's, and every source element appears in exactly one chunk. This guarantees no
33+
duplicated context leaks into a vector store during ingestion.
34+
35+
## Installation
36+
37+
```bash
38+
pip install haystack-oxidize-pdf
39+
```
40+
41+
The package depends on `haystack-ai>=2.0,<3` and `oxidize-pdf>=0.4.3`.
42+
43+
## Usage
44+
45+
### Components
46+
47+
This integration introduces `OxidizePdfConverter`, a `@component` that reads PDF sources
48+
and outputs Haystack `Document` objects. Sources may be file paths (`str` /
49+
`pathlib.Path`) or `ByteStream` instances, interchangeably.
50+
51+
The converter has a single `__init__` argument, `mode`, controlling how each source is
52+
turned into documents:
53+
54+
- `mode="rag"` (default): one `Document` per semantic chunk produced by oxidize-pdf's
55+
chunker. Per-chunk metadata exposes `chunk_index` (0-based, resets per source),
56+
`page_numbers` (1-indexed), `element_types`, `heading_context`, and `token_estimate`.
57+
- `mode="pages"`: one `Document` per page (plain text); metadata carries `page_number`
58+
(1-indexed).
59+
- `mode="markdown"`: a single `Document` per source containing the whole PDF as markdown;
60+
no `page_number` is emitted.
61+
62+
### Use the Converter standalone
63+
64+
```python
65+
from haystack_oxidize_pdf import OxidizePdfConverter
66+
67+
converter = OxidizePdfConverter() # mode="rag" by default
68+
result = converter.run(sources=["paper.pdf"])
69+
70+
for doc in result["documents"]:
71+
print(doc.meta["chunk_index"], doc.meta["heading_context"])
72+
print(doc.content[:200])
73+
```
74+
75+
### Use it in a Pipeline
76+
77+
```python
78+
from haystack import Pipeline
79+
from haystack_oxidize_pdf import OxidizePdfConverter
80+
81+
pipeline = Pipeline()
82+
pipeline.add_component("converter", OxidizePdfConverter(mode="rag"))
83+
# ...add an embedder, a document writer, etc.
84+
85+
result = pipeline.run({"converter": {"sources": ["paper.pdf"]}})
86+
documents = result["converter"]["documents"]
87+
```
88+
89+
### ByteStream input
90+
91+
The converter accepts `ByteStream` objects natively (via oxidize-pdf's
92+
`PdfReader.from_bytes`), so PDFs that never touch disk — uploads, objects fetched from
93+
blob storage — can be ingested directly. `ByteStream.meta` is merged into each output
94+
`Document.meta`:
95+
96+
```python
97+
from haystack.dataclasses import ByteStream
98+
from haystack_oxidize_pdf import OxidizePdfConverter
99+
100+
with open("paper.pdf", "rb") as f:
101+
stream = ByteStream(
102+
data=f.read(),
103+
mime_type="application/pdf",
104+
meta={"upstream_origin": "s3://bucket/key"},
105+
)
106+
107+
docs = OxidizePdfConverter().run(sources=[stream])["documents"]
108+
# each doc.meta carries upstream_origin == "s3://bucket/key"
109+
```
110+
111+
### Batch sources with per-source metadata
112+
113+
`meta` may be a single dict (broadcast to every output document) or a list of dicts (one
114+
per source, lengths must match):
115+
116+
```python
117+
docs = OxidizePdfConverter(mode="markdown").run(
118+
sources=["doc-a.pdf", "doc-b.pdf"],
119+
meta=[{"tag": "first"}, {"tag": "second"}],
120+
)["documents"]
121+
# docs[0].meta["tag"] == "first"; docs[1].meta["tag"] == "second"
122+
```
123+
124+
Caller-supplied `meta` overrides base file-level fields (`file_path`, `file_name`,
125+
`total_pages`, `pdf_version`), but per-document fields (`chunk_index`, `page_numbers`,
126+
`page_number`) are applied last and are never overwritten.
127+
128+
## License
129+
130+
`haystack-oxidize-pdf` is distributed under the terms of the
131+
[MIT license](https://github.com/bzsanti/oxidize-pdf-integrations/blob/main/LICENSE).

0 commit comments

Comments
 (0)