docling.rs 0.32.0 → 0.37.0
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.
- package/README.md +76 -4
- package/deps.js +15 -0
- package/index.d.ts +88 -1
- package/index.js +177 -42
- package/native.d.ts +115 -0
- package/native.js +10 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -93,6 +93,62 @@ for await (const chunk of streamFileMarkdown('paper.pdf')) {
|
|
|
93
93
|
}
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
### Chunking (docling's chunkers, for RAG)
|
|
97
|
+
|
|
98
|
+
`chunkFile` / `chunk` / `chunkDocument` (each with an `…Async` variant) run
|
|
99
|
+
docling's chunkers over a converted document and return embedding-ready
|
|
100
|
+
records. The default is the structure-driven **hierarchical** chunker (one
|
|
101
|
+
chunk per document item — whole lists, triplet-serialized tables — with its
|
|
102
|
+
heading path); pass `chunker: 'hybrid'` to refine against a token budget
|
|
103
|
+
(split oversized chunks, merge undersized same-heading neighbours), matching
|
|
104
|
+
docling's `HybridChunker`. The hybrid token counts come from a HuggingFace
|
|
105
|
+
`tokenizer.json`: pass a path via `tokenizer`, or omit it to use
|
|
106
|
+
`models/chunk/tokenizer.json` (all-MiniLM-L6-v2's — fetched by
|
|
107
|
+
`scripts/download_dependencies.sh` alongside the ML models, resolved through
|
|
108
|
+
the same install-home logic).
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
import { chunkFileAsync, Pipeline, chunkDocumentAsync } from 'docling.rs'
|
|
112
|
+
|
|
113
|
+
const chunks = await chunkFileAsync('report.docx', {
|
|
114
|
+
chunker: 'hybrid',
|
|
115
|
+
tokenizer: 'tokenizer.json', // e.g. all-MiniLM-L6-v2's
|
|
116
|
+
maxTokens: 256,
|
|
117
|
+
})
|
|
118
|
+
for (const c of chunks) {
|
|
119
|
+
await embed(c.contextualized) // heading path + text, ready for the embedder
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Chunk something you already converted (no re-conversion), e.g. a PDF
|
|
123
|
+
// that went through the warm Pipeline:
|
|
124
|
+
const { content } = new Pipeline().convertFile('paper.pdf', { to: 'json' })
|
|
125
|
+
const pdfChunks = await chunkDocumentAsync(content)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Each `Chunk` is `{ text, headings?, docItems, contextualized }` — `docItems`
|
|
129
|
+
holds the source items' JSON-pointer refs (`"#/texts/12"`), `contextualized`
|
|
130
|
+
is docling's `contextualize()` rendering to feed the embedding model.
|
|
131
|
+
|
|
132
|
+
#### Streaming chunks
|
|
133
|
+
|
|
134
|
+
`streamFileChunks` / `streamChunks` / `streamDocumentChunks` are the streaming
|
|
135
|
+
counterparts: async generators that yield each chunk **as the chunkers produce
|
|
136
|
+
it** — the first chunk is ready for embedding while the rest of the document
|
|
137
|
+
is still being chunked, and no all-chunks array is materialized. Abandoning
|
|
138
|
+
the generator early (`break`) cancels the background chunking.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
import { streamFileChunks } from 'docling.rs'
|
|
142
|
+
|
|
143
|
+
for await (const c of streamFileChunks('report.docx', {
|
|
144
|
+
chunker: 'hybrid',
|
|
145
|
+
tokenizer: 'tokenizer.json',
|
|
146
|
+
maxTokens: 256,
|
|
147
|
+
})) {
|
|
148
|
+
await embed(c.contextualized) // embedding overlaps the remaining chunking
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
96
152
|
### PDF / images: getting the ML models
|
|
97
153
|
|
|
98
154
|
Declarative formats (Markdown, HTML, DOCX, XLSX, …) are pure Rust and need
|
|
@@ -159,12 +215,22 @@ import { Pipeline } from 'docling.rs'
|
|
|
159
215
|
|
|
160
216
|
const pipeline = new Pipeline({ strict: true })
|
|
161
217
|
for (const path of pdfPaths) {
|
|
162
|
-
const { content } = pipeline.
|
|
218
|
+
const { content } = await pipeline.convertFileAsync(path, { to: 'json' }) // warm models, off the event loop
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Or stream a PDF's Markdown as pages finish converting:
|
|
222
|
+
for await (const chunk of pipeline.streamFileMarkdown('paper.pdf')) {
|
|
223
|
+
process.stdout.write(chunk)
|
|
163
224
|
}
|
|
164
225
|
```
|
|
165
226
|
|
|
166
|
-
`Pipeline` handles `pdf` and `image` inputs (the ML pipeline)
|
|
167
|
-
|
|
227
|
+
`Pipeline` handles `pdf` and `image` inputs (the ML pipeline). The sync
|
|
228
|
+
`convertFile` / `convert` block the event loop; the `*Async` variants run on the
|
|
229
|
+
libuv thread pool, and `streamFileMarkdown` yields Markdown chunks in document
|
|
230
|
+
order as pages finish. Conversions on one instance run one at a time (the
|
|
231
|
+
models are mutable sessions) — overlapping `*Async` calls queue in submission
|
|
232
|
+
order, so batch throughput comes from keeping the models warm, not from
|
|
233
|
+
parallel calls.
|
|
168
234
|
|
|
169
235
|
### Images
|
|
170
236
|
|
|
@@ -194,12 +260,18 @@ JSON output always embeds extracted images as data URIs.
|
|
|
194
260
|
| `convertFileAsync(path, options?)` | `Promise<ConvertResult>` | Off the event loop. |
|
|
195
261
|
| `convertAsync(input, options?)` | `Promise<ConvertResult>` | Off the event loop. |
|
|
196
262
|
| `streamFileMarkdown(path, options?)` | `AsyncGenerator<string>` | Markdown chunks in document order. |
|
|
263
|
+
| `chunkFile(path, options?)` | `Chunk[]` | Convert + run docling's hierarchical/hybrid chunker. |
|
|
264
|
+
| `chunk(input, options?)` | `Chunk[]` | Same, over in-memory bytes. |
|
|
265
|
+
| `chunkDocument(documentJson, options?)` | `Chunk[]` | Chunk an already-converted docling JSON document. |
|
|
266
|
+
| `chunkFileAsync` / `chunkAsync` / `chunkDocumentAsync` | `Promise<Chunk[]>` | Off the event loop. |
|
|
267
|
+
| `streamFileChunks` / `streamChunks` / `streamDocumentChunks` | `AsyncGenerator<Chunk>` | Chunks yielded as produced; `break` cancels. |
|
|
197
268
|
| `supportedFormats()` | `string[]` | Supported input format ids. |
|
|
198
269
|
| `formatFromName(name)` | `string \| null` | Detect a format id from a filename/extension. |
|
|
199
270
|
| `checkDependencies(options?)` | `DependencyStatus` | Report which PDF/image deps are present. |
|
|
200
271
|
|
|
201
272
|
`Pipeline` is the reusable warm PDF/image converter: `new Pipeline(converterOptions)`
|
|
202
|
-
then `convertFile` / `convert
|
|
273
|
+
then `convertFile` / `convert` / `convertFileAsync` / `convertAsync` /
|
|
274
|
+
`convertFileStreaming` / `streamFileMarkdown`.
|
|
203
275
|
|
|
204
276
|
`DocumentConverter` is the reusable form: `new DocumentConverter(converterOptions)`
|
|
205
277
|
then `convert` / `convertFile` / `convertFileAsync` / `convertAsync` /
|
package/deps.js
CHANGED
|
@@ -92,9 +92,22 @@ function resolvePaths(dir) {
|
|
|
92
92
|
tfDecoder:
|
|
93
93
|
process.env.DOCLING_TABLEFORMER_DECODER || path.join(models, 'tableformer', 'decoder.onnx'),
|
|
94
94
|
tfBbox: process.env.DOCLING_TABLEFORMER_BBOX || path.join(models, 'tableformer', 'bbox.onnx'),
|
|
95
|
+
chunkTokenizer:
|
|
96
|
+
process.env.DOCLING_CHUNK_TOKENIZER || path.join(models, 'chunk', 'tokenizer.json'),
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
/**
|
|
101
|
+
* The hybrid chunker's default tokenizer (all-MiniLM-L6-v2's tokenizer.json,
|
|
102
|
+
* fetched by `scripts/download_dependencies.sh` into `models/chunk/`), resolved
|
|
103
|
+
* through the same install-home logic as the ML models. Returns `null` when not
|
|
104
|
+
* installed — the native side then reports a clear error with the download hint.
|
|
105
|
+
*/
|
|
106
|
+
function defaultChunkTokenizer(dir) {
|
|
107
|
+
const p = resolvePaths(dir)
|
|
108
|
+
return fs.existsSync(p.chunkTokenizer) ? p.chunkTokenizer : null
|
|
109
|
+
}
|
|
110
|
+
|
|
98
111
|
/**
|
|
99
112
|
* Report which dependencies are present on disk. `ready` is true when the
|
|
100
113
|
* minimum for PDF (pdfium + layout) is present.
|
|
@@ -108,6 +121,7 @@ function checkDependencies(options = {}) {
|
|
|
108
121
|
layout: has(p.layout),
|
|
109
122
|
ocr: has(p.ocrRec) && has(p.ocrDict),
|
|
110
123
|
tableformer: has(p.tfEncoder) && has(p.tfDecoder) && has(p.tfBbox),
|
|
124
|
+
chunkTokenizer: has(p.chunkTokenizer),
|
|
111
125
|
}
|
|
112
126
|
status.ready = status.pdfium && status.layout
|
|
113
127
|
status.missing = [
|
|
@@ -185,4 +199,5 @@ module.exports = {
|
|
|
185
199
|
assertMlReady,
|
|
186
200
|
resolvePaths,
|
|
187
201
|
exportEnv,
|
|
202
|
+
defaultChunkTokenizer,
|
|
188
203
|
}
|
package/index.d.ts
CHANGED
|
@@ -8,9 +8,19 @@ import type {
|
|
|
8
8
|
ConvertOptions,
|
|
9
9
|
ConvertInput,
|
|
10
10
|
ConvertResult,
|
|
11
|
+
ChunkOptions,
|
|
12
|
+
Chunk,
|
|
11
13
|
} from './native'
|
|
12
14
|
|
|
13
|
-
export type {
|
|
15
|
+
export type {
|
|
16
|
+
ConverterOptions,
|
|
17
|
+
OutputOptions,
|
|
18
|
+
ConvertOptions,
|
|
19
|
+
ConvertInput,
|
|
20
|
+
ConvertResult,
|
|
21
|
+
ChunkOptions,
|
|
22
|
+
Chunk,
|
|
23
|
+
}
|
|
14
24
|
|
|
15
25
|
// Format helpers pass straight through from the native binding.
|
|
16
26
|
export { supportedFormats, formatFromName } from './native'
|
|
@@ -27,6 +37,27 @@ export declare function convertFileAsync(path: string, options?: ConvertOptions
|
|
|
27
37
|
/** Async (Promise) bytes conversion, off the event loop. */
|
|
28
38
|
export declare function convertAsync(input: ConvertInput, options?: ConvertOptions | null): Promise<ConvertResult>
|
|
29
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Chunk a file with docling's chunkers: convert it, then run the hierarchical
|
|
42
|
+
* (default) or hybrid (`chunker: 'hybrid'` + `tokenizer`) chunker over the
|
|
43
|
+
* document. Throws for PDF/image/METS if deps aren't installed.
|
|
44
|
+
*/
|
|
45
|
+
export declare function chunkFile(path: string, options?: ChunkOptions | null): Array<Chunk>
|
|
46
|
+
/** Async (Promise) {@link chunkFile}; conversion + chunking run off the event loop. */
|
|
47
|
+
export declare function chunkFileAsync(path: string, options?: ChunkOptions | null): Promise<Array<Chunk>>
|
|
48
|
+
/** Chunk in-memory bytes (same input contract as {@link convert}). */
|
|
49
|
+
export declare function chunk(input: ConvertInput, options?: ChunkOptions | null): Array<Chunk>
|
|
50
|
+
/** Async (Promise) {@link chunk}. */
|
|
51
|
+
export declare function chunkAsync(input: ConvertInput, options?: ChunkOptions | null): Promise<Array<Chunk>>
|
|
52
|
+
/**
|
|
53
|
+
* Chunk an already-converted document, passed as docling-core JSON (the
|
|
54
|
+
* `content` of a `convert*` call with `to: 'json'`) — so a document converted
|
|
55
|
+
* once (e.g. through the warm PDF {@link Pipeline}) chunks without re-converting.
|
|
56
|
+
*/
|
|
57
|
+
export declare function chunkDocument(documentJson: string, options?: ChunkOptions | null): Array<Chunk>
|
|
58
|
+
/** Async (Promise) {@link chunkDocument}. */
|
|
59
|
+
export declare function chunkDocumentAsync(documentJson: string, options?: ChunkOptions | null): Promise<Array<Chunk>>
|
|
60
|
+
|
|
30
61
|
/** A reusable converter holding config (strict / fetchImages / allowedFormats). */
|
|
31
62
|
export declare class DocumentConverter {
|
|
32
63
|
constructor(options?: ConverterOptions | null)
|
|
@@ -37,15 +68,40 @@ export declare class DocumentConverter {
|
|
|
37
68
|
convertFileStreaming(path: string, callback: StreamCallback, options?: OutputOptions | null): void
|
|
38
69
|
}
|
|
39
70
|
|
|
71
|
+
/** Output options for {@link Pipeline.streamFileMarkdown} (streamable modes only). */
|
|
72
|
+
export interface PipelineStreamOptions {
|
|
73
|
+
imageMode?: 'placeholder' | 'embedded'
|
|
74
|
+
artifactsDir?: string
|
|
75
|
+
}
|
|
76
|
+
|
|
40
77
|
/**
|
|
41
78
|
* A reusable PDF/image pipeline that keeps the ONNX models loaded across calls.
|
|
42
79
|
* Use instead of the per-call functions when converting many PDFs/images — the
|
|
43
80
|
* one-shot path reloads every model each call. Handles `pdf` and `image` inputs.
|
|
81
|
+
*
|
|
82
|
+
* The `*Async` variants run the conversion off the event loop; overlapping
|
|
83
|
+
* calls on one instance queue (the models are mutable sessions), so batch
|
|
84
|
+
* throughput comes from keeping the models warm, not from parallel calls.
|
|
44
85
|
*/
|
|
45
86
|
export declare class Pipeline {
|
|
46
87
|
constructor(options?: ConverterOptions | null)
|
|
47
88
|
convertFile(path: string, options?: OutputOptions | null): ConvertResult
|
|
48
89
|
convert(input: ConvertInput, options?: OutputOptions | null): ConvertResult
|
|
90
|
+
/** Async (Promise) file conversion on the warm pipeline, off the event loop. */
|
|
91
|
+
convertFileAsync(path: string, options?: OutputOptions | null): Promise<ConvertResult>
|
|
92
|
+
/** Async (Promise) bytes conversion on the warm pipeline, off the event loop. */
|
|
93
|
+
convertAsync(input: ConvertInput, options?: OutputOptions | null): Promise<ConvertResult>
|
|
94
|
+
/** Callback-form streaming (prefer {@link Pipeline.streamFileMarkdown}). */
|
|
95
|
+
convertFileStreaming(path: string, callback: StreamCallback, options?: OutputOptions | null): void
|
|
96
|
+
/**
|
|
97
|
+
* Stream a PDF's Markdown in chunks through the warm pipeline, in document
|
|
98
|
+
* order, as pages finish converting (an image arrives as a single chunk).
|
|
99
|
+
* Concatenating the chunks reproduces the buffered Markdown byte-for-byte.
|
|
100
|
+
*/
|
|
101
|
+
streamFileMarkdown(
|
|
102
|
+
filePath: string,
|
|
103
|
+
options?: PipelineStreamOptions,
|
|
104
|
+
): AsyncGenerator<string, void, unknown>
|
|
49
105
|
}
|
|
50
106
|
|
|
51
107
|
// --- dependency provisioning (PDF/image ML pipeline) -----------------------
|
|
@@ -62,6 +118,8 @@ export interface DependencyStatus {
|
|
|
62
118
|
ocr: boolean
|
|
63
119
|
/** TableFormer encoder/decoder/bbox present. */
|
|
64
120
|
tableformer: boolean
|
|
121
|
+
/** Hybrid-chunker tokenizer (models/chunk/tokenizer.json) present. */
|
|
122
|
+
chunkTokenizer: boolean
|
|
65
123
|
/** True when the minimum for PDF (pdfium + layout) is present. */
|
|
66
124
|
ready: boolean
|
|
67
125
|
/** Human-readable list of the missing required assets. */
|
|
@@ -95,3 +153,32 @@ export declare function streamFileMarkdown(
|
|
|
95
153
|
filePath: string,
|
|
96
154
|
options?: StreamOptions,
|
|
97
155
|
): AsyncGenerator<string, void, unknown>
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Stream a file's chunks as the chunkers produce them — the streaming
|
|
159
|
+
* counterpart of {@link chunkFile}. The first chunk is ready (e.g. for
|
|
160
|
+
* embedding) while the rest of the document is still being chunked, and no
|
|
161
|
+
* all-chunks array is materialized. Abandoning the generator early (`break`)
|
|
162
|
+
* cancels the background chunking. Throws for PDF/image/METS if deps aren't
|
|
163
|
+
* installed.
|
|
164
|
+
*/
|
|
165
|
+
export declare function streamFileChunks(
|
|
166
|
+
filePath: string,
|
|
167
|
+
options?: ChunkOptions | null,
|
|
168
|
+
): AsyncGenerator<Chunk, void, unknown>
|
|
169
|
+
|
|
170
|
+
/** Streaming counterpart of {@link chunk} (same contract as {@link streamFileChunks}). */
|
|
171
|
+
export declare function streamChunks(
|
|
172
|
+
input: ConvertInput,
|
|
173
|
+
options?: ChunkOptions | null,
|
|
174
|
+
): AsyncGenerator<Chunk, void, unknown>
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Streaming counterpart of {@link chunkDocument}: chunk an already-converted
|
|
178
|
+
* docling-core JSON document (same contract as {@link streamFileChunks}).
|
|
179
|
+
* Touches no ML models.
|
|
180
|
+
*/
|
|
181
|
+
export declare function streamDocumentChunks(
|
|
182
|
+
documentJson: string,
|
|
183
|
+
options?: ChunkOptions | null,
|
|
184
|
+
): AsyncGenerator<Chunk, void, unknown>
|
package/index.js
CHANGED
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
// 1. dependency guards — converting a PDF/image/METS input throws a clear
|
|
6
6
|
// error unless the ML models + pdfium are on disk (see
|
|
7
7
|
// scripts/download_dependencies.sh);
|
|
8
|
-
// 2.
|
|
8
|
+
// 2. `streamFileMarkdown` async generators over Markdown chunks (module-level
|
|
9
|
+
// and on the warm `Pipeline`).
|
|
9
10
|
//
|
|
10
11
|
// Works in Node.js and Bun (Bun implements N-API).
|
|
11
12
|
|
|
12
13
|
'use strict'
|
|
13
14
|
|
|
14
15
|
const native = require('./native.js')
|
|
15
|
-
const { checkDependencies, assertMlReady } = require('./deps.js')
|
|
16
|
+
const { checkDependencies, assertMlReady, defaultChunkTokenizer } = require('./deps.js')
|
|
16
17
|
|
|
17
18
|
// Resolve the format id of an input for the ML guard. Uses the native
|
|
18
19
|
// extension→format map; falls back to an explicitly-passed format string.
|
|
@@ -23,6 +24,48 @@ function mlFormatOf(name, format) {
|
|
|
23
24
|
return native.formatFromName(name || '') || ''
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
// Bridge a native (err, chunk) streaming callback into an async generator.
|
|
28
|
+
// `start` receives the callback and kicks off the native conversion. Chunks
|
|
29
|
+
// are delivered on the event loop (via a threadsafe function); a null chunk
|
|
30
|
+
// ends the stream, a non-null err ends it with a throw.
|
|
31
|
+
async function* chunkStream(start) {
|
|
32
|
+
const queue = []
|
|
33
|
+
let done = false
|
|
34
|
+
let failure = null
|
|
35
|
+
let notify = null
|
|
36
|
+
const wake = () => {
|
|
37
|
+
if (notify) {
|
|
38
|
+
const n = notify
|
|
39
|
+
notify = null
|
|
40
|
+
n()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
start((err, chunk) => {
|
|
45
|
+
if (err) {
|
|
46
|
+
failure = err
|
|
47
|
+
done = true
|
|
48
|
+
} else if (chunk === null || chunk === undefined) {
|
|
49
|
+
done = true
|
|
50
|
+
} else {
|
|
51
|
+
queue.push(chunk)
|
|
52
|
+
}
|
|
53
|
+
wake()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
while (true) {
|
|
57
|
+
if (queue.length > 0) {
|
|
58
|
+
yield queue.shift()
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
if (failure) throw failure
|
|
62
|
+
if (done) return
|
|
63
|
+
await new Promise((resolve) => {
|
|
64
|
+
notify = resolve
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
26
69
|
// --- guarded one-shot functions --------------------------------------------
|
|
27
70
|
|
|
28
71
|
function convertFile(path, options) {
|
|
@@ -46,6 +89,48 @@ async function convertAsync(input, options) {
|
|
|
46
89
|
return native.convertAsync(input, options)
|
|
47
90
|
}
|
|
48
91
|
|
|
92
|
+
// --- guarded chunking functions ---------------------------------------------
|
|
93
|
+
|
|
94
|
+
// For the hybrid chunker with no explicit tokenizer, resolve the default one
|
|
95
|
+
// (models/chunk/tokenizer.json) through the same install-home logic as the ML
|
|
96
|
+
// models — so DOCLING_RS_HOME / ~/.cache installs work, not only ./models. The
|
|
97
|
+
// native side keeps its own CWD-relative fallback as a backstop.
|
|
98
|
+
function withDefaultTokenizer(options) {
|
|
99
|
+
if (!options || options.tokenizer) return options
|
|
100
|
+
if (String(options.chunker || '').toLowerCase() !== 'hybrid') return options
|
|
101
|
+
const tokenizer = defaultChunkTokenizer()
|
|
102
|
+
return tokenizer ? { ...options, tokenizer } : options
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function chunkFile(path, options) {
|
|
106
|
+
assertMlReady(mlFormatOf(path))
|
|
107
|
+
return native.chunkFile(path, withDefaultTokenizer(options))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function chunk(input, options) {
|
|
111
|
+
assertMlReady(mlFormatOf(input && input.name, input && input.format))
|
|
112
|
+
return native.chunk(input, withDefaultTokenizer(options))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// async so a guard failure surfaces as a rejected promise, not a sync throw.
|
|
116
|
+
async function chunkFileAsync(path, options) {
|
|
117
|
+
assertMlReady(mlFormatOf(path))
|
|
118
|
+
return native.chunkFileAsync(path, withDefaultTokenizer(options))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function chunkAsync(input, options) {
|
|
122
|
+
assertMlReady(mlFormatOf(input && input.name, input && input.format))
|
|
123
|
+
return native.chunkAsync(input, withDefaultTokenizer(options))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function chunkDocument(documentJson, options) {
|
|
127
|
+
return native.chunkDocument(documentJson, withDefaultTokenizer(options))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function chunkDocumentAsync(documentJson, options) {
|
|
131
|
+
return native.chunkDocumentAsync(documentJson, withDefaultTokenizer(options))
|
|
132
|
+
}
|
|
133
|
+
|
|
49
134
|
// --- guarded classes --------------------------------------------------------
|
|
50
135
|
|
|
51
136
|
class DocumentConverter {
|
|
@@ -94,6 +179,41 @@ class Pipeline {
|
|
|
94
179
|
assertMlReady(mlFormatOf(input && input.name, input && input.format))
|
|
95
180
|
return this._inner.convert(input, options)
|
|
96
181
|
}
|
|
182
|
+
|
|
183
|
+
// async so a guard failure surfaces as a rejected promise, not a sync throw.
|
|
184
|
+
async convertFileAsync(path, options) {
|
|
185
|
+
assertMlReady(mlFormatOf(path))
|
|
186
|
+
return this._inner.convertFileAsync(path, options)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async convertAsync(input, options) {
|
|
190
|
+
assertMlReady(mlFormatOf(input && input.name, input && input.format))
|
|
191
|
+
return this._inner.convertAsync(input, options)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
convertFileStreaming(path, callback, options) {
|
|
195
|
+
assertMlReady(mlFormatOf(path))
|
|
196
|
+
return this._inner.convertFileStreaming(path, callback, options)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Stream a PDF's Markdown in chunks through the warm pipeline, in document
|
|
201
|
+
* order, as pages finish converting (an image arrives as a single chunk).
|
|
202
|
+
* Same contract as the module-level `streamFileMarkdown`, but reusing this
|
|
203
|
+
* instance's loaded models — no per-call model reload.
|
|
204
|
+
*
|
|
205
|
+
* @param {string} filePath
|
|
206
|
+
* @param {object} [options] output options (`imageMode`: `placeholder` or
|
|
207
|
+
* `embedded`; `referenced` is rejected)
|
|
208
|
+
* @returns {AsyncGenerator<string, void, unknown>}
|
|
209
|
+
*/
|
|
210
|
+
async *streamFileMarkdown(filePath, options = {}) {
|
|
211
|
+
assertMlReady(mlFormatOf(filePath))
|
|
212
|
+
const { imageMode, artifactsDir } = options
|
|
213
|
+
yield* chunkStream((callback) =>
|
|
214
|
+
this._inner.convertFileStreaming(filePath, callback, { imageMode, artifactsDir }),
|
|
215
|
+
)
|
|
216
|
+
}
|
|
97
217
|
}
|
|
98
218
|
|
|
99
219
|
// --- streaming --------------------------------------------------------------
|
|
@@ -113,49 +233,54 @@ async function* streamFileMarkdown(filePath, options = {}) {
|
|
|
113
233
|
assertMlReady(mlFormatOf(filePath))
|
|
114
234
|
const { strict, fetchImages, allowedFormats, imageMode, artifactsDir } = options
|
|
115
235
|
const converter = new native.DocumentConverter({ strict, fetchImages, allowedFormats })
|
|
236
|
+
yield* chunkStream((callback) =>
|
|
237
|
+
converter.convertFileStreaming(filePath, callback, { imageMode, artifactsDir }),
|
|
238
|
+
)
|
|
239
|
+
}
|
|
116
240
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
241
|
+
/**
|
|
242
|
+
* Stream a file's chunks as the chunkers produce them — the streaming
|
|
243
|
+
* counterpart of `chunkFile`. The first chunk is ready (e.g. for embedding)
|
|
244
|
+
* while the rest of the document is still being chunked, and no all-chunks
|
|
245
|
+
* array is materialized. Abandoning the generator early (`break`) cancels the
|
|
246
|
+
* background chunking.
|
|
247
|
+
*
|
|
248
|
+
* @param {string} filePath
|
|
249
|
+
* @param {object} [options] same `ChunkOptions` as `chunkFile`
|
|
250
|
+
* @returns {AsyncGenerator<Chunk, void, unknown>}
|
|
251
|
+
*/
|
|
252
|
+
async function* streamFileChunks(filePath, options = {}) {
|
|
253
|
+
assertMlReady(mlFormatOf(filePath))
|
|
254
|
+
const opts = withDefaultTokenizer(options)
|
|
255
|
+
yield* chunkStream((callback) => native.chunkFileStreaming(filePath, callback, opts))
|
|
256
|
+
}
|
|
131
257
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
{ imageMode, artifactsDir },
|
|
146
|
-
)
|
|
258
|
+
/**
|
|
259
|
+
* Streaming counterpart of `chunk`: chunk in-memory bytes, yielding each chunk
|
|
260
|
+
* as it is produced (same contract as {@link streamFileChunks}).
|
|
261
|
+
*
|
|
262
|
+
* @param {object} input same `ConvertInput` as `chunk`
|
|
263
|
+
* @param {object} [options] same `ChunkOptions` as `chunk`
|
|
264
|
+
* @returns {AsyncGenerator<Chunk, void, unknown>}
|
|
265
|
+
*/
|
|
266
|
+
async function* streamChunks(input, options = {}) {
|
|
267
|
+
assertMlReady(mlFormatOf(input && input.name, input && input.format))
|
|
268
|
+
const opts = withDefaultTokenizer(options)
|
|
269
|
+
yield* chunkStream((callback) => native.chunkStreaming(input, callback, opts))
|
|
270
|
+
}
|
|
147
271
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Streaming counterpart of `chunkDocument`: chunk an already-converted
|
|
274
|
+
* docling-core JSON document, yielding each chunk as it is produced (same
|
|
275
|
+
* contract as {@link streamFileChunks}). Touches no ML models — unguarded.
|
|
276
|
+
*
|
|
277
|
+
* @param {string} documentJson
|
|
278
|
+
* @param {object} [options] same `ChunkOptions` as `chunkDocument`
|
|
279
|
+
* @returns {AsyncGenerator<Chunk, void, unknown>}
|
|
280
|
+
*/
|
|
281
|
+
async function* streamDocumentChunks(documentJson, options = {}) {
|
|
282
|
+
const opts = withDefaultTokenizer(options)
|
|
283
|
+
yield* chunkStream((callback) => native.chunkDocumentStreaming(documentJson, callback, opts))
|
|
159
284
|
}
|
|
160
285
|
|
|
161
286
|
// --- exports (explicit, so ESM named imports work in Node and Bun) ----------
|
|
@@ -164,9 +289,19 @@ module.exports.convert = convert
|
|
|
164
289
|
module.exports.convertFile = convertFile
|
|
165
290
|
module.exports.convertAsync = convertAsync
|
|
166
291
|
module.exports.convertFileAsync = convertFileAsync
|
|
292
|
+
module.exports.chunk = chunk
|
|
293
|
+
module.exports.chunkFile = chunkFile
|
|
294
|
+
module.exports.chunkAsync = chunkAsync
|
|
295
|
+
module.exports.chunkFileAsync = chunkFileAsync
|
|
296
|
+
// Chunking an already-converted JSON document touches no ML models — unguarded.
|
|
297
|
+
module.exports.chunkDocument = chunkDocument
|
|
298
|
+
module.exports.chunkDocumentAsync = chunkDocumentAsync
|
|
167
299
|
module.exports.DocumentConverter = DocumentConverter
|
|
168
300
|
module.exports.Pipeline = Pipeline
|
|
169
301
|
module.exports.streamFileMarkdown = streamFileMarkdown
|
|
302
|
+
module.exports.streamFileChunks = streamFileChunks
|
|
303
|
+
module.exports.streamChunks = streamChunks
|
|
304
|
+
module.exports.streamDocumentChunks = streamDocumentChunks
|
|
170
305
|
module.exports.checkDependencies = checkDependencies
|
|
171
306
|
module.exports.supportedFormats = native.supportedFormats
|
|
172
307
|
module.exports.formatFromName = native.formatFromName
|
package/native.d.ts
CHANGED
|
@@ -98,6 +98,100 @@ export declare function convert(input: ConvertInput, options?: ConvertOptions |
|
|
|
98
98
|
export declare function convertFileAsync(path: string, options?: ConvertOptions | undefined | null): Promise<ConvertResult>
|
|
99
99
|
/** Async (Promise-returning) [`convert`]. */
|
|
100
100
|
export declare function convertAsync(input: ConvertInput, options?: ConvertOptions | undefined | null): Promise<ConvertResult>
|
|
101
|
+
/** Options for the chunk* functions. */
|
|
102
|
+
export interface ChunkOptions {
|
|
103
|
+
/**
|
|
104
|
+
* `"hierarchical"` (default): one chunk per document item, docling's
|
|
105
|
+
* structure-driven chunker. `"hybrid"`: tokenization-aware refinement —
|
|
106
|
+
* splits oversized chunks and merges undersized same-heading neighbours;
|
|
107
|
+
* requires `tokenizer`.
|
|
108
|
+
*/
|
|
109
|
+
chunker?: string
|
|
110
|
+
/**
|
|
111
|
+
* Path to a HuggingFace `tokenizer.json` (e.g. all-MiniLM-L6-v2's) for the
|
|
112
|
+
* hybrid chunker's token counts. When omitted, falls back to
|
|
113
|
+
* `models/chunk/tokenizer.json` (populated by
|
|
114
|
+
* `scripts/install/download_dependencies.sh`).
|
|
115
|
+
*/
|
|
116
|
+
tokenizer?: string
|
|
117
|
+
/**
|
|
118
|
+
* The hybrid chunker's token budget per chunk. Default `256` (docling's
|
|
119
|
+
* default for the MiniLM embedding model).
|
|
120
|
+
*/
|
|
121
|
+
maxTokens?: number
|
|
122
|
+
/**
|
|
123
|
+
* Merge undersized peer chunks with the same headings (hybrid only).
|
|
124
|
+
* Default `true`, matching docling.
|
|
125
|
+
*/
|
|
126
|
+
mergePeers?: boolean
|
|
127
|
+
}
|
|
128
|
+
/** One chunk record — the analogue of docling's `DocChunk`. */
|
|
129
|
+
export interface Chunk {
|
|
130
|
+
/** The chunk body (markdown-flavoured text, same as docling's `DocChunk.text`). */
|
|
131
|
+
text: string
|
|
132
|
+
/**
|
|
133
|
+
* The heading path above the chunk, outermost first; absent for content
|
|
134
|
+
* above any heading.
|
|
135
|
+
*/
|
|
136
|
+
headings?: Array<string>
|
|
137
|
+
/**
|
|
138
|
+
* JSON-pointer refs of the document items the chunk was built from
|
|
139
|
+
* (`"#/texts/12"`, `"#/tables/0"`, …).
|
|
140
|
+
*/
|
|
141
|
+
docItems: Array<string>
|
|
142
|
+
/**
|
|
143
|
+
* The embedding-ready rendering: heading path + text, newline-joined
|
|
144
|
+
* (docling's `chunker.contextualize(chunk)`).
|
|
145
|
+
*/
|
|
146
|
+
contextualized: string
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Chunk a file on disk with docling's chunkers: convert it, then run the
|
|
150
|
+
* hierarchical (default) or hybrid chunker over the document.
|
|
151
|
+
*/
|
|
152
|
+
export declare function chunkFile(path: string, options?: ChunkOptions | undefined | null): Array<Chunk>
|
|
153
|
+
/**
|
|
154
|
+
* Async (Promise-returning) [`chunk_file`]; conversion + chunking run on the
|
|
155
|
+
* libuv thread pool.
|
|
156
|
+
*/
|
|
157
|
+
export declare function chunkFileAsync(path: string, options?: ChunkOptions | undefined | null): Promise<Array<Chunk>>
|
|
158
|
+
/** Chunk in-memory bytes (same contract as [`convert`], then chunk). */
|
|
159
|
+
export declare function chunk(input: ConvertInput, options?: ChunkOptions | undefined | null): Array<Chunk>
|
|
160
|
+
/** Async (Promise-returning) [`chunk`]. */
|
|
161
|
+
export declare function chunkAsync(input: ConvertInput, options?: ChunkOptions | undefined | null): Promise<Array<Chunk>>
|
|
162
|
+
/**
|
|
163
|
+
* Chunk an already-converted document, passed as docling-core JSON (the
|
|
164
|
+
* `content` of a `convert*` call with `to: "json"`) — so a document converted
|
|
165
|
+
* once (e.g. through the warm PDF `Pipeline`) can be chunked without
|
|
166
|
+
* re-converting.
|
|
167
|
+
*/
|
|
168
|
+
export declare function chunkDocument(documentJson: string, options?: ChunkOptions | undefined | null): Array<Chunk>
|
|
169
|
+
/** Async (Promise-returning) [`chunk_document`]. */
|
|
170
|
+
export declare function chunkDocumentAsync(documentJson: string, options?: ChunkOptions | undefined | null): Promise<Array<Chunk>>
|
|
171
|
+
/**
|
|
172
|
+
* Chunk a file and stream each chunk as the chunkers produce it — no
|
|
173
|
+
* all-chunks array is materialized, and the first chunk reaches JS while the
|
|
174
|
+
* rest of the document is still being chunked.
|
|
175
|
+
*
|
|
176
|
+
* `callback` is invoked as `(err, chunk)`: once per chunk with `chunk` a
|
|
177
|
+
* `Chunk`, once with `chunk === null` at the end, or once with a non-null
|
|
178
|
+
* `err` on failure. Prefer the `streamFileChunks` async-generator wrapper in
|
|
179
|
+
* JS over calling this directly.
|
|
180
|
+
*/
|
|
181
|
+
export declare function chunkFileStreaming(path: string, callback: (err: Error | null, arg: Chunk | undefined | null) => any, options?: ChunkOptions | undefined | null): void
|
|
182
|
+
/**
|
|
183
|
+
* Streaming [`chunk`]: chunk in-memory bytes, pushing each chunk through the
|
|
184
|
+
* callback (same contract as [`chunk_file_streaming`]). Prefer the
|
|
185
|
+
* `streamChunks` async-generator wrapper in JS.
|
|
186
|
+
*/
|
|
187
|
+
export declare function chunkStreaming(input: ConvertInput, callback: (err: Error | null, arg: Chunk | undefined | null) => any, options?: ChunkOptions | undefined | null): void
|
|
188
|
+
/**
|
|
189
|
+
* Streaming [`chunk_document`]: chunk an already-converted docling-core JSON
|
|
190
|
+
* document, pushing each chunk through the callback (same contract as
|
|
191
|
+
* [`chunk_file_streaming`]). Prefer the `streamDocumentChunks`
|
|
192
|
+
* async-generator wrapper in JS.
|
|
193
|
+
*/
|
|
194
|
+
export declare function chunkDocumentStreaming(documentJson: string, callback: (err: Error | null, arg: Chunk | undefined | null) => any, options?: ChunkOptions | undefined | null): void
|
|
101
195
|
/** The list of supported input format ids. */
|
|
102
196
|
export declare function supportedFormats(): Array<string>
|
|
103
197
|
/**
|
|
@@ -154,4 +248,25 @@ export declare class Pipeline {
|
|
|
154
248
|
convertFile(path: string, options?: OutputOptions | undefined | null): ConvertResult
|
|
155
249
|
/** Convert PDF or image bytes, reusing the warm models. */
|
|
156
250
|
convert(input: ConvertInput, options?: OutputOptions | undefined | null): ConvertResult
|
|
251
|
+
/**
|
|
252
|
+
* Async (Promise-returning) file conversion on the warm pipeline. The
|
|
253
|
+
* CPU-bound work runs on the libuv thread pool, keeping the event loop
|
|
254
|
+
* free; calls on the same instance run one at a time (the models are
|
|
255
|
+
* mutable sessions), so overlapping Promises queue in submission order.
|
|
256
|
+
*/
|
|
257
|
+
convertFileAsync(path: string, options?: OutputOptions | undefined | null): Promise<ConvertResult>
|
|
258
|
+
/** Async (Promise-returning) bytes conversion on the warm pipeline. */
|
|
259
|
+
convertAsync(input: ConvertInput, options?: OutputOptions | undefined | null): Promise<ConvertResult>
|
|
260
|
+
/**
|
|
261
|
+
* Stream a PDF's Markdown in chunks through the warm pipeline, in document
|
|
262
|
+
* order, as pages finish converting (an image converts in one step and
|
|
263
|
+
* arrives as a single chunk).
|
|
264
|
+
*
|
|
265
|
+
* `callback` is invoked as `(err, chunk)`: once per Markdown chunk with
|
|
266
|
+
* `chunk` a string, once with `chunk === null` at the end, or once with a
|
|
267
|
+
* non-null `err` on failure. Only `placeholder` / `embedded` image modes
|
|
268
|
+
* stream; `referenced` is rejected. Prefer the `streamFileMarkdown`
|
|
269
|
+
* async-generator wrapper in JS over calling this directly.
|
|
270
|
+
*/
|
|
271
|
+
convertFileStreaming(path: string, callback: (err: Error | null, arg: string | undefined | null) => any, options?: OutputOptions | undefined | null): void
|
|
157
272
|
}
|
package/native.js
CHANGED
|
@@ -310,7 +310,7 @@ if (!nativeBinding) {
|
|
|
310
310
|
throw new Error(`Failed to load native binding`)
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
const { convertFile, convert, convertFileAsync, convertAsync, DocumentConverter, Pipeline, supportedFormats, formatFromName } = nativeBinding
|
|
313
|
+
const { convertFile, convert, convertFileAsync, convertAsync, DocumentConverter, Pipeline, chunkFile, chunkFileAsync, chunk, chunkAsync, chunkDocument, chunkDocumentAsync, chunkFileStreaming, chunkStreaming, chunkDocumentStreaming, supportedFormats, formatFromName } = nativeBinding
|
|
314
314
|
|
|
315
315
|
module.exports.convertFile = convertFile
|
|
316
316
|
module.exports.convert = convert
|
|
@@ -318,5 +318,14 @@ module.exports.convertFileAsync = convertFileAsync
|
|
|
318
318
|
module.exports.convertAsync = convertAsync
|
|
319
319
|
module.exports.DocumentConverter = DocumentConverter
|
|
320
320
|
module.exports.Pipeline = Pipeline
|
|
321
|
+
module.exports.chunkFile = chunkFile
|
|
322
|
+
module.exports.chunkFileAsync = chunkFileAsync
|
|
323
|
+
module.exports.chunk = chunk
|
|
324
|
+
module.exports.chunkAsync = chunkAsync
|
|
325
|
+
module.exports.chunkDocument = chunkDocument
|
|
326
|
+
module.exports.chunkDocumentAsync = chunkDocumentAsync
|
|
327
|
+
module.exports.chunkFileStreaming = chunkFileStreaming
|
|
328
|
+
module.exports.chunkStreaming = chunkStreaming
|
|
329
|
+
module.exports.chunkDocumentStreaming = chunkDocumentStreaming
|
|
321
330
|
module.exports.supportedFormats = supportedFormats
|
|
322
331
|
module.exports.formatFromName = formatFromName
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docling.rs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Node.js / Bun bindings for docling.rs — a Rust port of docling. Convert Markdown, HTML, DOCX, PPTX, XLSX, PDF, images and more into a unified DoclingDocument (Markdown or docling-core JSON).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docling",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"@napi-rs/cli": "^2.18.4"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
|
-
"docling.rs-linux-x64-gnu": "0.
|
|
68
|
-
"docling.rs-linux-arm64-gnu": "0.
|
|
69
|
-
"docling.rs-win32-x64-msvc": "0.
|
|
67
|
+
"docling.rs-linux-x64-gnu": "0.37.0",
|
|
68
|
+
"docling.rs-linux-arm64-gnu": "0.37.0",
|
|
69
|
+
"docling.rs-win32-x64-msvc": "0.37.0"
|
|
70
70
|
}
|
|
71
71
|
}
|