electblake commited on
Commit
9f51ad0
·
1 Parent(s): 2c91fea

Refactor application implementation

Browse files
.agents/references/nuextract3/2026-07-20/README.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # nuextract3
2
+
3
+ - Topic: `nuextract3`
4
+ - Capture Date: `2026-07-20`
5
+ - Scope: TODO
.agents/references/nuextract3/2026-07-20/REFERENCE_INDEX.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Reference Index
2
+
3
+ - Bundle Path: `S:\Spaces\Data-Extraction\NuMarkApp\.agents\references\nuextract3\2026-07-20`
4
+ - Mirrored References: `1`
5
+ - Fixtures: `1`
6
+
7
+ - `README.md`: bundle scope
8
+ - `SOURCE_URLS.md`: verified sources
9
+ - `REFERENCE_SUMMARY.md`: findings and version notes
10
+ - `fixtures/`: user-provided artifacts
11
+ - `<domain>/`: mirrored markdown references by domain
12
+
13
+ ## References
14
+
15
+ ### huggingface.co
16
+
17
+ - `huggingface.co/numind/NuExtract3.md`: `https://huggingface.co/numind/NuExtract3`
18
+
19
+ ## Fixtures
20
+
21
+ - `fixtures/user-drop-001.txt`
22
+
.agents/references/nuextract3/2026-07-20/REFERENCE_SUMMARY.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NuExtract3 Reference Summary
2
+
3
+ - Reference date: 2026-07-20
4
+ - Official model: `numind/NuExtract3`
5
+ - Library: Transformers
6
+ - Base model: Qwen/Qwen3.5-4B
7
+ - License: Apache-2.0
8
+
9
+ NuExtract3 is a 4B vision-language reasoning model for structured extraction and document-to-Markdown conversion. It accepts text, images, or both.
10
+
11
+ For direct Transformers inference, the official README loads `AutoProcessor` and `AutoModelForImageTextToText`, then passes `template`, `mode`, and `enable_thinking` through `processor.apply_chat_template`. Non-thinking structured extraction uses `enable_thinking=False`; the documented starting temperature is 0.2.
12
+
13
+ Runtime-specific note: this project uses Transformers 5.14.1, Python 3.12, BF16, automatic device placement, and FlashAttention 2.
14
+
15
+ - Reference Date: `2026-07-20`
16
+ - Official Version: TODO
17
+ - Key Findings:
18
+ - TODO
19
+ - Compatibility Notes:
20
+ - TODO
.agents/references/nuextract3/2026-07-20/SOURCE_URLS.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Source URLs
2
+
3
+ - Bundle: `2026-07-20`
4
+ - Generated: `2026-07-20T17:29:26.022745+00:00`
5
+ - Mirrored References: `1`
6
+
7
+ ## huggingface.co
8
+
9
+ - `https://huggingface.co/numind/NuExtract3` -> `huggingface.co/numind/NuExtract3.md`
10
+
.agents/references/nuextract3/2026-07-20/fixtures/user-drop-001.txt ADDED
@@ -0,0 +1,942 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ license_link: https://huggingface.co/numind/NuExtract3/blob/main/LICENSE
4
+ library_name: transformers
5
+ pipeline_tag: image-to-text
6
+ tags:
7
+ - image-text-to-text
8
+ - transformers
9
+ - safetensors
10
+ - qwen3_5
11
+ - vision-language
12
+ - vlm
13
+ - document-understanding
14
+ - structured-extraction
15
+ - information-extraction
16
+ - ocr
17
+ - document-to-markdown
18
+ - markdown
19
+ - rag
20
+ - reasoning
21
+ - multilingual
22
+ - conversational
23
+ base_model:
24
+ - Qwen/Qwen3.5-4B
25
+ model_name: NuExtract3
26
+ ---
27
+
28
+ <p align="center">
29
+ <a href="https://nuextract.ai/">
30
+ <img src="header.svg" width="900px"/>
31
+ </a>
32
+ </p>
33
+
34
+
35
+ <p align="center">
36
+ 🖥️ <a href="https://nuextract.ai/">API / Platform</a>&nbsp;&nbsp; | &nbsp;&nbsp;
37
+ 📑 <a href="https://numind.ai/blog">Blog</a>&nbsp;&nbsp; | &nbsp;&nbsp;
38
+ 🗣️ <a href="https://discord.gg/3tsEtJNCDe">Discord</a>&nbsp;&nbsp; | &nbsp;&nbsp;
39
+ 🛠️ <a href="https://github.com/numindai/nuextract">GitHub</a>
40
+ </p>
41
+
42
+ **NuExtract3** is a unified **4B** vision-language reasoning model for document understanding.
43
+
44
+ It combines strong **structured information extraction** with high-quality **image-to-Markdown** conversion, making it suitable for extraction pipelines, OCR, and RAG preprocessing for all types of documents such as scans, receipts, forms, invoices, contracts or tables.
45
+
46
+ Try it out in [the 🤗 space!](https://huggingface.co/spaces/numind/NuExtract-3-4B)
47
+
48
+ ## Overview
49
+
50
+ - **Structured extraction**: input (text/images) + JSON template + instructions --> JSON output
51
+ - **Markdown conversion**: input (text/images) --> Markdown
52
+ - **Multimodal inputs**: text, images, or text + images.
53
+ - **Multilingual** documents.
54
+ - **Reasoning** and non-reasoning inference modes.
55
+ - **Template generation** for structured extraction from natural language or input document.
56
+
57
+ # Benchmark results
58
+
59
+ ## Structured Extraction
60
+
61
+ We benchmarked NuExtract on NuMind's internal structured benchmark, measuring model's performances on ~600 documents of diverse types including invoices, movie posters or floor plans. These documents and their ground-truth cover diverse use-cases testing model visual understanding, OCR, reasoning and ability to handle long input and output contexts.
62
+ We plan to open-source this benchmark in the coming weeks, along with a extensive leaderboard including most popular open-weight and closed-sourced APIs and a Python library allowing to easily measure model performances on structured extraction.
63
+
64
+ <img src="st.svg" width="1000"/>
65
+
66
+ To measure a pair of predicted and ground-truth JSONs, we represent both as trees which we align based on node names, compute metric scores for aligned leaves and report the average of these scores. `string` and `verbatim-string` leaves are evaluated with indel distance (i.e. Levenshtein without replacement), while all others are evaluated with exact-match.
67
+ Models were evaluated using vllm, with a temperature of 0.25 and a maximum of 65000 output token (for both thinking and answer), which largely exceeds 22000 which is the number of tokens of the largest ground truth output.
68
+
69
+ <figure>
70
+
71
+ |Model name |Average score|Num. failed⁽¹⁾|Avg. num tokens thinking|Avg. num tokens answer|
72
+ |--------------------|-------------|-----------|------------------------|----------------------|
73
+ |NuExtract3.4_4B-RL |**0.651 ± 0.019**|27 |2036 |1856 |
74
+ |gemma-4-E4B-it |0.538 ± 0.023|31 |3005 |1287 |
75
+ |Qwen3.5-9B |0.479 ± 0.030|170 |22409 |1257 |
76
+ |Qwen3.5-4B |0.417 ± 0.031|229 |27177 |1201 |
77
+ |GLM-4.6V-Flash |0.435 ± 0.026|153 |2989 |1357 |
78
+ |Nemotron-3-Nano-Omni|0.387 ± 0.028|204 |25827 |522 |
79
+ |Ministral-3-3B |0.240 ± 0.022|344 |27586 |362 |
80
+
81
+ <figcaption>
82
+ <small>
83
+ (1) number of model outputs that were not JSON deserializable, either directly or by removing leading and trailing backticks.<br>
84
+ 95% confidence intervals computed using a nonparametric bootstrap over scores distributions.
85
+ </small>
86
+ </figcaption>
87
+ </figure>
88
+
89
+ The benchmark include samples containing multiple images resulting in large input context, and some with ground-truth containing large numbers of items to extract resulting in large outputs. We found that the reasoning of small models significantly negatively impact their performances. The reason is that many models ended up falling in repetition loops, hitting the output tokens limit and resulting in failed requests.
90
+
91
+ ## Document to Markdown
92
+
93
+ NuExtract can also convert document images into clean Markdown. Output will be Markdown for text (headers etc), HTML for tables, LaTeX for math and ```<figure data-type="image" data-id="img_n"><img src="/NM-dev/model_card-A/resolve/main/img_n.png" alt="Detail description of the images"/> ```
94
+
95
+ Modern, format-agnostic benchmarks for complex document understanding are limited, so we explored a new evaluation approach.
96
+ We selected 100 documents with challenging layouts and tables, asked each model to convert them into a structured representation, then used Gemini 3 Flash to compare model outputs against the source document and choose the most accurate result.
97
+ The rankings aligned with human votes, suggesting this is a promising method for evaluating document-to-Markdown capabilities. More details will be shared in an upcoming technical report.
98
+ Here are some results:
99
+
100
+ <img src="ocr_preferences.svg" width="1000"/>
101
+
102
+ ### Using "Markdown-to-structured"
103
+
104
+ To add other evaluate references, we used our structured extraction benchmark to evaluate models in a two-step fashion: convert the benchmark inputs to Markdown, then use Qwen3.6 27B to perform the structured extraction task on them. Intuitively, it allows to evaluate how models achieve to keep the input document content and layout: good models will allow the "structured extractor" model to perform better scores.
105
+
106
+ <img src="md2st.svg" width="1000"/>
107
+
108
+
109
+ # Using NuExtract
110
+
111
+ ## Structured extraction
112
+
113
+ Structured extraction takes as inputs:
114
+
115
+ 1. An input document, which can be text, image, or both;
116
+ 2. A JSON template describing the information to extract;
117
+ 3. (Optional) Instructions, allowing to specify expected output formats or values, to provide with the `instructions` chat template kwarg;
118
+ 4. (Optional) In-Context Learning (ICL) examples.
119
+
120
+ ### Input JSON template
121
+
122
+ NuExtract uses a input JSON template whose structure is identical to the output JSON. Its leaf values are specify the **types** of the output JSON leaves. For examples:
123
+
124
+ ```json
125
+ {
126
+ "invoice_number": "verbatim-string",
127
+ "invoice_date": "date",
128
+ "total_amount": "number",
129
+ "currency": "currency",
130
+ "line_items": [
131
+ {
132
+ "description": "verbatim-string",
133
+ "item_type": ["electronics", "clothing", "vehicle", "furniture", "other"],
134
+ "quantity": "integer",
135
+ "unit_price": "number",
136
+ "total": "number"
137
+ }
138
+ ]
139
+ }
140
+ ```
141
+
142
+ Supported template types include:
143
+
144
+ - `verbatim-string`: extract text exactly as it appears in the document;
145
+ - `string`: generic string field, allowing abstraction or light paraphrasing;
146
+ - `integer`: whole number;
147
+ - `number`: integer or decimal number;
148
+ - `date-time`: ISO-8601 date, time or date-time;
149
+ - Other specific types such as `data`, `time`, `country`, `currency`, `email` and so on.
150
+ [**For more details, read the complete types specifications and examples**](TYPES.md)
151
+
152
+ Template constructors:
153
+
154
+ - Arrays, for example `["string"]`;
155
+ - Enums, for example `["yes", "no", "maybe"]`;
156
+ - Multi-enums (multiple possible values), for example `[["A", "B", "C"]]`.
157
+
158
+ If the model does not find relevant information for a field, it returns `null` or `[]`.
159
+
160
+ ### Converting JSON schema / Pydantic models to NuExtract template
161
+
162
+ Our Python SDK (`pip install numind`) offers a method to convert JSON schemas to NuExtract templates:
163
+
164
+ ```Python
165
+ from typing import Literal
166
+
167
+ from pydantic import Field, BaseModel
168
+ from numind.nuextract_utils import convert_json_schema_to_nuextract_template
169
+
170
+
171
+ class HotelBooking(BaseModel):
172
+ city: str
173
+ check_in_date: str = Field(description="date")
174
+ check_out_date: str = Field(description="date")
175
+ number_of_guests: int
176
+ room_type: Literal["single", "double", "suite"]
177
+
178
+
179
+ template, dropped_branches = convert_json_schema_to_nuextract_template(
180
+ HotelBooking.model_json_schema()
181
+ )
182
+
183
+ # {'check_in_date': 'date', 'check_out_date': 'date', 'city': 'string', 'number_of_guests': 'integer', 'room_type': ['single', 'double', 'suite']}
184
+ ```
185
+
186
+ ## Document-to-Markdown
187
+
188
+ NuExtract can also convert document images into clean Markdown. Output will be markdown for text (headers etc), html for tables, latex for mat and ```<figure data-type="image" data-id="img_n"><img src="/NM-dev/model_card-A/resolve/main/img_n.png" alt="Detail description of the images"/> ```
189
+
190
+ Markdown example:
191
+
192
+ ```markdown
193
+ <figure data-type="image" data-id="img_1">
194
+ <img src="img_1.png" alt="Logo of Mobilier 2000 with contact information: Tél.: (418) 275-4232, 1654, boul. Marcotte, Roberval (Qc) G8H 2P2"/>
195
+ </figure>
196
+
197
+ # COMMANDE
198
+ **NUMÉRO 72259**
199
+
200
+ 1
201
+
202
+ **Vendu à**
203
+ TREMBLAY ERIC
204
+ ERIC TREMBLAY
205
+ 348 BOUL. DE L'ANSE
206
+ ROBERVAL
207
+ G8H 1Y9
208
+
209
+ **Livré à**
210
+ TREMBLAY ERIC
211
+ ERIC TREMBLAY
212
+ 348 BOUL. DE L'ANSE
213
+ ROBERVAL
214
+ G8H 1Y9
215
+
216
+ <table>
217
+ <thead>
218
+ <tr>
219
+ <th># CLIENT</th>
220
+ <th>EXPÉDITEUR</th>
221
+ <th>TERME DE CRÉDIT</th>
222
+ <th>DATE</th>
223
+ </tr>
224
+ </thead>
225
+ <tbody>
226
+ <tr>
227
+ <td>2753133</td>
228
+ <td>Notre camion</td>
229
+ <td>à la livraison</td>
230
+ <td>22/06/2023</td>
231
+ </tr>
232
+ </tbody>
233
+ </table>
234
+
235
+ <table>
236
+ <thead>
237
+ <tr>
238
+ <th>NOM DU VENDEUR</th>
239
+ <th>VOTRE ÉCONOMIE !</th>
240
+ <th># COMMANDE</th>
241
+ </tr>
242
+ </thead>
243
+ <tbody>
244
+ <tr>
245
+ <td>Éric</td>
246
+ <td>0.00</td>
247
+ <td></td>
248
+ </tr>
249
+ </tbody>
250
+ </table>
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Reasoning and non-reasoning modes
256
+
257
+ NuExtract supports both reasoning and non-reasoning inference.
258
+
259
+ ### Non-thinking mode
260
+
261
+ Use this for fast and deterministic extraction or Markdown conversion.
262
+
263
+ ```python
264
+ enable_thinking = False
265
+ temperature = 0.2
266
+ ```
267
+
268
+ ### Thinking mode
269
+
270
+ Use this for difficult documents, complex layouts, ambiguous fields, or cases where the document structure requires additional reasoning.
271
+
272
+ ```python
273
+ enable_thinking = True
274
+ temperature = 0.6
275
+ ```
276
+
277
+ For production extraction workloads, we recommend starting with **non-reasoning mode** and enabling reasoning only for difficult examples.
278
+
279
+
280
+ ---
281
+
282
+ ## vLLM deployment
283
+
284
+ NuExtract can be served with vLLM using an OpenAI-compatible API.
285
+
286
+ ```bash
287
+ vllm serve numind/NuExtract3 \
288
+ --trust-remote-code \
289
+ --limit-mm-per-prompt '{"image": 99, "video": 0}' \
290
+ --chat-template-content-format openai \
291
+ --generation-config vllm \
292
+ --max-model-len 131072 \
293
+ --speculative-config '{"method": "qwen3_next_mtp", "num_speculative_tokens": 2}'
294
+ ```
295
+
296
+
297
+ ### Multi Token Prediction
298
+ <details>
299
+ The deployment commands above enable Multi Token Prediction (MTP) through vLLM speculative decoding:
300
+
301
+ ```bash
302
+ --speculative-config '{"method": "qwen3_next_mtp", "num_speculative_tokens": 2}'
303
+ ```
304
+
305
+ MTP can improve decoding throughput without changing the OpenAI-compatible request payload. You can tune `num_speculative_tokens` for your hardware and workload, or remove `--speculative-config` if your vLLM version or environment does not support this speculative decoding method.
306
+
307
+ If you encounter memory issues, reduce the maximum model length and the maximum number of images:
308
+
309
+ ```bash
310
+ vllm serve numind/NuExtract-3 \
311
+ --trust-remote-code \
312
+ --limit-mm-per-prompt '{"image": 6, "video": 0}' \
313
+ --chat-template-content-format openai \
314
+ --generation-config vllm \
315
+ --max-model-len 16384 \
316
+ --speculative-config '{"method": "qwen3_next_mtp", "num_speculative_tokens": 2}'
317
+ ```
318
+ </details>
319
+
320
+ ## vLLM inference: structured extraction: text
321
+ ```python
322
+ import json
323
+ from openai import OpenAI
324
+
325
+ client = OpenAI(
326
+ api_key="EMPTY",
327
+ base_url="http://localhost:8000/v1",
328
+ )
329
+
330
+ template = {
331
+ "store": "verbatim-string",
332
+ "date": "date-time",
333
+ "total": "number",
334
+ "currency": ["USD", "EUR", "GBP", "JPY", "Other"],
335
+ "items": [
336
+ {
337
+ "name": "verbatim-string",
338
+ "price": "number"
339
+ }
340
+ ]
341
+ }
342
+
343
+ response = client.chat.completions.create(
344
+ model="numind/NuExtract3",
345
+ temperature=0.2,
346
+ messages=[
347
+ {
348
+ "role": "user",
349
+ "content": [
350
+ {
351
+ "type": "text",
352
+ "text": "Yesterday I bought apples and coffee at Trader Joe's for a total of $12.40."
353
+ }
354
+ ],
355
+ }
356
+ ],
357
+ extra_body={
358
+ "chat_template_kwargs": {
359
+ "template": json.dumps(template),
360
+ "instructions": "Specify the time for the `date` entry only if it is present, otherwise only output the date component.",
361
+ "enable_thinking": False
362
+ }
363
+ }
364
+ )
365
+
366
+ print(response.choices[0].message.content)
367
+ ```
368
+
369
+ Example output:
370
+
371
+ ```json
372
+ {
373
+ "store": "Trader Joe's",
374
+ "date": null,
375
+ "total": 12.40,
376
+ "currency": "USD",
377
+ "items": [
378
+ {
379
+ "name": "apples",
380
+ "price": null
381
+ },
382
+ {
383
+ "name": "coffee",
384
+ "price": null
385
+ }
386
+ ]
387
+ }
388
+ ```
389
+
390
+ ---
391
+
392
+ ## vLLM inference: structured extraction: image
393
+
394
+ ```python
395
+ import json
396
+ import base64
397
+ from openai import OpenAI
398
+
399
+ client = OpenAI(
400
+ api_key="EMPTY",
401
+ base_url="http://localhost:8000/v1",
402
+ )
403
+
404
+ def encode_image(image_path):
405
+ with open(image_path, "rb") as image_file:
406
+ return base64.b64encode(image_file.read()).decode("utf-8")
407
+
408
+ image_base64 = encode_image("receipt.png")
409
+ data_url = f"data:image/png;base64,{image_base64}"
410
+
411
+ template = {
412
+ "store": "verbatim-string",
413
+ "date": "date-time",
414
+ "total": "number",
415
+ "payment_method": "verbatim-string"
416
+ }
417
+
418
+ response = client.chat.completions.create(
419
+ model="numind/NuExtract3",
420
+ temperature=0.2,
421
+ messages=[
422
+ {
423
+ "role": "user",
424
+ "content": [
425
+ {
426
+ "type": "image_url",
427
+ "image_url": {"url": data_url}
428
+ }
429
+ ],
430
+ }
431
+ ],
432
+ extra_body={
433
+ "chat_template_kwargs": {
434
+ "template": json.dumps(template, indent=4),
435
+ "enable_thinking": False
436
+ }
437
+ }
438
+ )
439
+
440
+ print(response.choices[0].message.content)
441
+ ```
442
+
443
+ Example output:
444
+
445
+ ```json
446
+ {
447
+ "store": "Trader Joe's",
448
+ "date": "2025-04-12",
449
+ "total": 42.85,
450
+ "payment_method": "Visa"
451
+ }
452
+ ```
453
+
454
+ ### Multiple page PDF
455
+ <details>
456
+ You can render a PDF to one PNG image per page with PyMuPDF, then pass the images to vLLM in page order.
457
+
458
+ ```python
459
+ import base64
460
+ import json
461
+
462
+ import fitz # pip install pymupdf
463
+ from openai import OpenAI
464
+
465
+ client = OpenAI(
466
+ api_key="EMPTY",
467
+ base_url="http://localhost:8000/v1",
468
+ )
469
+
470
+ def pdf_to_png_data_urls(pdf_path, dpi=170):
471
+ data_urls = []
472
+
473
+ with fitz.open(pdf_path) as doc:
474
+ for page in doc:
475
+ pix = page.get_pixmap(dpi=dpi, alpha=False)
476
+ png_bytes = pix.tobytes("png")
477
+ png_base64 = base64.b64encode(png_bytes).decode("utf-8")
478
+ data_urls.append(f"data:image/png;base64,{png_base64}")
479
+
480
+ return data_urls
481
+
482
+ data_urls = pdf_to_png_data_urls("invoice.pdf", dpi=170)
483
+
484
+ template = {
485
+ "invoice_number": "verbatim-string",
486
+ "invoice_date": "date",
487
+ "total": "number",
488
+ "currency": "currency",
489
+ "line_items": [
490
+ {
491
+ "description": "verbatim-string",
492
+ "quantity": "number",
493
+ "unit_price": "number",
494
+ "total": "number"
495
+ }
496
+ ]
497
+ }
498
+
499
+ response = client.chat.completions.create(
500
+ model="numind/NuExtract3",
501
+ temperature=0.2,
502
+ messages=[
503
+ {
504
+ "role": "user",
505
+ "content": [
506
+ {
507
+ "type": "image_url",
508
+ "image_url": {"url": data_url}
509
+ }
510
+ for data_url in data_urls
511
+ ],
512
+ }
513
+ ],
514
+ extra_body={
515
+ "chat_template_kwargs": {
516
+ "template": json.dumps(template, indent=4),
517
+ "enable_thinking": False
518
+ }
519
+ }
520
+ )
521
+
522
+ print(response.choices[0].message.content)
523
+ ```
524
+ </details>
525
+
526
+
527
+
528
+ ## vLLM inference: document-to-Markdown
529
+
530
+ For Markdown OCR, use `mode="markdown"` or `mode="content"` without a template.
531
+
532
+ ```python
533
+ import base64
534
+ from openai import OpenAI
535
+
536
+ client = OpenAI(
537
+ api_key="EMPTY",
538
+ base_url="http://localhost:8000/v1",
539
+ )
540
+
541
+ def encode_image(image_path):
542
+ with open(image_path, "rb") as image_file:
543
+ return base64.b64encode(image_file.read()).decode("utf-8")
544
+
545
+ image_base64 = encode_image("document.png")
546
+ data_url = f"data:image/png;base64,{image_base64}"
547
+
548
+ response = client.chat.completions.create(
549
+ model="numind/NuExtract3",
550
+ temperature=1,
551
+ messages=[
552
+ {
553
+ "role": "user",
554
+ "content": [
555
+ {
556
+ "type": "image_url",
557
+ "image_url": {"url": data_url}
558
+ }
559
+ ],
560
+ }
561
+ ],
562
+ extra_body={
563
+ "chat_template_kwargs": {
564
+ "mode": "markdown",
565
+ "enable_thinking": False
566
+ }
567
+ }
568
+ )
569
+
570
+ print(response.choices[0].message.content)
571
+ ```
572
+
573
+ ---
574
+
575
+ ## vLLM inference: reasoning mode
576
+ <details>
577
+ Reasoning can be enabled for harder structured extraction or Markdown tasks.
578
+
579
+ ```python
580
+ response = client.chat.completions.create(
581
+ model="numind/NuExtract3",
582
+ temperature=1,
583
+ messages=[
584
+ {
585
+ "role": "user",
586
+ "content": [
587
+ {
588
+ "type": "image_url",
589
+ "image_url": {"url": data_url}
590
+ }
591
+ ],
592
+ }
593
+ ],
594
+ extra_body={
595
+ "chat_template_kwargs": {
596
+ "mode": "markdown",
597
+ "enable_thinking": True
598
+ }
599
+ }
600
+ )
601
+
602
+ result = response.choices[0].message.content
603
+
604
+ if "</think>" in result:
605
+ reasoning, answer = [part.strip() for part in result.split("</think>")]
606
+ else:
607
+ reasoning, answer = None, result
608
+
609
+ print(answer)
610
+ ```
611
+ </details>
612
+
613
+
614
+ ## In-context examples for extraction
615
+ <details>
616
+ NuExtract supports in-context examples for structured extraction.
617
+
618
+ Examples are especially useful when the desired formatting is ambiguous or when the schema requires task-specific conventions. Examples can be provided by using `developer` messages, for which all items of the contents except the last one are the input, and the last one is the expected output.
619
+
620
+ ```python
621
+ import json
622
+ from openai import OpenAI
623
+
624
+ client = OpenAI(
625
+ api_key="EMPTY",
626
+ base_url="http://localhost:8000/v1",
627
+ )
628
+
629
+ template = {
630
+ "names": ["string"]
631
+ }
632
+
633
+ response = client.chat.completions.create(
634
+ model="numind/NuExtract3",
635
+ temperature=0.2,
636
+ messages=[
637
+ {
638
+ "role": "developer",
639
+ "content": [
640
+ {
641
+ "type": "text",
642
+ "text": "Stephen is the manager at Susan's store.",
643
+ },
644
+ {
645
+ "type": "text",
646
+ "text": "{\"names\": [\"-STEPHEN-\", \"-SUSAN-\"]}",
647
+ }
648
+ ],
649
+ },
650
+ {
651
+ "role": "user",
652
+ "content": [
653
+ {
654
+ "type": "text",
655
+ "text": "John went to the restaurant with Mary. James went to the cinema."
656
+ }
657
+ ],
658
+ }
659
+ ],
660
+ extra_body={
661
+ "chat_template_kwargs": {
662
+ "template": json.dumps(template, indent=4),
663
+ "enable_thinking": False
664
+ }
665
+ }
666
+ )
667
+
668
+ print(response.choices[0].message.content)
669
+ ```
670
+
671
+ Example output:
672
+
673
+ ```json
674
+ {
675
+ "names": ["-JOHN-", "-MARY-", "-JAMES-"]
676
+ }
677
+ ```
678
+ </details>
679
+
680
+
681
+ ## vLLM inference: template generation
682
+
683
+ NuExtract can generate an extraction template from a natural language description.
684
+
685
+ ```python
686
+ from openai import OpenAI
687
+
688
+ client = OpenAI(
689
+ api_key="EMPTY",
690
+ base_url="http://localhost:8000/v1",
691
+ )
692
+
693
+ response = client.chat.completions.create(
694
+ model="numind/NuExtract3",
695
+ temperature=0.2,
696
+ messages=[
697
+ {
698
+ "role": "user",
699
+ "content": [
700
+ {
701
+ "type": "text",
702
+ "text": "I want to extract the key details from a rental contract."
703
+ }
704
+ ],
705
+ }
706
+ ],
707
+ extra_body={
708
+ "chat_template_kwargs": {
709
+ "mode": "template-generation"
710
+ }
711
+ }
712
+ )
713
+
714
+ print(response.choices[0].message.content)
715
+ ```
716
+
717
+ Example output:
718
+
719
+ ```json
720
+ {
721
+ "contract_title": "verbatim-string",
722
+ "landlord": "verbatim-string",
723
+ "tenant": "verbatim-string",
724
+ "property_address": "verbatim-string",
725
+ "start_date": "date-time",
726
+ "end_date": "date-time",
727
+ "monthly_rent": "number",
728
+ "currency": "verbatim-string",
729
+ "deposit": "number",
730
+ "signatories": ["verbatim-string"]
731
+ }
732
+ ```
733
+
734
+ ## Curl examples
735
+ <details>
736
+
737
+ The following examples assume that vLLM is running locally on port 8000. They use `jq` to build valid JSON request bodies without manually escaping the image data or template string.
738
+
739
+ ### Single image structured extraction
740
+
741
+ ```bash
742
+ API_KEY="EMPTY"
743
+ IMAGE_BASE64_FILE=$(mktemp)
744
+ REQUEST_BODY_FILE=$(mktemp)
745
+
746
+ base64 < receipt.png | tr -d '\n' > "$IMAGE_BASE64_FILE"
747
+
748
+ TEMPLATE=$(cat <<'JSON'
749
+ {
750
+ "store": "verbatim-string",
751
+ "date": "date-time",
752
+ "total": "number",
753
+ "payment_method": "verbatim-string"
754
+ }
755
+ JSON
756
+ )
757
+
758
+ jq -n \
759
+ --rawfile image_base64 "$IMAGE_BASE64_FILE" \
760
+ --arg template "$TEMPLATE" \
761
+ '{
762
+ model: "numind/NuExtract3",
763
+ temperature: 0.6,
764
+ messages: [
765
+ {
766
+ role: "user",
767
+ content: [
768
+ {
769
+ type: "image_url",
770
+ image_url: {url: ("data:image/png;base64," + $image_base64)}
771
+ }
772
+ ]
773
+ }
774
+ ],
775
+ chat_template_kwargs: {
776
+ template: $template,
777
+ enable_thinking: false
778
+ }
779
+ }' > "$REQUEST_BODY_FILE"
780
+
781
+ curl http://localhost:8000/v1/chat/completions \
782
+ -H "Content-Type: application/json" \
783
+ -H "Authorization: Bearer $API_KEY" \
784
+ --data-binary "@$REQUEST_BODY_FILE"
785
+
786
+ rm "$IMAGE_BASE64_FILE" "$REQUEST_BODY_FILE"
787
+ ```
788
+
789
+ ### Single image content extraction
790
+
791
+ ```bash
792
+ API_KEY="EMPTY"
793
+ IMAGE_BASE64_FILE=$(mktemp)
794
+ REQUEST_BODY_FILE=$(mktemp)
795
+
796
+ base64 < document.png | tr -d '\n' > "$IMAGE_BASE64_FILE"
797
+
798
+ jq -n \
799
+ --rawfile image_base64 "$IMAGE_BASE64_FILE" \
800
+ '{
801
+ model: "numind/NuExtract3",
802
+ temperature: 0.6,
803
+ messages: [
804
+ {
805
+ role: "user",
806
+ content: [
807
+ {
808
+ type: "image_url",
809
+ image_url: {url: ("data:image/png;base64," + $image_base64)}
810
+ }
811
+ ]
812
+ }
813
+ ],
814
+ chat_template_kwargs: {
815
+ mode: "content",
816
+ enable_thinking: false
817
+ }
818
+ }' > "$REQUEST_BODY_FILE"
819
+
820
+ curl http://localhost:8000/v1/chat/completions \
821
+ -H "Content-Type: application/json" \
822
+ -H "Authorization: Bearer $API_KEY" \
823
+ --data-binary "@$REQUEST_BODY_FILE"
824
+
825
+ rm "$IMAGE_BASE64_FILE" "$REQUEST_BODY_FILE"
826
+ ```
827
+ </details>
828
+
829
+
830
+ ## Transformers example
831
+ <details>
832
+ You can also run NuExtract directly with `transformers`. The same `template`, `mode`, and `enable_thinking` options are passed to `processor.apply_chat_template`.
833
+
834
+ ```python
835
+ import json
836
+
837
+ import torch
838
+ from PIL import Image
839
+ from transformers import AutoModelForImageTextToText, AutoProcessor
840
+
841
+ model_id = "numind/NuExtract3"
842
+
843
+ processor = AutoProcessor.from_pretrained(
844
+ model_id,
845
+ trust_remote_code=True,
846
+ )
847
+ model = AutoModelForImageTextToText.from_pretrained(
848
+ model_id,
849
+ dtype=torch.bfloat16,
850
+ device_map="auto",
851
+ trust_remote_code=True,
852
+ ).eval()
853
+
854
+ def run_nuextract(messages, **chat_template_kwargs):
855
+ inputs = processor.apply_chat_template(
856
+ messages,
857
+ add_generation_prompt=True,
858
+ tokenize=True,
859
+ return_dict=True,
860
+ return_tensors="pt",
861
+ **chat_template_kwargs,
862
+ ).to(model.device)
863
+
864
+ with torch.inference_mode():
865
+ generated_ids = model.generate(
866
+ **inputs,
867
+ max_new_tokens=4096,
868
+ do_sample=False,
869
+ )
870
+
871
+ generated_ids = generated_ids[:, inputs.input_ids.shape[1]:]
872
+ return processor.batch_decode(
873
+ generated_ids,
874
+ skip_special_tokens=True,
875
+ clean_up_tokenization_spaces=False,
876
+ )[0].strip()
877
+
878
+ # Single image structured extraction
879
+ receipt_image = Image.open("receipt.png").convert("RGB")
880
+ receipt_messages = [
881
+ {
882
+ "role": "user",
883
+ "content": [
884
+ {
885
+ "type": "image",
886
+ "image": receipt_image,
887
+ }
888
+ ],
889
+ }
890
+ ]
891
+
892
+ template = {
893
+ "store": "verbatim-string",
894
+ "date": "date-time",
895
+ "total": "number",
896
+ "payment_method": "verbatim-string"
897
+ }
898
+
899
+ structured_output = run_nuextract(
900
+ receipt_messages,
901
+ template=json.dumps(template, indent=4),
902
+ enable_thinking=False,
903
+ )
904
+ print(structured_output)
905
+
906
+ # Single image content extraction
907
+ document_image = Image.open("document.png").convert("RGB")
908
+ document_messages = [
909
+ {
910
+ "role": "user",
911
+ "content": [
912
+ {
913
+ "type": "image",
914
+ "image": document_image,
915
+ }
916
+ ],
917
+ }
918
+ ]
919
+
920
+ content_output = run_nuextract(
921
+ document_messages,
922
+ mode="content",
923
+ enable_thinking=False,
924
+ )
925
+ print(content_output)
926
+ ```
927
+ </details>
928
+
929
+ Special thanks to the Lambda.ai team for the compute that made this project a success.
930
+
931
+ ## Citation
932
+
933
+ If you use NuExtract, please cite NuMind and link to the model page.
934
+
935
+ ```bibtex
936
+ @misc{nuextract3,
937
+ title = {NuExtract3},
938
+ author = {NuMind},
939
+ year = {2026},
940
+ url = {https://nuextract.ai/}
941
+ }
942
+ ```
.agents/references/nuextract3/2026-07-20/huggingface.co/numind/NuExtract3.md ADDED
@@ -0,0 +1,1010 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!--
2
+ source: https://huggingface.co/numind/NuExtract3
3
+ retrieved: 2026-07-20T17:29:25.403047+00:00
4
+ final_url: https://huggingface.co/numind/NuExtract3
5
+ content_type: text/html
6
+ -->
7
+
8
+ # numind/NuExtract3 · Hugging Face
9
+
10
+ [Hugging Face](https://huggingface.co/)
11
+
12
+ #
13
+
14
+ [https://huggingface.co/numind](https://huggingface.co/numind)
15
+
16
+ [numind](https://huggingface.co/numind)
17
+
18
+ /
19
+
20
+ [NuExtract3](https://huggingface.co/numind/NuExtract3)
21
+
22
+ like 295
23
+
24
+ Follow
25
+
26
+ NuMind 649
27
+
28
+ [Image-to-Text](https://huggingface.co/models?pipeline_tag=image-to-text)
29
+
30
+ [Transformers](https://huggingface.co/models?library=transformers)
31
+
32
+ [Safetensors](https://huggingface.co/models?library=safetensors)
33
+
34
+ [qwen3_5](https://huggingface.co/models?other=qwen3_5)
35
+
36
+ [image-text-to-text](https://huggingface.co/models?other=image-text-to-text)
37
+
38
+ [vision-language](https://huggingface.co/models?other=vision-language)
39
+
40
+ [vlm](https://huggingface.co/models?other=vlm)
41
+
42
+ [document-understanding](https://huggingface.co/models?other=document-understanding)
43
+
44
+ [structured-extraction](https://huggingface.co/models?other=structured-extraction)
45
+
46
+ [information-extraction](https://huggingface.co/models?other=information-extraction)
47
+
48
+ [ocr](https://huggingface.co/models?other=ocr)
49
+
50
+ [document-to-markdown](https://huggingface.co/models?other=document-to-markdown)
51
+
52
+ [markdown](https://huggingface.co/models?other=markdown)
53
+
54
+ [rag](https://huggingface.co/models?other=rag)
55
+
56
+ [reasoning](https://huggingface.co/models?other=reasoning)
57
+
58
+ [multilingual](https://huggingface.co/models?other=multilingual)
59
+
60
+ [conversational](https://huggingface.co/models?other=conversational)
61
+
62
+ [Eval Results](https://huggingface.co/models?other=eval-results)
63
+
64
+ License: apache-2.0
65
+
66
+ [Model card](https://huggingface.co/numind/NuExtract3)
67
+
68
+ [Files Files and versions xet](https://huggingface.co/numind/NuExtract3/tree/main)
69
+
70
+ [Community 2](https://huggingface.co/numind/NuExtract3/discussions)
71
+
72
+ Deploy
73
+
74
+ Copy to bucket new
75
+
76
+ Use this model
77
+
78
+ [https://nuextract.ai/](https://nuextract.ai/)
79
+
80
+ 🖥️ [API / Platform](https://nuextract.ai/) | 📑 [Blog](https://numind.ai/blog) | 🗣️ [Discord](https://discord.gg/3tsEtJNCDe) | 🛠️ [GitHub](https://github.com/numindai/nuextract)
81
+
82
+ NuExtract3 is a unified 4B vision-language reasoning model for document understanding.
83
+
84
+ It combines strong structured information extraction with high-quality image-to-Markdown conversion, making it suitable for extraction pipelines, OCR, and RAG preprocessing for all types of documents such as scans, receipts, forms, invoices, contracts or tables.
85
+
86
+ Try it out in [the 🤗 space!](https://huggingface.co/spaces/numind/NuExtract-3-4B)
87
+
88
+ ## [https://huggingface.co/numind/NuExtract3#overview](https://huggingface.co/numind/NuExtract3#overview) Overview
89
+
90
+ - Structured extraction : input (text/images) + JSON template + instructions --> JSON output
91
+
92
+ - Markdown conversion : input (text/images) --> Markdown
93
+
94
+ - Multimodal inputs : text, images, or text + images.
95
+
96
+ - Multilingual documents.
97
+
98
+ - Reasoning and non-reasoning inference modes.
99
+
100
+ - Template generation for structured extraction from natural language or input document.
101
+
102
+ # [https://huggingface.co/numind/NuExtract3#benchmark-results](https://huggingface.co/numind/NuExtract3#benchmark-results) Benchmark results
103
+
104
+ ## [https://huggingface.co/numind/NuExtract3#structured-extraction](https://huggingface.co/numind/NuExtract3#structured-extraction) Structured Extraction
105
+
106
+ We benchmarked NuExtract on NuMind's internal structured benchmark, measuring model's performances on ~600 documents of diverse types including invoices, movie posters or floor plans. These documents and their ground-truth cover diverse use-cases testing model visual understanding, OCR, reasoning and ability to handle long input and output contexts. We plan to open-source this benchmark in the coming weeks, along with a extensive leaderboard including most popular open-weight and closed-sourced APIs and a Python library allowing to easily measure model performances on structured extraction.
107
+
108
+ To measure a pair of predicted and ground-truth JSONs, we represent both as trees which we align based on node names, compute metric scores for aligned leaves and report the average of these scores. `string` and `verbatim-string` leaves are evaluated with indel distance (i.e. Levenshtein without replacement), while all others are evaluated with exact-match. Models were evaluated using vllm, with a temperature of 0.25 and a maximum of 65000 output token (for both thinking and answer), which largely exceeds 22000 which is the number of tokens of the largest ground truth output.
109
+
110
+ Model name Average score Num. failed⁽¹⁾ Avg. num tokens thinking Avg. num tokens answer NuExtract3.4_4B-RL 0.651 ± 0.019 27 2036 1856 gemma-4-E4B-it 0.538 ± 0.023 31 3005 1287 Qwen3.5-9B 0.479 ± 0.030 170 22409 1257 Qwen3.5-4B 0.417 ± 0.031 229 27177 1201 GLM-4.6V-Flash 0.435 ± 0.026 153 2989 1357 Nemotron-3-Nano-Omni 0.387 ± 0.028 204 25827 522 Ministral-3-3B 0.240 ± 0.022 344 27586 362
111
+
112
+ (1) number of model outputs that were not JSON deserializable, either directly or by removing leading and trailing backticks.
113
+ 95% confidence intervals computed using a nonparametric bootstrap over scores distributions.
114
+
115
+ The benchmark include samples containing multiple images resulting in large input context, and some with ground-truth containing large numbers of items to extract resulting in large outputs. We found that the reasoning of small models significantly negatively impact their performances. The reason is that many models ended up falling in repetition loops, hitting the output tokens limit and resulting in failed requests.
116
+
117
+ ## [https://huggingface.co/numind/NuExtract3#document-to-markdown](https://huggingface.co/numind/NuExtract3#document-to-markdown) Document to Markdown
118
+
119
+ NuExtract can also convert document images into clean Markdown. Output will be Markdown for text (headers etc), HTML for tables, LaTeX for math and `<figure data-type="image" data-id="img_n"><img src="/NM-dev/model_card-A/resolve/main/img_n.png" alt="Detail description of the images"/> `
120
+
121
+ Modern, format-agnostic benchmarks for complex document understanding are limited, so we explored a new evaluation approach. We selected 100 documents with challenging layouts and tables, asked each model to convert them into a structured representation, then used Gemini 3 Flash to compare model outputs against the source document and choose the most accurate result. The rankings aligned with human votes, suggesting this is a promising method for evaluating document-to-Markdown capabilities. More details will be shared in an upcoming technical report. Here are some results:
122
+
123
+ ### [https://huggingface.co/numind/NuExtract3#using-markdown-to-structured](https://huggingface.co/numind/NuExtract3#using-markdown-to-structured) Using "Markdown-to-structured"
124
+
125
+ To add other evaluate references, we used our structured extraction benchmark to evaluate models in a two-step fashion: convert the benchmark inputs to Markdown, then use Qwen3.6 27B to perform the structured extraction task on them. Intuitively, it allows to evaluate how models achieve to keep the input document content and layout: good models will allow the "structured extractor" model to perform better scores.
126
+
127
+ # [https://huggingface.co/numind/NuExtract3#using-nuextract](https://huggingface.co/numind/NuExtract3#using-nuextract) Using NuExtract
128
+
129
+ ## [https://huggingface.co/numind/NuExtract3#structured-extraction-1](https://huggingface.co/numind/NuExtract3#structured-extraction-1) Structured extraction
130
+
131
+ Structured extraction takes as inputs:
132
+
133
+ 1. An input document, which can be text, image, or both;
134
+
135
+ 2. A JSON template describing the information to extract;
136
+
137
+ 3. (Optional) Instructions, allowing to specify expected output formats or values, to provide with the `instructions` chat template kwarg;
138
+
139
+ 4. (Optional) In-Context Learning (ICL) examples.
140
+
141
+ ### [https://huggingface.co/numind/NuExtract3#input-json-template](https://huggingface.co/numind/NuExtract3#input-json-template) Input JSON template
142
+
143
+ NuExtract uses a input JSON template whose structure is identical to the output JSON. Its leaf values are specify the types of the output JSON leaves. For examples:
144
+
145
+ ```
146
+ {
147
+ "invoice_number": "verbatim-string",
148
+ "invoice_date": "date",
149
+ "total_amount": "number",
150
+ "currency": "currency",
151
+ "line_items": [
152
+ {
153
+ "description": "verbatim-string",
154
+ "item_type": ["electronics", "clothing", "vehicle", "furniture", "other"],
155
+ "quantity": "integer",
156
+ "unit_price": "number",
157
+ "total": "number"
158
+ }
159
+ ]
160
+ }
161
+ ```
162
+
163
+ Supported template types include:
164
+
165
+ - `verbatim-string`: extract text exactly as it appears in the document;
166
+
167
+ - `string`: generic string field, allowing abstraction or light paraphrasing;
168
+
169
+ - `integer`: whole number;
170
+
171
+ - `number`: integer or decimal number;
172
+
173
+ - `date-time`: ISO-8601 date, time or date-time;
174
+
175
+ - Other specific types such as `data`, `time`, `country`, `currency`, `email` and so on. [For more details, read the complete types specifications and examples](https://huggingface.co/numind/NuExtract3/blob/main/TYPES.md)
176
+
177
+ Template constructors:
178
+
179
+ - Arrays, for example `["string"]`;
180
+
181
+ - Enums, for example `["yes", "no", "maybe"]`;
182
+
183
+ - Multi-enums (multiple possible values), for example `[["A", "B", "C"]]`.
184
+
185
+ If the model does not find relevant information for a field, it returns `null` or `[]`.
186
+
187
+ ### [https://huggingface.co/numind/NuExtract3#converting-json-schema--pydantic-models-to-nuextract-template](https://huggingface.co/numind/NuExtract3#converting-json-schema--pydantic-models-to-nuextract-template) Converting JSON schema / Pydantic models to NuExtract template
188
+
189
+ Our Python SDK (`pip install numind`) offers a method to convert JSON schemas to NuExtract templates:
190
+
191
+ ```
192
+ from typing import Literal
193
+
194
+ from pydantic import Field, BaseModel
195
+ from numind.nuextract_utils import convert_json_schema_to_nuextract_template
196
+
197
+ class HotelBooking(BaseModel):
198
+ city: str
199
+ check_in_date: str = Field(description="date")
200
+ check_out_date: str = Field(description="date")
201
+ number_of_guests: int
202
+ room_type: Literal["single", "double", "suite"]
203
+
204
+ template, dropped_branches = convert_json_schema_to_nuextract_template(
205
+ HotelBooking.model_json_schema()
206
+ )
207
+
208
+ # {'check_in_date': 'date', 'check_out_date': 'date', 'city': 'string', 'number_of_guests': 'integer', 'room_type': ['single', 'double', 'suite']}
209
+ ```
210
+
211
+ ## [https://huggingface.co/numind/NuExtract3#document-to-markdown-1](https://huggingface.co/numind/NuExtract3#document-to-markdown-1) Document-to-Markdown
212
+
213
+ NuExtract can also convert document images into clean Markdown. Output will be markdown for text (headers etc), html for tables, latex for mat and `<figure data-type="image" data-id="img_n"><img src="/NM-dev/model_card-A/resolve/main/img_n.png" alt="Detail description of the images"/> `
214
+
215
+ Markdown example:
216
+
217
+ ```
218
+ <figure data-type="image" data-id="img_1">
219
+ <img src="/numind/NuExtract3/resolve/main/img_1.png" alt="Logo of Mobilier 2000 with contact information: Tél.: (418) 275-4232, 1654, boul. Marcotte, Roberval (Qc) G8H 2P2"/>
220
+ </figure>
221
+
222
+ # COMMANDE
223
+ **NUMÉRO 72259**
224
+
225
+ 1
226
+
227
+ **Vendu à**
228
+ TREMBLAY ERIC
229
+ ERIC TREMBLAY
230
+ 348 BOUL. DE L'ANSE
231
+ ROBERVAL
232
+ G8H 1Y9
233
+
234
+ **Livré à**
235
+ TREMBLAY ERIC
236
+ ERIC TREMBLAY
237
+ 348 BOUL. DE L'ANSE
238
+ ROBERVAL
239
+ G8H 1Y9
240
+
241
+ <table>
242
+ <thead>
243
+ <tr>
244
+ <th># CLIENT</th>
245
+ <th>EXPÉDITEUR</th>
246
+ <th>TERME DE CRÉDIT</th>
247
+ <th>DATE</th>
248
+ </tr>
249
+ </thead>
250
+ <tbody>
251
+ <tr>
252
+ <td>2753133</td>
253
+ <td>Notre camion</td>
254
+ <td>à la livraison</td>
255
+ <td>22/06/2023</td>
256
+ </tr>
257
+ </tbody>
258
+ </table>
259
+
260
+ <table>
261
+ <thead>
262
+ <tr>
263
+ <th>NOM DU VENDEUR</th>
264
+ <th>VOTRE ÉCONOMIE !</th>
265
+ <th># COMMANDE</th>
266
+ </tr>
267
+ </thead>
268
+ <tbody>
269
+ <tr>
270
+ <td>Éric</td>
271
+ <td>0.00</td>
272
+ <td></td>
273
+ </tr>
274
+ </tbody>
275
+ </table>
276
+ ```
277
+
278
+ ## [https://huggingface.co/numind/NuExtract3#reasoning-and-non-reasoning-modes](https://huggingface.co/numind/NuExtract3#reasoning-and-non-reasoning-modes) Reasoning and non-reasoning modes
279
+
280
+ NuExtract supports both reasoning and non-reasoning inference.
281
+
282
+ ### [https://huggingface.co/numind/NuExtract3#non-thinking-mode](https://huggingface.co/numind/NuExtract3#non-thinking-mode) Non-thinking mode
283
+
284
+ Use this for fast and deterministic extraction or Markdown conversion.
285
+
286
+ ```
287
+ enable_thinking = False
288
+ temperature = 0.2
289
+ ```
290
+
291
+ ### [https://huggingface.co/numind/NuExtract3#thinking-mode](https://huggingface.co/numind/NuExtract3#thinking-mode) Thinking mode
292
+
293
+ Use this for difficult documents, complex layouts, ambiguous fields, or cases where the document structure requires additional reasoning.
294
+
295
+ ```
296
+ enable_thinking = True
297
+ temperature = 0.6
298
+ ```
299
+
300
+ For production extraction workloads, we recommend starting with non-reasoning mode and enabling reasoning only for difficult examples.
301
+
302
+ ## [https://huggingface.co/numind/NuExtract3#vllm-deployment](https://huggingface.co/numind/NuExtract3#vllm-deployment) vLLM deployment
303
+
304
+ NuExtract can be served with vLLM using an OpenAI-compatible API.
305
+
306
+ ```
307
+ vllm serve numind/NuExtract3 \
308
+ --trust-remote-code \
309
+ --limit-mm-per-prompt '{"image": 99, "video": 0}' \
310
+ --chat-template-content-format openai \
311
+ --generation-config vllm \
312
+ --max-model-len 131072 \
313
+ --speculative-config '{"method": "qwen3_next_mtp", "num_speculative_tokens": 2}'
314
+ ```
315
+
316
+ ### [https://huggingface.co/numind/NuExtract3#multi-token-prediction](https://huggingface.co/numind/NuExtract3#multi-token-prediction) Multi Token Prediction
317
+
318
+ The deployment commands above enable Multi Token Prediction (MTP) through vLLM speculative decoding:
319
+
320
+ ```
321
+ --speculative-config '{"method": "qwen3_next_mtp", "num_speculative_tokens": 2}'
322
+ ```
323
+
324
+ MTP can improve decoding throughput without changing the OpenAI-compatible request payload. You can tune `num_speculative_tokens` for your hardware and workload, or remove `--speculative-config` if your vLLM version or environment does not support this speculative decoding method.
325
+
326
+ If you encounter memory issues, reduce the maximum model length and the maximum number of images:
327
+
328
+ ```
329
+ vllm serve numind/NuExtract-3 \
330
+ --trust-remote-code \
331
+ --limit-mm-per-prompt '{"image": 6, "video": 0}' \
332
+ --chat-template-content-format openai \
333
+ --generation-config vllm \
334
+ --max-model-len 16384 \
335
+ --speculative-config '{"method": "qwen3_next_mtp", "num_speculative_tokens": 2}'
336
+ ```
337
+
338
+ ## [https://huggingface.co/numind/NuExtract3#vllm-inference-structured-extraction-text](https://huggingface.co/numind/NuExtract3#vllm-inference-structured-extraction-text) vLLM inference: structured extraction: text
339
+
340
+ ```
341
+ import json
342
+ from openai import OpenAI
343
+
344
+ client = OpenAI(
345
+ api_key="EMPTY",
346
+ base_url="http://localhost:8000/v1",
347
+ )
348
+
349
+ template = {
350
+ "store": "verbatim-string",
351
+ "date": "date-time",
352
+ "total": "number",
353
+ "currency": ["USD", "EUR", "GBP", "JPY", "Other"],
354
+ "items": [
355
+ {
356
+ "name": "verbatim-string",
357
+ "price": "number"
358
+ }
359
+ ]
360
+ }
361
+
362
+ response = client.chat.completions.create(
363
+ model="numind/NuExtract3",
364
+ temperature=0.2,
365
+ messages=[
366
+ {
367
+ "role": "user",
368
+ "content": [
369
+ {
370
+ "type": "text",
371
+ "text": "Yesterday I bought apples and coffee at Trader Joe's for a total of $12.40."
372
+ }
373
+ ],
374
+ }
375
+ ],
376
+ extra_body={
377
+ "chat_template_kwargs": {
378
+ "template": json.dumps(template),
379
+ "instructions": "Specify the time for the `date` entry only if it is present, otherwise only output the date component.",
380
+ "enable_thinking": False
381
+ }
382
+ }
383
+ )
384
+
385
+ print(response.choices[0].message.content)
386
+ ```
387
+
388
+ Example output:
389
+
390
+ ```
391
+ {
392
+ "store": "Trader Joe's",
393
+ "date": null,
394
+ "total": 12.40,
395
+ "currency": "USD",
396
+ "items": [
397
+ {
398
+ "name": "apples",
399
+ "price": null
400
+ },
401
+ {
402
+ "name": "coffee",
403
+ "price": null
404
+ }
405
+ ]
406
+ }
407
+ ```
408
+
409
+ ## [https://huggingface.co/numind/NuExtract3#vllm-inference-structured-extraction-image](https://huggingface.co/numind/NuExtract3#vllm-inference-structured-extraction-image) vLLM inference: structured extraction: image
410
+
411
+ ```
412
+ import json
413
+ import base64
414
+ from openai import OpenAI
415
+
416
+ client = OpenAI(
417
+ api_key="EMPTY",
418
+ base_url="http://localhost:8000/v1",
419
+ )
420
+
421
+ def encode_image(image_path):
422
+ with open(image_path, "rb") as image_file:
423
+ return base64.b64encode(image_file.read()).decode("utf-8")
424
+
425
+ image_base64 = encode_image("receipt.png")
426
+ data_url = f"data:image/png;base64,{image_base64}"
427
+
428
+ template = {
429
+ "store": "verbatim-string",
430
+ "date": "date-time",
431
+ "total": "number",
432
+ "payment_method": "verbatim-string"
433
+ }
434
+
435
+ response = client.chat.completions.create(
436
+ model="numind/NuExtract3",
437
+ temperature=0.2,
438
+ messages=[
439
+ {
440
+ "role": "user",
441
+ "content": [
442
+ {
443
+ "type": "image_url",
444
+ "image_url": {"url": data_url}
445
+ }
446
+ ],
447
+ }
448
+ ],
449
+ extra_body={
450
+ "chat_template_kwargs": {
451
+ "template": json.dumps(template, indent=4),
452
+ "enable_thinking": False
453
+ }
454
+ }
455
+ )
456
+
457
+ print(response.choices[0].message.content)
458
+ ```
459
+
460
+ Example output:
461
+
462
+ ```
463
+ {
464
+ "store": "Trader Joe's",
465
+ "date": "2025-04-12",
466
+ "total": 42.85,
467
+ "payment_method": "Visa"
468
+ }
469
+ ```
470
+
471
+ ### [https://huggingface.co/numind/NuExtract3#multiple-page-pdf](https://huggingface.co/numind/NuExtract3#multiple-page-pdf) Multiple page PDF
472
+
473
+ You can render a PDF to one PNG image per page with PyMuPDF, then pass the images to vLLM in page order.
474
+
475
+ ```
476
+ import base64
477
+ import json
478
+
479
+ import fitz # pip install pymupdf
480
+ from openai import OpenAI
481
+
482
+ client = OpenAI(
483
+ api_key="EMPTY",
484
+ base_url="http://localhost:8000/v1",
485
+ )
486
+
487
+ def pdf_to_png_data_urls(pdf_path, dpi=170):
488
+ data_urls = []
489
+
490
+ with fitz.open(pdf_path) as doc:
491
+ for page in doc:
492
+ pix = page.get_pixmap(dpi=dpi, alpha=False)
493
+ png_bytes = pix.tobytes("png")
494
+ png_base64 = base64.b64encode(png_bytes).decode("utf-8")
495
+ data_urls.append(f"data:image/png;base64,{png_base64}")
496
+
497
+ return data_urls
498
+
499
+ data_urls = pdf_to_png_data_urls("invoice.pdf", dpi=170)
500
+
501
+ template = {
502
+ "invoice_number": "verbatim-string",
503
+ "invoice_date": "date",
504
+ "total": "number",
505
+ "currency": "currency",
506
+ "line_items": [
507
+ {
508
+ "description": "verbatim-string",
509
+ "quantity": "number",
510
+ "unit_price": "number",
511
+ "total": "number"
512
+ }
513
+ ]
514
+ }
515
+
516
+ response = client.chat.completions.create(
517
+ model="numind/NuExtract3",
518
+ temperature=0.2,
519
+ messages=[
520
+ {
521
+ "role": "user",
522
+ "content": [
523
+ {
524
+ "type": "image_url",
525
+ "image_url": {"url": data_url}
526
+ }
527
+ for data_url in data_urls
528
+ ],
529
+ }
530
+ ],
531
+ extra_body={
532
+ "chat_template_kwargs": {
533
+ "template": json.dumps(template, indent=4),
534
+ "enable_thinking": False
535
+ }
536
+ }
537
+ )
538
+
539
+ print(response.choices[0].message.content)
540
+ ```
541
+
542
+ ## [https://huggingface.co/numind/NuExtract3#vllm-inference-document-to-markdown](https://huggingface.co/numind/NuExtract3#vllm-inference-document-to-markdown) vLLM inference: document-to-Markdown
543
+
544
+ For Markdown OCR, use `mode="markdown"` or `mode="content"` without a template.
545
+
546
+ ```
547
+ import base64
548
+ from openai import OpenAI
549
+
550
+ client = OpenAI(
551
+ api_key="EMPTY",
552
+ base_url="http://localhost:8000/v1",
553
+ )
554
+
555
+ def encode_image(image_path):
556
+ with open(image_path, "rb") as image_file:
557
+ return base64.b64encode(image_file.read()).decode("utf-8")
558
+
559
+ image_base64 = encode_image("document.png")
560
+ data_url = f"data:image/png;base64,{image_base64}"
561
+
562
+ response = client.chat.completions.create(
563
+ model="numind/NuExtract3",
564
+ temperature=1,
565
+ messages=[
566
+ {
567
+ "role": "user",
568
+ "content": [
569
+ {
570
+ "type": "image_url",
571
+ "image_url": {"url": data_url}
572
+ }
573
+ ],
574
+ }
575
+ ],
576
+ extra_body={
577
+ "chat_template_kwargs": {
578
+ "mode": "markdown",
579
+ "enable_thinking": False
580
+ }
581
+ }
582
+ )
583
+
584
+ print(response.choices[0].message.content)
585
+ ```
586
+
587
+ ## [https://huggingface.co/numind/NuExtract3#vllm-inference-reasoning-mode](https://huggingface.co/numind/NuExtract3#vllm-inference-reasoning-mode) vLLM inference: reasoning mode
588
+
589
+ Reasoning can be enabled for harder structured extraction or Markdown tasks.
590
+
591
+ ```
592
+ response = client.chat.completions.create(
593
+ model="numind/NuExtract3",
594
+ temperature=1,
595
+ messages=[
596
+ {
597
+ "role": "user",
598
+ "content": [
599
+ {
600
+ "type": "image_url",
601
+ "image_url": {"url": data_url}
602
+ }
603
+ ],
604
+ }
605
+ ],
606
+ extra_body={
607
+ "chat_template_kwargs": {
608
+ "mode": "markdown",
609
+ "enable_thinking": True
610
+ }
611
+ }
612
+ )
613
+
614
+ result = response.choices[0].message.content
615
+
616
+ if "</think>" in result:
617
+ reasoning, answer = [part.strip() for part in result.split("</think>")]
618
+ else:
619
+ reasoning, answer = None, result
620
+
621
+ print(answer)
622
+ ```
623
+
624
+ ## [https://huggingface.co/numind/NuExtract3#in-context-examples-for-extraction](https://huggingface.co/numind/NuExtract3#in-context-examples-for-extraction) In-context examples for extraction
625
+
626
+ NuExtract supports in-context examples for structured extraction.
627
+
628
+ Examples are especially useful when the desired formatting is ambiguous or when the schema requires task-specific conventions. Examples can be provided by using `developer` messages, for which all items of the contents except the last one are the input, and the last one is the expected output.
629
+
630
+ ```
631
+ import json
632
+ from openai import OpenAI
633
+
634
+ client = OpenAI(
635
+ api_key="EMPTY",
636
+ base_url="http://localhost:8000/v1",
637
+ )
638
+
639
+ template = {
640
+ "names": ["string"]
641
+ }
642
+
643
+ response = client.chat.completions.create(
644
+ model="numind/NuExtract3",
645
+ temperature=0.2,
646
+ messages=[
647
+ {
648
+ "role": "developer",
649
+ "content": [
650
+ {
651
+ "type": "text",
652
+ "text": "Stephen is the manager at Susan's store.",
653
+ },
654
+ {
655
+ "type": "text",
656
+ "text": "{\"names\": [\"-STEPHEN-\", \"-SUSAN-\"]}",
657
+ }
658
+ ],
659
+ },
660
+ {
661
+ "role": "user",
662
+ "content": [
663
+ {
664
+ "type": "text",
665
+ "text": "John went to the restaurant with Mary. James went to the cinema."
666
+ }
667
+ ],
668
+ }
669
+ ],
670
+ extra_body={
671
+ "chat_template_kwargs": {
672
+ "template": json.dumps(template, indent=4),
673
+ "enable_thinking": False
674
+ }
675
+ }
676
+ )
677
+
678
+ print(response.choices[0].message.content)
679
+ ```
680
+
681
+ Example output:
682
+
683
+ ```
684
+ {
685
+ "names": ["-JOHN-", "-MARY-", "-JAMES-"]
686
+ }
687
+ ```
688
+
689
+ ## [https://huggingface.co/numind/NuExtract3#vllm-inference-template-generation](https://huggingface.co/numind/NuExtract3#vllm-inference-template-generation) vLLM inference: template generation
690
+
691
+ NuExtract can generate an extraction template from a natural language description.
692
+
693
+ ```
694
+ from openai import OpenAI
695
+
696
+ client = OpenAI(
697
+ api_key="EMPTY",
698
+ base_url="http://localhost:8000/v1",
699
+ )
700
+
701
+ response = client.chat.completions.create(
702
+ model="numind/NuExtract3",
703
+ temperature=0.2,
704
+ messages=[
705
+ {
706
+ "role": "user",
707
+ "content": [
708
+ {
709
+ "type": "text",
710
+ "text": "I want to extract the key details from a rental contract."
711
+ }
712
+ ],
713
+ }
714
+ ],
715
+ extra_body={
716
+ "chat_template_kwargs": {
717
+ "mode": "template-generation"
718
+ }
719
+ }
720
+ )
721
+
722
+ print(response.choices[0].message.content)
723
+ ```
724
+
725
+ Example output:
726
+
727
+ ```
728
+ {
729
+ "contract_title": "verbatim-string",
730
+ "landlord": "verbatim-string",
731
+ "tenant": "verbatim-string",
732
+ "property_address": "verbatim-string",
733
+ "start_date": "date-time",
734
+ "end_date": "date-time",
735
+ "monthly_rent": "number",
736
+ "currency": "verbatim-string",
737
+ "deposit": "number",
738
+ "signatories": ["verbatim-string"]
739
+ }
740
+ ```
741
+
742
+ ## [https://huggingface.co/numind/NuExtract3#curl-examples](https://huggingface.co/numind/NuExtract3#curl-examples) Curl examples
743
+
744
+ The following examples assume that vLLM is running locally on port 8000. They use `jq` to build valid JSON request bodies without manually escaping the image data or template string.
745
+
746
+ ### [https://huggingface.co/numind/NuExtract3#single-image-structured-extraction](https://huggingface.co/numind/NuExtract3#single-image-structured-extraction) Single image structured extraction
747
+
748
+ ```
749
+ API_KEY="EMPTY"
750
+ IMAGE_BASE64_FILE=$(mktemp)
751
+ REQUEST_BODY_FILE=$(mktemp)
752
+
753
+ base64 < receipt.png | tr -d '\n' > "$IMAGE_BASE64_FILE"
754
+
755
+ TEMPLATE=$(cat <<'JSON'
756
+ {
757
+ "store": "verbatim-string",
758
+ "date": "date-time",
759
+ "total": "number",
760
+ "payment_method": "verbatim-string"
761
+ }
762
+ JSON
763
+ )
764
+
765
+ jq -n \
766
+ --rawfile image_base64 "$IMAGE_BASE64_FILE" \
767
+ --arg template "$TEMPLATE" \
768
+ '{
769
+ model: "numind/NuExtract3",
770
+ temperature: 0.6,
771
+ messages: [
772
+ {
773
+ role: "user",
774
+ content: [
775
+ {
776
+ type: "image_url",
777
+ image_url: {url: ("data:image/png;base64," + $image_base64)}
778
+ }
779
+ ]
780
+ }
781
+ ],
782
+ chat_template_kwargs: {
783
+ template: $template,
784
+ enable_thinking: false
785
+ }
786
+ }' > "$REQUEST_BODY_FILE"
787
+
788
+ curl http://localhost:8000/v1/chat/completions \
789
+ -H "Content-Type: application/json" \
790
+ -H "Authorization: Bearer $API_KEY" \
791
+ --data-binary "@$REQUEST_BODY_FILE"
792
+
793
+ rm "$IMAGE_BASE64_FILE" "$REQUEST_BODY_FILE"
794
+ ```
795
+
796
+ ### [https://huggingface.co/numind/NuExtract3#single-image-content-extraction](https://huggingface.co/numind/NuExtract3#single-image-content-extraction) Single image content extraction
797
+
798
+ ```
799
+ API_KEY="EMPTY"
800
+ IMAGE_BASE64_FILE=$(mktemp)
801
+ REQUEST_BODY_FILE=$(mktemp)
802
+
803
+ base64 < document.png | tr -d '\n' > "$IMAGE_BASE64_FILE"
804
+
805
+ jq -n \
806
+ --rawfile image_base64 "$IMAGE_BASE64_FILE" \
807
+ '{
808
+ model: "numind/NuExtract3",
809
+ temperature: 0.6,
810
+ messages: [
811
+ {
812
+ role: "user",
813
+ content: [
814
+ {
815
+ type: "image_url",
816
+ image_url: {url: ("data:image/png;base64," + $image_base64)}
817
+ }
818
+ ]
819
+ }
820
+ ],
821
+ chat_template_kwargs: {
822
+ mode: "content",
823
+ enable_thinking: false
824
+ }
825
+ }' > "$REQUEST_BODY_FILE"
826
+
827
+ curl http://localhost:8000/v1/chat/completions \
828
+ -H "Content-Type: application/json" \
829
+ -H "Authorization: Bearer $API_KEY" \
830
+ --data-binary "@$REQUEST_BODY_FILE"
831
+
832
+ rm "$IMAGE_BASE64_FILE" "$REQUEST_BODY_FILE"
833
+ ```
834
+
835
+ ## [https://huggingface.co/numind/NuExtract3#transformers-example](https://huggingface.co/numind/NuExtract3#transformers-example) Transformers example
836
+
837
+ You can also run NuExtract directly with `transformers`. The same `template`, `mode`, and `enable_thinking` options are passed to `processor.apply_chat_template`.
838
+
839
+ ```
840
+ import json
841
+
842
+ import torch
843
+ from PIL import Image
844
+ from transformers import AutoModelForImageTextToText, AutoProcessor
845
+
846
+ model_id = "numind/NuExtract3"
847
+
848
+ processor = AutoProcessor.from_pretrained(
849
+ model_id,
850
+ trust_remote_code=True,
851
+ )
852
+ model = AutoModelForImageTextToText.from_pretrained(
853
+ model_id,
854
+ dtype=torch.bfloat16,
855
+ device_map="auto",
856
+ trust_remote_code=True,
857
+ ).eval()
858
+
859
+ def run_nuextract(messages, **chat_template_kwargs):
860
+ inputs = processor.apply_chat_template(
861
+ messages,
862
+ add_generation_prompt=True,
863
+ tokenize=True,
864
+ return_dict=True,
865
+ return_tensors="pt",
866
+ **chat_template_kwargs,
867
+ ).to(model.device)
868
+
869
+ with torch.inference_mode():
870
+ generated_ids = model.generate(
871
+ **inputs,
872
+ max_new_tokens=4096,
873
+ do_sample=False,
874
+ )
875
+
876
+ generated_ids = generated_ids[:, inputs.input_ids.shape[1]:]
877
+ return processor.batch_decode(
878
+ generated_ids,
879
+ skip_special_tokens=True,
880
+ clean_up_tokenization_spaces=False,
881
+ )[0].strip()
882
+
883
+ # Single image structured extraction
884
+ receipt_image = Image.open("receipt.png").convert("RGB")
885
+ receipt_messages = [
886
+ {
887
+ "role": "user",
888
+ "content": [
889
+ {
890
+ "type": "image",
891
+ "image": receipt_image,
892
+ }
893
+ ],
894
+ }
895
+ ]
896
+
897
+ template = {
898
+ "store": "verbatim-string",
899
+ "date": "date-time",
900
+ "total": "number",
901
+ "payment_method": "verbatim-string"
902
+ }
903
+
904
+ structured_output = run_nuextract(
905
+ receipt_messages,
906
+ template=json.dumps(template, indent=4),
907
+ enable_thinking=False,
908
+ )
909
+ print(structured_output)
910
+
911
+ # Single image content extraction
912
+ document_image = Image.open("document.png").convert("RGB")
913
+ document_messages = [
914
+ {
915
+ "role": "user",
916
+ "content": [
917
+ {
918
+ "type": "image",
919
+ "image": document_image,
920
+ }
921
+ ],
922
+ }
923
+ ]
924
+
925
+ content_output = run_nuextract(
926
+ document_messages,
927
+ mode="content",
928
+ enable_thinking=False,
929
+ )
930
+ print(content_output)
931
+ ```
932
+
933
+ Special thanks to the Lambda.ai team for the compute that made this project a success.
934
+
935
+ ## [https://huggingface.co/numind/NuExtract3#citation](https://huggingface.co/numind/NuExtract3#citation) Citation
936
+
937
+ If you use NuExtract, please cite NuMind and link to the model page.
938
+
939
+ ```
940
+ @misc{nuextract3,
941
+ title = {NuExtract3},
942
+ author = {NuMind},
943
+ year = {2026},
944
+ url = {https://nuextract.ai/}
945
+ }
946
+ ```
947
+
948
+ Downloads last month 1,236,855
949
+
950
+ Safetensors[https://huggingface.co/docs/safetensors](https://huggingface.co/docs/safetensors)
951
+
952
+ Model size
953
+
954
+ 5B params
955
+
956
+ Tensor type
957
+
958
+ BF16
959
+
960
+ ·
961
+
962
+ Chat template
963
+
964
+ Files info
965
+
966
+ Inference Providers[NEW](https://huggingface.co/docs/inference-providers)
967
+
968
+ [Image-to-Text](https://huggingface.co/tasks/image-to-text)
969
+
970
+ This model isn't deployed by any Inference Provider.[🙋 3 Ask for provider support](https://huggingface.co/spaces/huggingface/InferenceSupport/discussions/10487)
971
+
972
+ ## Model tree for numind/NuExtract3[https://huggingface.co/docs/hub/model-cards#specifying-a-base-model](https://huggingface.co/docs/hub/model-cards#specifying-a-base-model)
973
+
974
+ Base model
975
+
976
+ [Qwen/Qwen3.5-4B-Base](https://huggingface.co/Qwen/Qwen3.5-4B-Base)
977
+
978
+ Finetuned
979
+
980
+ [Qwen/Qwen3.5-4B](https://huggingface.co/Qwen/Qwen3.5-4B)
981
+
982
+ Finetuned
983
+
984
+ ([392](https://huggingface.co/models?other=base_model:finetune:Qwen/Qwen3.5-4B) )
985
+
986
+ this model
987
+
988
+ Finetunes
989
+
990
+ [4 models](https://huggingface.co/models?other=base_model:finetune:numind/NuExtract3)
991
+
992
+ Quantizations
993
+
994
+ [11 models](https://huggingface.co/models?other=base_model:quantized:numind/NuExtract3)
995
+
996
+ ## Spaces using numind/NuExtract3 9
997
+
998
+ ## Collection including numind/NuExtract3
999
+
1000
+ ####
1001
+
1002
+ [NuExtract3 Collection 12 items • Updated May 26 • 15](https://huggingface.co/collections/numind/nuextract3)
1003
+
1004
+ ## Evaluation results [https://huggingface.co/docs/hub/eval-results](https://huggingface.co/docs/hub/eval-results)
1005
+
1006
+ - [allenai/olmOCR-bench](https://huggingface.co/datasets/allenai/olmOCR-bench) · Old Scans[View evaluation results](https://huggingface.co/numind/NuExtract3/blob/main/.eval_results/olmocrbench.yaml)
1007
+
1008
+ [source](https://github.com/davanstrien/ocr-bench/blob/99f7550c/experiments/olmocr-bench-oldscans/BENCHMARKING.md)[leaderboard](https://huggingface.co/datasets/allenai/olmOCR-bench?eval_result=numind/NuExtract3&leaderboard_task_id=old_scans)
1009
+
1010
+ 37.8 *
.agents/references/nuextract3/2026-07-20/huggingface.co/numind/NuExtract3/blob/main/README.md ADDED
@@ -0,0 +1,1001 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!--
2
+ source: https://huggingface.co/numind/NuExtract3/blob/main/README.md
3
+ retrieved: 2026-07-20T17:29:25.828402+00:00
4
+ final_url: https://huggingface.co/numind/NuExtract3/blob/main/README.md
5
+ content_type: text/html
6
+ -->
7
+
8
+ # README.md · numind/NuExtract3 at main
9
+
10
+ [Hugging Face](https://huggingface.co/)
11
+
12
+ #
13
+
14
+ [https://huggingface.co/numind](https://huggingface.co/numind)
15
+
16
+ [numind](https://huggingface.co/numind)
17
+
18
+ /
19
+
20
+ [NuExtract3](https://huggingface.co/numind/NuExtract3)
21
+
22
+ like 295
23
+
24
+ Follow
25
+
26
+ NuMind 649
27
+
28
+ [Image-to-Text](https://huggingface.co/models?pipeline_tag=image-to-text)
29
+
30
+ [Transformers](https://huggingface.co/models?library=transformers)
31
+
32
+ [Safetensors](https://huggingface.co/models?library=safetensors)
33
+
34
+ [qwen3_5](https://huggingface.co/models?other=qwen3_5)
35
+
36
+ [image-text-to-text](https://huggingface.co/models?other=image-text-to-text)
37
+
38
+ [vision-language](https://huggingface.co/models?other=vision-language)
39
+
40
+ [vlm](https://huggingface.co/models?other=vlm)
41
+
42
+ [document-understanding](https://huggingface.co/models?other=document-understanding)
43
+
44
+ [structured-extraction](https://huggingface.co/models?other=structured-extraction)
45
+
46
+ [information-extraction](https://huggingface.co/models?other=information-extraction)
47
+
48
+ [ocr](https://huggingface.co/models?other=ocr)
49
+
50
+ [document-to-markdown](https://huggingface.co/models?other=document-to-markdown)
51
+
52
+ [markdown](https://huggingface.co/models?other=markdown)
53
+
54
+ [rag](https://huggingface.co/models?other=rag)
55
+
56
+ [reasoning](https://huggingface.co/models?other=reasoning)
57
+
58
+ [multilingual](https://huggingface.co/models?other=multilingual)
59
+
60
+ [conversational](https://huggingface.co/models?other=conversational)
61
+
62
+ [Eval Results](https://huggingface.co/models?other=eval-results)
63
+
64
+ License: apache-2.0
65
+
66
+ [Model card](https://huggingface.co/numind/NuExtract3)
67
+
68
+ [Files Files and versions xet](https://huggingface.co/numind/NuExtract3/tree/main)
69
+
70
+ [Community 2](https://huggingface.co/numind/NuExtract3/discussions)
71
+
72
+ Deploy
73
+
74
+ Copy to bucket new
75
+
76
+ Use this model
77
+
78
+ main
79
+
80
+ [NuExtract3](https://huggingface.co/numind/NuExtract3/tree/main) / README.md
81
+
82
+ [NathanFradet](https://huggingface.co/NathanFradet)
83
+
84
+ Update README.md
85
+
86
+ [acaf70e](https://huggingface.co/numind/NuExtract3/commit/acaf70ecff9c3dbbfcbae651b82b66a0d8dbd0c6) verified about 2 months ago
87
+
88
+ [preview](https://huggingface.co/numind/NuExtract3/blob/main/README.md)[code](https://huggingface.co/numind/NuExtract3/blob/main/README.md?code=true)
89
+
90
+ |
91
+
92
+ [Raw](https://huggingface.co/numind/NuExtract3/raw/main/README.md)
93
+
94
+ Download with hf CLI
95
+
96
+ Copy download link
97
+
98
+ [History](https://huggingface.co/numind/NuExtract3/commits/main/README.md)[Blame](https://huggingface.co/numind/NuExtract3/blame/main/README.md)[Contribute](https://huggingface.co/numind/NuExtract3/edit/main/README.md)[Delete](https://huggingface.co/numind/NuExtract3/delete/main/README.md)
99
+
100
+ Safe
101
+
102
+ 25.5 kB
103
+
104
+ [metadata](https://huggingface.co/docs/hub/model-cards#model-card-metadata)
105
+
106
+ ```
107
+ license: apache-2.0
108
+ license_link: https://huggingface.co/numind/NuExtract3/blob/main/LICENSE
109
+ library_name: transformers
110
+ pipeline_tag: image-to-text
111
+ tags:
112
+ - image-text-to-text
113
+ - transformers
114
+ - safetensors
115
+ - qwen3_5
116
+ - vision-language
117
+ - vlm
118
+ - document-understanding
119
+ - structured-extraction
120
+ - information-extraction
121
+ - ocr
122
+ - document-to-markdown
123
+ - markdown
124
+ - rag
125
+ - reasoning
126
+ - multilingual
127
+ - conversational
128
+ base_model:
129
+ - Qwen/Qwen3.5-4B
130
+ model_name: NuExtract3
131
+ ```
132
+
133
+ [https://nuextract.ai/](https://nuextract.ai/)
134
+
135
+ 🖥️ [API / Platform](https://nuextract.ai/) | 📑 [Blog](https://numind.ai/blog) | 🗣️ [Discord](https://discord.gg/3tsEtJNCDe) | 🛠️ [GitHub](https://github.com/numindai/nuextract)
136
+
137
+ NuExtract3 is a unified 4B vision-language reasoning model for document understanding.
138
+
139
+ It combines strong structured information extraction with high-quality image-to-Markdown conversion, making it suitable for extraction pipelines, OCR, and RAG preprocessing for all types of documents such as scans, receipts, forms, invoices, contracts or tables.
140
+
141
+ Try it out in [the 🤗 space!](https://huggingface.co/spaces/numind/NuExtract-3-4B)
142
+
143
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#overview](https://huggingface.co/numind/NuExtract3/blob/main/README.md#overview) Overview
144
+
145
+ - Structured extraction : input (text/images) + JSON template + instructions --> JSON output
146
+
147
+ - Markdown conversion : input (text/images) --> Markdown
148
+
149
+ - Multimodal inputs : text, images, or text + images.
150
+
151
+ - Multilingual documents.
152
+
153
+ - Reasoning and non-reasoning inference modes.
154
+
155
+ - Template generation for structured extraction from natural language or input document.
156
+
157
+ # [https://huggingface.co/numind/NuExtract3/blob/main/README.md#benchmark-results](https://huggingface.co/numind/NuExtract3/blob/main/README.md#benchmark-results) Benchmark results
158
+
159
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#structured-extraction](https://huggingface.co/numind/NuExtract3/blob/main/README.md#structured-extraction) Structured Extraction
160
+
161
+ We benchmarked NuExtract on NuMind's internal structured benchmark, measuring model's performances on ~600 documents of diverse types including invoices, movie posters or floor plans. These documents and their ground-truth cover diverse use-cases testing model visual understanding, OCR, reasoning and ability to handle long input and output contexts. We plan to open-source this benchmark in the coming weeks, along with a extensive leaderboard including most popular open-weight and closed-sourced APIs and a Python library allowing to easily measure model performances on structured extraction.
162
+
163
+ To measure a pair of predicted and ground-truth JSONs, we represent both as trees which we align based on node names, compute metric scores for aligned leaves and report the average of these scores. `string` and `verbatim-string` leaves are evaluated with indel distance (i.e. Levenshtein without replacement), while all others are evaluated with exact-match. Models were evaluated using vllm, with a temperature of 0.25 and a maximum of 65000 output token (for both thinking and answer), which largely exceeds 22000 which is the number of tokens of the largest ground truth output.
164
+
165
+ Model name Average score Num. failed⁽¹⁾ Avg. num tokens thinking Avg. num tokens answer NuExtract3.4_4B-RL 0.651 ± 0.019 27 2036 1856 gemma-4-E4B-it 0.538 ± 0.023 31 3005 1287 Qwen3.5-9B 0.479 ± 0.030 170 22409 1257 Qwen3.5-4B 0.417 ± 0.031 229 27177 1201 GLM-4.6V-Flash 0.435 ± 0.026 153 2989 1357 Nemotron-3-Nano-Omni 0.387 ± 0.028 204 25827 522 Ministral-3-3B 0.240 ± 0.022 344 27586 362
166
+
167
+ (1) number of model outputs that were not JSON deserializable, either directly or by removing leading and trailing backticks.
168
+ 95% confidence intervals computed using a nonparametric bootstrap over scores distributions.
169
+
170
+ The benchmark include samples containing multiple images resulting in large input context, and some with ground-truth containing large numbers of items to extract resulting in large outputs. We found that the reasoning of small models significantly negatively impact their performances. The reason is that many models ended up falling in repetition loops, hitting the output tokens limit and resulting in failed requests.
171
+
172
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#document-to-markdown](https://huggingface.co/numind/NuExtract3/blob/main/README.md#document-to-markdown) Document to Markdown
173
+
174
+ NuExtract can also convert document images into clean Markdown. Output will be Markdown for text (headers etc), HTML for tables, LaTeX for math and `<figure data-type="image" data-id="img_n"><img src="/NM-dev/model_card-A/resolve/main/img_n.png" alt="Detail description of the images"/> `
175
+
176
+ Modern, format-agnostic benchmarks for complex document understanding are limited, so we explored a new evaluation approach. We selected 100 documents with challenging layouts and tables, asked each model to convert them into a structured representation, then used Gemini 3 Flash to compare model outputs against the source document and choose the most accurate result. The rankings aligned with human votes, suggesting this is a promising method for evaluating document-to-Markdown capabilities. More details will be shared in an upcoming technical report. Here are some results:
177
+
178
+ ### [https://huggingface.co/numind/NuExtract3/blob/main/README.md#using-markdown-to-structured](https://huggingface.co/numind/NuExtract3/blob/main/README.md#using-markdown-to-structured) Using "Markdown-to-structured"
179
+
180
+ To add other evaluate references, we used our structured extraction benchmark to evaluate models in a two-step fashion: convert the benchmark inputs to Markdown, then use Qwen3.6 27B to perform the structured extraction task on them. Intuitively, it allows to evaluate how models achieve to keep the input document content and layout: good models will allow the "structured extractor" model to perform better scores.
181
+
182
+ # [https://huggingface.co/numind/NuExtract3/blob/main/README.md#using-nuextract](https://huggingface.co/numind/NuExtract3/blob/main/README.md#using-nuextract) Using NuExtract
183
+
184
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#structured-extraction-1](https://huggingface.co/numind/NuExtract3/blob/main/README.md#structured-extraction-1) Structured extraction
185
+
186
+ Structured extraction takes as inputs:
187
+
188
+ 1. An input document, which can be text, image, or both;
189
+
190
+ 2. A JSON template describing the information to extract;
191
+
192
+ 3. (Optional) Instructions, allowing to specify expected output formats or values, to provide with the `instructions` chat template kwarg;
193
+
194
+ 4. (Optional) In-Context Learning (ICL) examples.
195
+
196
+ ### [https://huggingface.co/numind/NuExtract3/blob/main/README.md#input-json-template](https://huggingface.co/numind/NuExtract3/blob/main/README.md#input-json-template) Input JSON template
197
+
198
+ NuExtract uses a input JSON template whose structure is identical to the output JSON. Its leaf values are specify the types of the output JSON leaves. For examples:
199
+
200
+ ```
201
+ {
202
+ "invoice_number": "verbatim-string",
203
+ "invoice_date": "date",
204
+ "total_amount": "number",
205
+ "currency": "currency",
206
+ "line_items": [
207
+ {
208
+ "description": "verbatim-string",
209
+ "item_type": ["electronics", "clothing", "vehicle", "furniture", "other"],
210
+ "quantity": "integer",
211
+ "unit_price": "number",
212
+ "total": "number"
213
+ }
214
+ ]
215
+ }
216
+ ```
217
+
218
+ Supported template types include:
219
+
220
+ - `verbatim-string`: extract text exactly as it appears in the document;
221
+
222
+ - `string`: generic string field, allowing abstraction or light paraphrasing;
223
+
224
+ - `integer`: whole number;
225
+
226
+ - `number`: integer or decimal number;
227
+
228
+ - `date-time`: ISO-8601 date, time or date-time;
229
+
230
+ - Other specific types such as `data`, `time`, `country`, `currency`, `email` and so on. [For more details, read the complete types specifications and examples](https://huggingface.co/numind/NuExtract3/blob/main/TYPES.md)
231
+
232
+ Template constructors:
233
+
234
+ - Arrays, for example `["string"]`;
235
+
236
+ - Enums, for example `["yes", "no", "maybe"]`;
237
+
238
+ - Multi-enums (multiple possible values), for example `[["A", "B", "C"]]`.
239
+
240
+ If the model does not find relevant information for a field, it returns `null` or `[]`.
241
+
242
+ ### [https://huggingface.co/numind/NuExtract3/blob/main/README.md#converting-json-schema--pydantic-models-to-nuextract-template](https://huggingface.co/numind/NuExtract3/blob/main/README.md#converting-json-schema--pydantic-models-to-nuextract-template) Converting JSON schema / Pydantic models to NuExtract template
243
+
244
+ Our Python SDK (`pip install numind`) offers a method to convert JSON schemas to NuExtract templates:
245
+
246
+ ```
247
+ from typing import Literal
248
+
249
+ from pydantic import Field, BaseModel
250
+ from numind.nuextract_utils import convert_json_schema_to_nuextract_template
251
+
252
+ class HotelBooking(BaseModel):
253
+ city: str
254
+ check_in_date: str = Field(description="date")
255
+ check_out_date: str = Field(description="date")
256
+ number_of_guests: int
257
+ room_type: Literal["single", "double", "suite"]
258
+
259
+ template, dropped_branches = convert_json_schema_to_nuextract_template(
260
+ HotelBooking.model_json_schema()
261
+ )
262
+
263
+ # {'check_in_date': 'date', 'check_out_date': 'date', 'city': 'string', 'number_of_guests': 'integer', 'room_type': ['single', 'double', 'suite']}
264
+ ```
265
+
266
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#document-to-markdown-1](https://huggingface.co/numind/NuExtract3/blob/main/README.md#document-to-markdown-1) Document-to-Markdown
267
+
268
+ NuExtract can also convert document images into clean Markdown. Output will be markdown for text (headers etc), html for tables, latex for mat and `<figure data-type="image" data-id="img_n"><img src="/NM-dev/model_card-A/resolve/main/img_n.png" alt="Detail description of the images"/> `
269
+
270
+ Markdown example:
271
+
272
+ ```
273
+ <figure data-type="image" data-id="img_1">
274
+ <img src="/numind/NuExtract3/resolve/main/img_1.png" alt="Logo of Mobilier 2000 with contact information: Tél.: (418) 275-4232, 1654, boul. Marcotte, Roberval (Qc) G8H 2P2"/>
275
+ </figure>
276
+
277
+ # COMMANDE
278
+ **NUMÉRO 72259**
279
+
280
+ 1
281
+
282
+ **Vendu à**
283
+ TREMBLAY ERIC
284
+ ERIC TREMBLAY
285
+ 348 BOUL. DE L'ANSE
286
+ ROBERVAL
287
+ G8H 1Y9
288
+
289
+ **Livré à**
290
+ TREMBLAY ERIC
291
+ ERIC TREMBLAY
292
+ 348 BOUL. DE L'ANSE
293
+ ROBERVAL
294
+ G8H 1Y9
295
+
296
+ <table>
297
+ <thead>
298
+ <tr>
299
+ <th># CLIENT</th>
300
+ <th>EXPÉDITEUR</th>
301
+ <th>TERME DE CRÉDIT</th>
302
+ <th>DATE</th>
303
+ </tr>
304
+ </thead>
305
+ <tbody>
306
+ <tr>
307
+ <td>2753133</td>
308
+ <td>Notre camion</td>
309
+ <td>à la livraison</td>
310
+ <td>22/06/2023</td>
311
+ </tr>
312
+ </tbody>
313
+ </table>
314
+
315
+ <table>
316
+ <thead>
317
+ <tr>
318
+ <th>NOM DU VENDEUR</th>
319
+ <th>VOTRE ÉCONOMIE !</th>
320
+ <th># COMMANDE</th>
321
+ </tr>
322
+ </thead>
323
+ <tbody>
324
+ <tr>
325
+ <td>Éric</td>
326
+ <td>0.00</td>
327
+ <td></td>
328
+ </tr>
329
+ </tbody>
330
+ </table>
331
+ ```
332
+
333
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#reasoning-and-non-reasoning-modes](https://huggingface.co/numind/NuExtract3/blob/main/README.md#reasoning-and-non-reasoning-modes) Reasoning and non-reasoning modes
334
+
335
+ NuExtract supports both reasoning and non-reasoning inference.
336
+
337
+ ### [https://huggingface.co/numind/NuExtract3/blob/main/README.md#non-thinking-mode](https://huggingface.co/numind/NuExtract3/blob/main/README.md#non-thinking-mode) Non-thinking mode
338
+
339
+ Use this for fast and deterministic extraction or Markdown conversion.
340
+
341
+ ```
342
+ enable_thinking = False
343
+ temperature = 0.2
344
+ ```
345
+
346
+ ### [https://huggingface.co/numind/NuExtract3/blob/main/README.md#thinking-mode](https://huggingface.co/numind/NuExtract3/blob/main/README.md#thinking-mode) Thinking mode
347
+
348
+ Use this for difficult documents, complex layouts, ambiguous fields, or cases where the document structure requires additional reasoning.
349
+
350
+ ```
351
+ enable_thinking = True
352
+ temperature = 0.6
353
+ ```
354
+
355
+ For production extraction workloads, we recommend starting with non-reasoning mode and enabling reasoning only for difficult examples.
356
+
357
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-deployment](https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-deployment) vLLM deployment
358
+
359
+ NuExtract can be served with vLLM using an OpenAI-compatible API.
360
+
361
+ ```
362
+ vllm serve numind/NuExtract3 \
363
+ --trust-remote-code \
364
+ --limit-mm-per-prompt '{"image": 99, "video": 0}' \
365
+ --chat-template-content-format openai \
366
+ --generation-config vllm \
367
+ --max-model-len 131072 \
368
+ --speculative-config '{"method": "qwen3_next_mtp", "num_speculative_tokens": 2}'
369
+ ```
370
+
371
+ ### [https://huggingface.co/numind/NuExtract3/blob/main/README.md#multi-token-prediction](https://huggingface.co/numind/NuExtract3/blob/main/README.md#multi-token-prediction) Multi Token Prediction
372
+
373
+ The deployment commands above enable Multi Token Prediction (MTP) through vLLM speculative decoding:
374
+
375
+ ```
376
+ --speculative-config '{"method": "qwen3_next_mtp", "num_speculative_tokens": 2}'
377
+ ```
378
+
379
+ MTP can improve decoding throughput without changing the OpenAI-compatible request payload. You can tune `num_speculative_tokens` for your hardware and workload, or remove `--speculative-config` if your vLLM version or environment does not support this speculative decoding method.
380
+
381
+ If you encounter memory issues, reduce the maximum model length and the maximum number of images:
382
+
383
+ ```
384
+ vllm serve numind/NuExtract-3 \
385
+ --trust-remote-code \
386
+ --limit-mm-per-prompt '{"image": 6, "video": 0}' \
387
+ --chat-template-content-format openai \
388
+ --generation-config vllm \
389
+ --max-model-len 16384 \
390
+ --speculative-config '{"method": "qwen3_next_mtp", "num_speculative_tokens": 2}'
391
+ ```
392
+
393
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-inference-structured-extraction-text](https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-inference-structured-extraction-text) vLLM inference: structured extraction: text
394
+
395
+ ```
396
+ import json
397
+ from openai import OpenAI
398
+
399
+ client = OpenAI(
400
+ api_key="EMPTY",
401
+ base_url="http://localhost:8000/v1",
402
+ )
403
+
404
+ template = {
405
+ "store": "verbatim-string",
406
+ "date": "date-time",
407
+ "total": "number",
408
+ "currency": ["USD", "EUR", "GBP", "JPY", "Other"],
409
+ "items": [
410
+ {
411
+ "name": "verbatim-string",
412
+ "price": "number"
413
+ }
414
+ ]
415
+ }
416
+
417
+ response = client.chat.completions.create(
418
+ model="numind/NuExtract3",
419
+ temperature=0.2,
420
+ messages=[
421
+ {
422
+ "role": "user",
423
+ "content": [
424
+ {
425
+ "type": "text",
426
+ "text": "Yesterday I bought apples and coffee at Trader Joe's for a total of $12.40."
427
+ }
428
+ ],
429
+ }
430
+ ],
431
+ extra_body={
432
+ "chat_template_kwargs": {
433
+ "template": json.dumps(template),
434
+ "instructions": "Specify the time for the `date` entry only if it is present, otherwise only output the date component.",
435
+ "enable_thinking": False
436
+ }
437
+ }
438
+ )
439
+
440
+ print(response.choices[0].message.content)
441
+ ```
442
+
443
+ Example output:
444
+
445
+ ```
446
+ {
447
+ "store": "Trader Joe's",
448
+ "date": null,
449
+ "total": 12.40,
450
+ "currency": "USD",
451
+ "items": [
452
+ {
453
+ "name": "apples",
454
+ "price": null
455
+ },
456
+ {
457
+ "name": "coffee",
458
+ "price": null
459
+ }
460
+ ]
461
+ }
462
+ ```
463
+
464
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-inference-structured-extraction-image](https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-inference-structured-extraction-image) vLLM inference: structured extraction: image
465
+
466
+ ```
467
+ import json
468
+ import base64
469
+ from openai import OpenAI
470
+
471
+ client = OpenAI(
472
+ api_key="EMPTY",
473
+ base_url="http://localhost:8000/v1",
474
+ )
475
+
476
+ def encode_image(image_path):
477
+ with open(image_path, "rb") as image_file:
478
+ return base64.b64encode(image_file.read()).decode("utf-8")
479
+
480
+ image_base64 = encode_image("receipt.png")
481
+ data_url = f"data:image/png;base64,{image_base64}"
482
+
483
+ template = {
484
+ "store": "verbatim-string",
485
+ "date": "date-time",
486
+ "total": "number",
487
+ "payment_method": "verbatim-string"
488
+ }
489
+
490
+ response = client.chat.completions.create(
491
+ model="numind/NuExtract3",
492
+ temperature=0.2,
493
+ messages=[
494
+ {
495
+ "role": "user",
496
+ "content": [
497
+ {
498
+ "type": "image_url",
499
+ "image_url": {"url": data_url}
500
+ }
501
+ ],
502
+ }
503
+ ],
504
+ extra_body={
505
+ "chat_template_kwargs": {
506
+ "template": json.dumps(template, indent=4),
507
+ "enable_thinking": False
508
+ }
509
+ }
510
+ )
511
+
512
+ print(response.choices[0].message.content)
513
+ ```
514
+
515
+ Example output:
516
+
517
+ ```
518
+ {
519
+ "store": "Trader Joe's",
520
+ "date": "2025-04-12",
521
+ "total": 42.85,
522
+ "payment_method": "Visa"
523
+ }
524
+ ```
525
+
526
+ ### [https://huggingface.co/numind/NuExtract3/blob/main/README.md#multiple-page-pdf](https://huggingface.co/numind/NuExtract3/blob/main/README.md#multiple-page-pdf) Multiple page PDF
527
+
528
+ You can render a PDF to one PNG image per page with PyMuPDF, then pass the images to vLLM in page order.
529
+
530
+ ```
531
+ import base64
532
+ import json
533
+
534
+ import fitz # pip install pymupdf
535
+ from openai import OpenAI
536
+
537
+ client = OpenAI(
538
+ api_key="EMPTY",
539
+ base_url="http://localhost:8000/v1",
540
+ )
541
+
542
+ def pdf_to_png_data_urls(pdf_path, dpi=170):
543
+ data_urls = []
544
+
545
+ with fitz.open(pdf_path) as doc:
546
+ for page in doc:
547
+ pix = page.get_pixmap(dpi=dpi, alpha=False)
548
+ png_bytes = pix.tobytes("png")
549
+ png_base64 = base64.b64encode(png_bytes).decode("utf-8")
550
+ data_urls.append(f"data:image/png;base64,{png_base64}")
551
+
552
+ return data_urls
553
+
554
+ data_urls = pdf_to_png_data_urls("invoice.pdf", dpi=170)
555
+
556
+ template = {
557
+ "invoice_number": "verbatim-string",
558
+ "invoice_date": "date",
559
+ "total": "number",
560
+ "currency": "currency",
561
+ "line_items": [
562
+ {
563
+ "description": "verbatim-string",
564
+ "quantity": "number",
565
+ "unit_price": "number",
566
+ "total": "number"
567
+ }
568
+ ]
569
+ }
570
+
571
+ response = client.chat.completions.create(
572
+ model="numind/NuExtract3",
573
+ temperature=0.2,
574
+ messages=[
575
+ {
576
+ "role": "user",
577
+ "content": [
578
+ {
579
+ "type": "image_url",
580
+ "image_url": {"url": data_url}
581
+ }
582
+ for data_url in data_urls
583
+ ],
584
+ }
585
+ ],
586
+ extra_body={
587
+ "chat_template_kwargs": {
588
+ "template": json.dumps(template, indent=4),
589
+ "enable_thinking": False
590
+ }
591
+ }
592
+ )
593
+
594
+ print(response.choices[0].message.content)
595
+ ```
596
+
597
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-inference-document-to-markdown](https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-inference-document-to-markdown) vLLM inference: document-to-Markdown
598
+
599
+ For Markdown OCR, use `mode="markdown"` or `mode="content"` without a template.
600
+
601
+ ```
602
+ import base64
603
+ from openai import OpenAI
604
+
605
+ client = OpenAI(
606
+ api_key="EMPTY",
607
+ base_url="http://localhost:8000/v1",
608
+ )
609
+
610
+ def encode_image(image_path):
611
+ with open(image_path, "rb") as image_file:
612
+ return base64.b64encode(image_file.read()).decode("utf-8")
613
+
614
+ image_base64 = encode_image("document.png")
615
+ data_url = f"data:image/png;base64,{image_base64}"
616
+
617
+ response = client.chat.completions.create(
618
+ model="numind/NuExtract3",
619
+ temperature=1,
620
+ messages=[
621
+ {
622
+ "role": "user",
623
+ "content": [
624
+ {
625
+ "type": "image_url",
626
+ "image_url": {"url": data_url}
627
+ }
628
+ ],
629
+ }
630
+ ],
631
+ extra_body={
632
+ "chat_template_kwargs": {
633
+ "mode": "markdown",
634
+ "enable_thinking": False
635
+ }
636
+ }
637
+ )
638
+
639
+ print(response.choices[0].message.content)
640
+ ```
641
+
642
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-inference-reasoning-mode](https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-inference-reasoning-mode) vLLM inference: reasoning mode
643
+
644
+ Reasoning can be enabled for harder structured extraction or Markdown tasks.
645
+
646
+ ```
647
+ response = client.chat.completions.create(
648
+ model="numind/NuExtract3",
649
+ temperature=1,
650
+ messages=[
651
+ {
652
+ "role": "user",
653
+ "content": [
654
+ {
655
+ "type": "image_url",
656
+ "image_url": {"url": data_url}
657
+ }
658
+ ],
659
+ }
660
+ ],
661
+ extra_body={
662
+ "chat_template_kwargs": {
663
+ "mode": "markdown",
664
+ "enable_thinking": True
665
+ }
666
+ }
667
+ )
668
+
669
+ result = response.choices[0].message.content
670
+
671
+ if "</think>" in result:
672
+ reasoning, answer = [part.strip() for part in result.split("</think>")]
673
+ else:
674
+ reasoning, answer = None, result
675
+
676
+ print(answer)
677
+ ```
678
+
679
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#in-context-examples-for-extraction](https://huggingface.co/numind/NuExtract3/blob/main/README.md#in-context-examples-for-extraction) In-context examples for extraction
680
+
681
+ NuExtract supports in-context examples for structured extraction.
682
+
683
+ Examples are especially useful when the desired formatting is ambiguous or when the schema requires task-specific conventions. Examples can be provided by using `developer` messages, for which all items of the contents except the last one are the input, and the last one is the expected output.
684
+
685
+ ```
686
+ import json
687
+ from openai import OpenAI
688
+
689
+ client = OpenAI(
690
+ api_key="EMPTY",
691
+ base_url="http://localhost:8000/v1",
692
+ )
693
+
694
+ template = {
695
+ "names": ["string"]
696
+ }
697
+
698
+ response = client.chat.completions.create(
699
+ model="numind/NuExtract3",
700
+ temperature=0.2,
701
+ messages=[
702
+ {
703
+ "role": "developer",
704
+ "content": [
705
+ {
706
+ "type": "text",
707
+ "text": "Stephen is the manager at Susan's store.",
708
+ },
709
+ {
710
+ "type": "text",
711
+ "text": "{\"names\": [\"-STEPHEN-\", \"-SUSAN-\"]}",
712
+ }
713
+ ],
714
+ },
715
+ {
716
+ "role": "user",
717
+ "content": [
718
+ {
719
+ "type": "text",
720
+ "text": "John went to the restaurant with Mary. James went to the cinema."
721
+ }
722
+ ],
723
+ }
724
+ ],
725
+ extra_body={
726
+ "chat_template_kwargs": {
727
+ "template": json.dumps(template, indent=4),
728
+ "enable_thinking": False
729
+ }
730
+ }
731
+ )
732
+
733
+ print(response.choices[0].message.content)
734
+ ```
735
+
736
+ Example output:
737
+
738
+ ```
739
+ {
740
+ "names": ["-JOHN-", "-MARY-", "-JAMES-"]
741
+ }
742
+ ```
743
+
744
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-inference-template-generation](https://huggingface.co/numind/NuExtract3/blob/main/README.md#vllm-inference-template-generation) vLLM inference: template generation
745
+
746
+ NuExtract can generate an extraction template from a natural language description.
747
+
748
+ ```
749
+ from openai import OpenAI
750
+
751
+ client = OpenAI(
752
+ api_key="EMPTY",
753
+ base_url="http://localhost:8000/v1",
754
+ )
755
+
756
+ response = client.chat.completions.create(
757
+ model="numind/NuExtract3",
758
+ temperature=0.2,
759
+ messages=[
760
+ {
761
+ "role": "user",
762
+ "content": [
763
+ {
764
+ "type": "text",
765
+ "text": "I want to extract the key details from a rental contract."
766
+ }
767
+ ],
768
+ }
769
+ ],
770
+ extra_body={
771
+ "chat_template_kwargs": {
772
+ "mode": "template-generation"
773
+ }
774
+ }
775
+ )
776
+
777
+ print(response.choices[0].message.content)
778
+ ```
779
+
780
+ Example output:
781
+
782
+ ```
783
+ {
784
+ "contract_title": "verbatim-string",
785
+ "landlord": "verbatim-string",
786
+ "tenant": "verbatim-string",
787
+ "property_address": "verbatim-string",
788
+ "start_date": "date-time",
789
+ "end_date": "date-time",
790
+ "monthly_rent": "number",
791
+ "currency": "verbatim-string",
792
+ "deposit": "number",
793
+ "signatories": ["verbatim-string"]
794
+ }
795
+ ```
796
+
797
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#curl-examples](https://huggingface.co/numind/NuExtract3/blob/main/README.md#curl-examples) Curl examples
798
+
799
+ The following examples assume that vLLM is running locally on port 8000. They use `jq` to build valid JSON request bodies without manually escaping the image data or template string.
800
+
801
+ ### [https://huggingface.co/numind/NuExtract3/blob/main/README.md#single-image-structured-extraction](https://huggingface.co/numind/NuExtract3/blob/main/README.md#single-image-structured-extraction) Single image structured extraction
802
+
803
+ ```
804
+ API_KEY="EMPTY"
805
+ IMAGE_BASE64_FILE=$(mktemp)
806
+ REQUEST_BODY_FILE=$(mktemp)
807
+
808
+ base64 < receipt.png | tr -d '\n' > "$IMAGE_BASE64_FILE"
809
+
810
+ TEMPLATE=$(cat <<'JSON'
811
+ {
812
+ "store": "verbatim-string",
813
+ "date": "date-time",
814
+ "total": "number",
815
+ "payment_method": "verbatim-string"
816
+ }
817
+ JSON
818
+ )
819
+
820
+ jq -n \
821
+ --rawfile image_base64 "$IMAGE_BASE64_FILE" \
822
+ --arg template "$TEMPLATE" \
823
+ '{
824
+ model: "numind/NuExtract3",
825
+ temperature: 0.6,
826
+ messages: [
827
+ {
828
+ role: "user",
829
+ content: [
830
+ {
831
+ type: "image_url",
832
+ image_url: {url: ("data:image/png;base64," + $image_base64)}
833
+ }
834
+ ]
835
+ }
836
+ ],
837
+ chat_template_kwargs: {
838
+ template: $template,
839
+ enable_thinking: false
840
+ }
841
+ }' > "$REQUEST_BODY_FILE"
842
+
843
+ curl http://localhost:8000/v1/chat/completions \
844
+ -H "Content-Type: application/json" \
845
+ -H "Authorization: Bearer $API_KEY" \
846
+ --data-binary "@$REQUEST_BODY_FILE"
847
+
848
+ rm "$IMAGE_BASE64_FILE" "$REQUEST_BODY_FILE"
849
+ ```
850
+
851
+ ### [https://huggingface.co/numind/NuExtract3/blob/main/README.md#single-image-content-extraction](https://huggingface.co/numind/NuExtract3/blob/main/README.md#single-image-content-extraction) Single image content extraction
852
+
853
+ ```
854
+ API_KEY="EMPTY"
855
+ IMAGE_BASE64_FILE=$(mktemp)
856
+ REQUEST_BODY_FILE=$(mktemp)
857
+
858
+ base64 < document.png | tr -d '\n' > "$IMAGE_BASE64_FILE"
859
+
860
+ jq -n \
861
+ --rawfile image_base64 "$IMAGE_BASE64_FILE" \
862
+ '{
863
+ model: "numind/NuExtract3",
864
+ temperature: 0.6,
865
+ messages: [
866
+ {
867
+ role: "user",
868
+ content: [
869
+ {
870
+ type: "image_url",
871
+ image_url: {url: ("data:image/png;base64," + $image_base64)}
872
+ }
873
+ ]
874
+ }
875
+ ],
876
+ chat_template_kwargs: {
877
+ mode: "content",
878
+ enable_thinking: false
879
+ }
880
+ }' > "$REQUEST_BODY_FILE"
881
+
882
+ curl http://localhost:8000/v1/chat/completions \
883
+ -H "Content-Type: application/json" \
884
+ -H "Authorization: Bearer $API_KEY" \
885
+ --data-binary "@$REQUEST_BODY_FILE"
886
+
887
+ rm "$IMAGE_BASE64_FILE" "$REQUEST_BODY_FILE"
888
+ ```
889
+
890
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#transformers-example](https://huggingface.co/numind/NuExtract3/blob/main/README.md#transformers-example) Transformers example
891
+
892
+ You can also run NuExtract directly with `transformers`. The same `template`, `mode`, and `enable_thinking` options are passed to `processor.apply_chat_template`.
893
+
894
+ ```
895
+ import json
896
+
897
+ import torch
898
+ from PIL import Image
899
+ from transformers import AutoModelForImageTextToText, AutoProcessor
900
+
901
+ model_id = "numind/NuExtract3"
902
+
903
+ processor = AutoProcessor.from_pretrained(
904
+ model_id,
905
+ trust_remote_code=True,
906
+ )
907
+ model = AutoModelForImageTextToText.from_pretrained(
908
+ model_id,
909
+ dtype=torch.bfloat16,
910
+ device_map="auto",
911
+ trust_remote_code=True,
912
+ ).eval()
913
+
914
+ def run_nuextract(messages, **chat_template_kwargs):
915
+ inputs = processor.apply_chat_template(
916
+ messages,
917
+ add_generation_prompt=True,
918
+ tokenize=True,
919
+ return_dict=True,
920
+ return_tensors="pt",
921
+ **chat_template_kwargs,
922
+ ).to(model.device)
923
+
924
+ with torch.inference_mode():
925
+ generated_ids = model.generate(
926
+ **inputs,
927
+ max_new_tokens=4096,
928
+ do_sample=False,
929
+ )
930
+
931
+ generated_ids = generated_ids[:, inputs.input_ids.shape[1]:]
932
+ return processor.batch_decode(
933
+ generated_ids,
934
+ skip_special_tokens=True,
935
+ clean_up_tokenization_spaces=False,
936
+ )[0].strip()
937
+
938
+ # Single image structured extraction
939
+ receipt_image = Image.open("receipt.png").convert("RGB")
940
+ receipt_messages = [
941
+ {
942
+ "role": "user",
943
+ "content": [
944
+ {
945
+ "type": "image",
946
+ "image": receipt_image,
947
+ }
948
+ ],
949
+ }
950
+ ]
951
+
952
+ template = {
953
+ "store": "verbatim-string",
954
+ "date": "date-time",
955
+ "total": "number",
956
+ "payment_method": "verbatim-string"
957
+ }
958
+
959
+ structured_output = run_nuextract(
960
+ receipt_messages,
961
+ template=json.dumps(template, indent=4),
962
+ enable_thinking=False,
963
+ )
964
+ print(structured_output)
965
+
966
+ # Single image content extraction
967
+ document_image = Image.open("document.png").convert("RGB")
968
+ document_messages = [
969
+ {
970
+ "role": "user",
971
+ "content": [
972
+ {
973
+ "type": "image",
974
+ "image": document_image,
975
+ }
976
+ ],
977
+ }
978
+ ]
979
+
980
+ content_output = run_nuextract(
981
+ document_messages,
982
+ mode="content",
983
+ enable_thinking=False,
984
+ )
985
+ print(content_output)
986
+ ```
987
+
988
+ Special thanks to the Lambda.ai team for the compute that made this project a success.
989
+
990
+ ## [https://huggingface.co/numind/NuExtract3/blob/main/README.md#citation](https://huggingface.co/numind/NuExtract3/blob/main/README.md#citation) Citation
991
+
992
+ If you use NuExtract, please cite NuMind and link to the model page.
993
+
994
+ ```
995
+ @misc{nuextract3,
996
+ title = {NuExtract3},
997
+ author = {NuMind},
998
+ year = {2026},
999
+ url = {https://nuextract.ai/}
1000
+ }
1001
+ ```
AGENTS.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Repository implementation policy
2
+
3
+ - This repository has one supported execution path: the explicitly requested path.
4
+ - Never add error handling, `try`/`except`, recovery logic, fallback values, fallback providers, compatibility shims, silent substitution, or degraded modes.
5
+ - Never catch, suppress, translate, wrap, retry, or replace failures.
6
+ - Never add validation branches whose purpose is to produce a custom error path.
7
+ - Let failures surface naturally at their source.
8
+ - Do not preserve legacy behavior unless the user explicitly requests it.
9
+ - When a requested implementation cannot run as specified, report the blocker; do not create an alternate implementation.
app/config.py DELETED
@@ -1,2 +0,0 @@
1
- import os
2
- TRANSFORMERS_MODEL = os.environ.get("TRANSFORMERS_MODEL", "mradermacher/NuMarkdown-8B-Thinking-i1-GGUF")
 
 
 
app/nuextract.py CHANGED
@@ -1,7 +1,71 @@
1
- from transformers import AutoModel
2
- from app.config import TRANSFORMERS_MODEL
3
-
4
-
5
-
6
- model = AutoModel.from_pretrained(TRANSFORMERS_MODEL, dtype="auto")
7
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+
3
+ import torch
4
+ from PIL import Image
5
+ from transformers import AutoModelForImageTextToText, AutoProcessor
6
+
7
+ model_id = "numind/NuExtract3"
8
+
9
+ processor = AutoProcessor.from_pretrained(
10
+ model_id,
11
+ trust_remote_code=True,
12
+ )
13
+
14
+ model = AutoModelForImageTextToText.from_pretrained(
15
+ model_id,
16
+ dtype=torch.bfloat16,
17
+ device_map="auto",
18
+ trust_remote_code=True,
19
+ ).eval()
20
+
21
+
22
+ def run_nuextract(messages, **chat_template_kwargs):
23
+ inputs = processor.apply_chat_template(
24
+ messages,
25
+ add_generation_prompt=True,
26
+ tokenize=True,
27
+ return_dict=True,
28
+ return_tensors="pt",
29
+ **chat_template_kwargs,
30
+ ).to(model.device)
31
+
32
+ with torch.inference_mode():
33
+ generated_ids = model.generate(
34
+ **inputs,
35
+ max_new_tokens=4096,
36
+ do_sample=False,
37
+ )
38
+
39
+ generated_ids = generated_ids[:, inputs.input_ids.shape[1]:]
40
+ return processor.batch_decode(
41
+ generated_ids,
42
+ skip_special_tokens=True,
43
+ clean_up_tokenization_spaces=False,
44
+ )[0].strip()
45
+
46
+
47
+ sample_path = (
48
+ Path(__file__).parents[1]
49
+ / "data"
50
+ / "samples"
51
+ / "Screenshot 2026-07-18 171416.png"
52
+ )
53
+ sample_image = Image.open(sample_path).convert("RGB")
54
+ messages = [
55
+ {
56
+ "role": "user",
57
+ "content": [
58
+ {
59
+ "type": "image",
60
+ "image": sample_image,
61
+ }
62
+ ],
63
+ }
64
+ ]
65
+
66
+ output = run_nuextract(
67
+ messages,
68
+ mode="content",
69
+ enable_thinking=False,
70
+ )
71
+ print(output)
notebooks/NuExtract3.ipynb ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 3,
6
+ "id": "38837f4a",
7
+ "metadata": {},
8
+ "outputs": [
9
+ {
10
+ "data": {
11
+ "application/vnd.jupyter.widget-view+json": {
12
+ "model_id": "282526cb378b487a8b6743dcdbaaf51d",
13
+ "version_major": 2,
14
+ "version_minor": 0
15
+ },
16
+ "text/plain": [
17
+ "NuExtract3-Q4_K_M.gguf: 0%| | 0.00/2.71G [00:00<?, ?B/s]"
18
+ ]
19
+ },
20
+ "metadata": {},
21
+ "output_type": "display_data"
22
+ },
23
+ {
24
+ "ename": "ValueError",
25
+ "evalue": "Could not load model numind/NuExtract3-GGUF with any of the following classes: (<class 'transformers.models.auto.modeling_auto.AutoModelForImageTextToText'>, <class 'transformers.models.qwen3_5.modeling_qwen3_5.Qwen3_5ForConditionalGeneration'>). See the original errors:\n\nwhile loading with AutoModelForImageTextToText, an error is thrown:\nTraceback (most recent call last):\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\pipelines\\base.py\", line 240, in load_model\n model = model_class.from_pretrained(model, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\models\\auto\\auto_factory.py\", line 336, in from_pretrained\n config, kwargs = AutoConfig.from_pretrained(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\models\\auto\\configuration_auto.py\", line 376, in from_pretrained\n config_dict, unused_kwargs = PreTrainedConfig.get_config_dict(pretrained_model_name_or_path, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\configuration_utils.py\", line 721, in get_config_dict\n config_dict, kwargs = cls._get_config_dict(pretrained_model_name_or_path, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\configuration_utils.py\", line 807, in _get_config_dict\n config_dict = load_gguf_checkpoint(resolved_config_file, return_tensors=False)[\"config\"]\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\modeling_gguf_pytorch_utils.py\", line 590, in load_gguf_checkpoint\n if is_gguf_available() and is_torch_available():\n ^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\utils\\import_utils.py\", line 1207, in is_gguf_available\n return is_available and version.parse(gguf_version) >= version.parse(min_version)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\packaging\\version.py\", line 56, in parse\n return Version(version)\n ^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\packaging\\version.py\", line 202, in __init__\n raise InvalidVersion(f\"Invalid version: '{version}'\")\npackaging.version.InvalidVersion: Invalid version: 'N/A'\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\pipelines\\base.py\", line 256, in load_model\n model = model_class.from_pretrained(model, **fp32_kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\models\\auto\\auto_factory.py\", line 336, in from_pretrained\n config, kwargs = AutoConfig.from_pretrained(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\models\\auto\\configuration_auto.py\", line 376, in from_pretrained\n config_dict, unused_kwargs = PreTrainedConfig.get_config_dict(pretrained_model_name_or_path, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\configuration_utils.py\", line 721, in get_config_dict\n config_dict, kwargs = cls._get_config_dict(pretrained_model_name_or_path, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\configuration_utils.py\", line 807, in _get_config_dict\n config_dict = load_gguf_checkpoint(resolved_config_file, return_tensors=False)[\"config\"]\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\modeling_gguf_pytorch_utils.py\", line 590, in load_gguf_checkpoint\n if is_gguf_available() and is_torch_available():\n ^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\utils\\import_utils.py\", line 1207, in is_gguf_available\n return is_available and version.parse(gguf_version) >= version.parse(min_version)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\packaging\\version.py\", line 56, in parse\n return Version(version)\n ^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\packaging\\version.py\", line 202, in __init__\n raise InvalidVersion(f\"Invalid version: '{version}'\")\npackaging.version.InvalidVersion: Invalid version: 'N/A'\n\nwhile loading with Qwen3_5ForConditionalGeneration, an error is thrown:\nTraceback (most recent call last):\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\pipelines\\base.py\", line 240, in load_model\n model = model_class.from_pretrained(model, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\modeling_utils.py\", line 4281, in from_pretrained\n raise ValueError(\"accelerate is required when loading a GGUF file `pip install accelerate`.\")\nValueError: accelerate is required when loading a GGUF file `pip install accelerate`.\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\pipelines\\base.py\", line 256, in load_model\n model = model_class.from_pretrained(model, **fp32_kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\modeling_utils.py\", line 4281, in from_pretrained\n raise ValueError(\"accelerate is required when loading a GGUF file `pip install accelerate`.\")\nValueError: accelerate is required when loading a GGUF file `pip install accelerate`.\n\n\n",
26
+ "output_type": "error",
27
+ "traceback": [
28
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
29
+ "\u001b[31mValueError\u001b[39m Traceback (most recent call last)",
30
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[3]\u001b[39m\u001b[32m, line 6\u001b[39m\n\u001b[32m 2\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m transformers \u001b[38;5;28;01mimport\u001b[39;00m AutoConfig, AutoProcessor, pipeline\n\u001b[32m 3\u001b[39m \n\u001b[32m 4\u001b[39m config = AutoConfig.from_pretrained(\u001b[33m\"numind/NuExtract3\"\u001b[39m)\n\u001b[32m 5\u001b[39m processor = AutoProcessor.from_pretrained(\u001b[33m\"numind/NuExtract3\"\u001b[39m)\n\u001b[32m----> \u001b[39m\u001b[32m6\u001b[39m pipe = pipeline(\u001b[33m\"image-text-to-text\"\u001b[39m, model=\u001b[33m\"numind/NuExtract3-GGUF\"\u001b[39m, config=config, processor=processor, device_map=\u001b[33m\"auto\"\u001b[39m, model_kwargs={\u001b[33m\"gguf_file\"\u001b[39m: \u001b[33m\"NuExtract3-Q4_K_M.gguf\"\u001b[39m})\n\u001b[32m 7\u001b[39m image = str(next((Path.cwd() / \u001b[33m\"data\"\u001b[39m / \u001b[33m\"samples\"\u001b[39m).glob(\u001b[33m\"*.png\"\u001b[39m)))\n\u001b[32m 8\u001b[39m messages = [{\"role\": \"user\", \"content\": [\n\u001b[32m 9\u001b[39m {\u001b[33m\"type\"\u001b[39m: \u001b[33m\"image\"\u001b[39m, \u001b[33m\"url\"\u001b[39m: image},\n",
31
+ "\u001b[36mFile \u001b[39m\u001b[32ms:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\pipelines\\__init__.py:1033\u001b[39m, in \u001b[36mpipeline\u001b[39m\u001b[34m(task, model, config, tokenizer, feature_extractor, image_processor, video_processor, processor, revision, use_fast, token, device, device_map, dtype, trust_remote_code, model_kwargs, pipeline_class, **kwargs)\u001b[39m\n\u001b[32m 1031\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(model, \u001b[38;5;28mstr\u001b[39m):\n\u001b[32m 1032\u001b[39m model_classes = targeted_task[\u001b[33m\"\u001b[39m\u001b[33mpt\u001b[39m\u001b[33m\"\u001b[39m]\n\u001b[32m-> \u001b[39m\u001b[32m1033\u001b[39m model = \u001b[30;43mload_model\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 1034\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43madapter_path\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mif\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43madapter_path\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mis\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mnot\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mNone\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43;01melse\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43mmodel\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1035\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mmodel_classes\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mmodel_classes\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1036\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1037\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mtask\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mtask\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1038\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mhub_kwargs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1039\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mmodel_kwargs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1040\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 1042\u001b[39m hub_kwargs[\u001b[33m\"\u001b[39m\u001b[33m_commit_hash\u001b[39m\u001b[33m\"\u001b[39m] = model.config._commit_hash\n\u001b[32m 1044\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m pipeline_class \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n",
32
+ "\u001b[36mFile \u001b[39m\u001b[32ms:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\pipelines\\base.py:276\u001b[39m, in \u001b[36mload_model\u001b[39m\u001b[34m(model, config, model_classes, task, **model_kwargs)\u001b[39m\n\u001b[32m 274\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m class_name, trace \u001b[38;5;129;01min\u001b[39;00m all_traceback.items():\n\u001b[32m 275\u001b[39m error += \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mwhile loading with \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mclass_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m, an error is thrown:\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00mtrace\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m--> \u001b[39m\u001b[32m276\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[32m 277\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mCould not load model \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mmodel\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m with any of the following classes: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mclass_tuple\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m. See the original errors:\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00merror\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 278\u001b[39m )\n\u001b[32m 280\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m model\n",
33
+ "\u001b[31mValueError\u001b[39m: Could not load model numind/NuExtract3-GGUF with any of the following classes: (<class 'transformers.models.auto.modeling_auto.AutoModelForImageTextToText'>, <class 'transformers.models.qwen3_5.modeling_qwen3_5.Qwen3_5ForConditionalGeneration'>). See the original errors:\n\nwhile loading with AutoModelForImageTextToText, an error is thrown:\nTraceback (most recent call last):\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\pipelines\\base.py\", line 240, in load_model\n model = model_class.from_pretrained(model, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\models\\auto\\auto_factory.py\", line 336, in from_pretrained\n config, kwargs = AutoConfig.from_pretrained(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\models\\auto\\configuration_auto.py\", line 376, in from_pretrained\n config_dict, unused_kwargs = PreTrainedConfig.get_config_dict(pretrained_model_name_or_path, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\configuration_utils.py\", line 721, in get_config_dict\n config_dict, kwargs = cls._get_config_dict(pretrained_model_name_or_path, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\configuration_utils.py\", line 807, in _get_config_dict\n config_dict = load_gguf_checkpoint(resolved_config_file, return_tensors=False)[\"config\"]\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\modeling_gguf_pytorch_utils.py\", line 590, in load_gguf_checkpoint\n if is_gguf_available() and is_torch_available():\n ^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\utils\\import_utils.py\", line 1207, in is_gguf_available\n return is_available and version.parse(gguf_version) >= version.parse(min_version)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\packaging\\version.py\", line 56, in parse\n return Version(version)\n ^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\packaging\\version.py\", line 202, in __init__\n raise InvalidVersion(f\"Invalid version: '{version}'\")\npackaging.version.InvalidVersion: Invalid version: 'N/A'\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\pipelines\\base.py\", line 256, in load_model\n model = model_class.from_pretrained(model, **fp32_kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\models\\auto\\auto_factory.py\", line 336, in from_pretrained\n config, kwargs = AutoConfig.from_pretrained(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\models\\auto\\configuration_auto.py\", line 376, in from_pretrained\n config_dict, unused_kwargs = PreTrainedConfig.get_config_dict(pretrained_model_name_or_path, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\configuration_utils.py\", line 721, in get_config_dict\n config_dict, kwargs = cls._get_config_dict(pretrained_model_name_or_path, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\configuration_utils.py\", line 807, in _get_config_dict\n config_dict = load_gguf_checkpoint(resolved_config_file, return_tensors=False)[\"config\"]\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\modeling_gguf_pytorch_utils.py\", line 590, in load_gguf_checkpoint\n if is_gguf_available() and is_torch_available():\n ^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\utils\\import_utils.py\", line 1207, in is_gguf_available\n return is_available and version.parse(gguf_version) >= version.parse(min_version)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\packaging\\version.py\", line 56, in parse\n return Version(version)\n ^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\packaging\\version.py\", line 202, in __init__\n raise InvalidVersion(f\"Invalid version: '{version}'\")\npackaging.version.InvalidVersion: Invalid version: 'N/A'\n\nwhile loading with Qwen3_5ForConditionalGeneration, an error is thrown:\nTraceback (most recent call last):\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\pipelines\\base.py\", line 240, in load_model\n model = model_class.from_pretrained(model, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\modeling_utils.py\", line 4281, in from_pretrained\n raise ValueError(\"accelerate is required when loading a GGUF file `pip install accelerate`.\")\nValueError: accelerate is required when loading a GGUF file `pip install accelerate`.\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\pipelines\\base.py\", line 256, in load_model\n model = model_class.from_pretrained(model, **fp32_kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\transformers\\modeling_utils.py\", line 4281, in from_pretrained\n raise ValueError(\"accelerate is required when loading a GGUF file `pip install accelerate`.\")\nValueError: accelerate is required when loading a GGUF file `pip install accelerate`.\n\n\n"
34
+ ]
35
+ }
36
+ ],
37
+ "source": [
38
+ "from pathlib import Path\n",
39
+ "from transformers import AutoConfig, AutoProcessor, pipeline\n",
40
+ "\n",
41
+ "config = AutoConfig.from_pretrained(\"numind/NuExtract3\")\n",
42
+ "processor = AutoProcessor.from_pretrained(\"numind/NuExtract3\")\n",
43
+ "pipe = pipeline(\"image-text-to-text\", model=\"numind/NuExtract3-GGUF\", config=config, processor=processor, device_map=\"auto\", model_kwargs={\"gguf_file\": \"NuExtract3-Q4_K_M.gguf\"})\n",
44
+ "image = str(next((Path.cwd() / \"data\" / \"samples\").glob(\"*.png\")))\n",
45
+ "messages = [{\"role\": \"user\", \"content\": [\n",
46
+ " {\"type\": \"image\", \"url\": image},\n",
47
+ " {\"type\": \"text\", \"text\": \"Extract all document content as structured JSON.\"},\n",
48
+ "]}]\n",
49
+ "pipe(text=messages, max_new_tokens=4096)"
50
+ ]
51
+ }
52
+ ],
53
+ "metadata": {
54
+ "kernelspec": {
55
+ "display_name": ".venv",
56
+ "language": "python",
57
+ "name": "python3"
58
+ },
59
+ "language_info": {
60
+ "name": "python",
61
+ "version": "3.12.10"
62
+ }
63
+ },
64
+ "nbformat": 4,
65
+ "nbformat_minor": 5
66
+ }
notebooks/nuextract-2.0_inference.ipynb DELETED
@@ -1,876 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "markdown",
5
- "id": "cbe8b1f6-ec66-4fbc-a9f8-774acd8a255a",
6
- "metadata": {},
7
- "source": [
8
- "# NuExtract 2.0 Inference\n",
9
- "\n",
10
- "In this notebook we will provide examples of how to use the NuExtract 2.0 models for inference.\n",
11
- "\n",
12
- "First, let's load a model."
13
- ]
14
- },
15
- {
16
- "cell_type": "code",
17
- "execution_count": null,
18
- "id": "a561e38e-58e5-4f7e-80ee-eabc2d05e49a",
19
- "metadata": {
20
- "tags": []
21
- },
22
- "outputs": [
23
- {
24
- "name": "stderr",
25
- "output_type": "stream",
26
- "text": [
27
- "s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
28
- " from .autonotebook import tqdm as notebook_tqdm\n"
29
- ]
30
- }
31
- ],
32
- "source": [
33
- "import torch\n",
34
- "from transformers import AutoModelForImageTextToText, AutoProcessor\n",
35
- "model_name = \"numind/NuExtract-2.0-2B\"\n",
36
- "# model_name = \"numind/NuExtract-2.0-8B\"\n",
37
- "\n",
38
- "processor = AutoProcessor.from_pretrained(model_name,\n",
39
- " trust_remote_code=True,\n",
40
- " padding_side='left',\n",
41
- " use_fast=True)\n",
42
- "model = AutoModelForImageTextToText.from_pretrained(model_name,\n",
43
- " trust_remote_code=True,\n",
44
- " dtype=torch.bfloat16,\n",
45
- " attn_implementation=\"flash_attention_2\",\n",
46
- " device_map=\"auto\")\n",
47
- "\n",
48
- "# we choose greedy sampling here, which works well for most information extraction tasks\n",
49
- "generation_config = {\n",
50
- " \"do_sample\": False,\n",
51
- " \"num_beams\": 1,\n",
52
- " \"max_new_tokens\": 2048,\n",
53
- " \"temperature\": None,\n",
54
- " \"top_p\": None,\n",
55
- " \"top_k\": None\n",
56
- "}"
57
- ]
58
- },
59
- {
60
- "cell_type": "markdown",
61
- "id": "d739f582-16ad-4e52-b012-429757945a84",
62
- "metadata": {},
63
- "source": [
64
- "## Preparing Model Inputs\n",
65
- "\n",
66
- "Before using the model, we also need to make sure our prompts are properly formatted to work with NuExtract. NuExtract expects all input information to come as a single user chat prompt, formatted as follows:\n",
67
- "\n",
68
- "```python\n",
69
- "f\"\"\"\n",
70
- "# Template:\n",
71
- "{template}\n",
72
- "# Context:\n",
73
- "{text}\n",
74
- "\"\"\"\n",
75
- "```\n",
76
- "and if in-context examples are provided:\n",
77
- "```python\n",
78
- "f\"\"\"\n",
79
- "# Template:\n",
80
- "{template}\n",
81
- "# Examples\n",
82
- "## Input:\n",
83
- "{input1}\n",
84
- "## Output:\n",
85
- "{output1}\n",
86
- "## Input:\n",
87
- "{input2}\n",
88
- "## Output:\n",
89
- "{output2}\n",
90
- "# Context:\n",
91
- "{text}\n",
92
- "\"\"\"\n",
93
- "```\n",
94
- "\n",
95
- "If you are working with image inputs, you should use image placeholders for `text`, `input1`, etc. Later, we will inject tokens representing the actual image content in the location of these placeholders.\n",
96
- "\n",
97
- "The following function can make this formatting more convenient for us (the model can actually do this for us internally, but we will use this function initially to illustrate how things work). "
98
- ]
99
- },
100
- {
101
- "cell_type": "code",
102
- "execution_count": null,
103
- "id": "58b017a4-4802-4afe-b2b4-8f034f572c24",
104
- "metadata": {
105
- "tags": []
106
- },
107
- "outputs": [],
108
- "source": [
109
- "def construct_messages(document, template, examples=None, image_placeholder=\"<|vision_start|><|image_pad|><|vision_end|>\"):\n",
110
- " \"\"\"\n",
111
- " Construct the individual NuExtract message texts, prior to chat template formatting.\n",
112
- " \"\"\"\n",
113
- " images = []\n",
114
- " # add few-shot examples if needed\n",
115
- " if examples is not None and len(examples) > 0:\n",
116
- " icl = \"# Examples:\\n\"\n",
117
- " for row in examples:\n",
118
- " example_input = row['input']\n",
119
- "\n",
120
- " if not isinstance(row['input'], str):\n",
121
- " example_input = image_placeholder\n",
122
- " images.append(row['input'])\n",
123
- "\n",
124
- " icl += f\"## Input:\\n{example_input}\\n## Output:\\n{row['output']}\\n\"\n",
125
- " else:\n",
126
- " icl = \"\"\n",
127
- "\n",
128
- " # if input document is an image, set text to an image placeholder\n",
129
- " text = document\n",
130
- " if not isinstance(document, str):\n",
131
- " text = image_placeholder\n",
132
- " images.append(document)\n",
133
- " text = f\"\"\"# Template:\\n{template}\\n{icl}# Context:\\n{text}\"\"\"\n",
134
- "\n",
135
- " messages = [\n",
136
- " {\n",
137
- " \"role\": \"system\",\n",
138
- " \"content\": \"You are NuExtract, an information extraction tool created by NuMind.\"\n",
139
- " },\n",
140
- " {\n",
141
- " \"role\": \"user\",\n",
142
- " \"content\": [{\"type\": \"text\", \"text\": text}] + images,\n",
143
- " }\n",
144
- " ]\n",
145
- " return messages"
146
- ]
147
- },
148
- {
149
- "cell_type": "markdown",
150
- "id": "e918d64e-bc31-4d68-8ef5-2d485b5e5745",
151
- "metadata": {},
152
- "source": [
153
- "## Inference\n",
154
- "### Basic Example\n",
155
- "\n",
156
- "Now we are ready to run the model!\n",
157
- "\n",
158
- "Let's start with a basic text-only example, where we want to extract peoples' names from a short text."
159
- ]
160
- },
161
- {
162
- "cell_type": "code",
163
- "execution_count": null,
164
- "id": "b1cad1ec-4753-4064-aca0-c89fec4b5bfd",
165
- "metadata": {
166
- "tags": []
167
- },
168
- "outputs": [],
169
- "source": [
170
- "from qwen_vl_utils import process_vision_info\n",
171
- "\n",
172
- "template = \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\"\n",
173
- "document = \"John went to the restaurant with Mary. James went to the cinema.\"\n",
174
- "\n",
175
- "# prepare the user message content\n",
176
- "messages = construct_messages(document, template)\n",
177
- "text = processor.apply_chat_template(\n",
178
- " messages, tokenize=False, add_generation_prompt=True,\n",
179
- ")\n",
180
- "\n",
181
- "image_inputs = process_vision_info(messages)[0]\n",
182
- "inputs = processor(\n",
183
- " text=[text],\n",
184
- " images=image_inputs,\n",
185
- " padding=True,\n",
186
- " return_tensors=\"pt\",\n",
187
- ").to(\"cuda\")"
188
- ]
189
- },
190
- {
191
- "cell_type": "markdown",
192
- "id": "6c0e4f92-94dd-4b37-8772-591246f9b71a",
193
- "metadata": {},
194
- "source": [
195
- "Our NuExtract message is now formatted in standard chat template formatting; the tokenized version (`inputs`) will be given directly to the model."
196
- ]
197
- },
198
- {
199
- "cell_type": "code",
200
- "execution_count": null,
201
- "id": "42bd5add-b48a-44e4-9843-2f187e768678",
202
- "metadata": {
203
- "tags": []
204
- },
205
- "outputs": [
206
- {
207
- "name": "stdout",
208
- "output_type": "stream",
209
- "text": [
210
- "<|im_start|>system\n",
211
- "You are NuExtract, an information extraction tool created by NuMind.<|im_end|>\n",
212
- "<|im_start|>user\n",
213
- "# Template:\n",
214
- "{\"names\": [\"verbatim-string\"]}\n",
215
- "# Context:\n",
216
- "John went to the restaurant with Mary. James went to the cinema.<|im_end|>\n",
217
- "<|im_start|>assistant\n",
218
- "\n"
219
- ]
220
- }
221
- ],
222
- "source": [
223
- "print(text)"
224
- ]
225
- },
226
- {
227
- "cell_type": "markdown",
228
- "id": "df0979a7-ea46-4d25-a838-baeaeeca6ee5",
229
- "metadata": {},
230
- "source": [
231
- "The other `image_inputs` are empty in this case because this is a text-only example."
232
- ]
233
- },
234
- {
235
- "cell_type": "code",
236
- "execution_count": null,
237
- "id": "e3493013-313f-4318-a813-d492732bb0c1",
238
- "metadata": {
239
- "tags": []
240
- },
241
- "outputs": [
242
- {
243
- "name": "stdout",
244
- "output_type": "stream",
245
- "text": [
246
- "None\n"
247
- ]
248
- }
249
- ],
250
- "source": [
251
- "print(image_inputs)"
252
- ]
253
- },
254
- {
255
- "cell_type": "markdown",
256
- "id": "0e300775-48a9-42fb-9c92-3e1ceb530101",
257
- "metadata": {},
258
- "source": [
259
- "Now let's actually run the model."
260
- ]
261
- },
262
- {
263
- "cell_type": "code",
264
- "execution_count": null,
265
- "id": "ed374bf3-1af1-4a3e-a9eb-9e2e7ca113b8",
266
- "metadata": {
267
- "tags": []
268
- },
269
- "outputs": [
270
- {
271
- "name": "stdout",
272
- "output_type": "stream",
273
- "text": [
274
- "['{\"names\": [\"John\", \"Mary\", \"James\"]}']\n"
275
- ]
276
- }
277
- ],
278
- "source": [
279
- "# Inference: Generation of the output\n",
280
- "generated_ids = model.generate(\n",
281
- " **inputs,\n",
282
- " **generation_config\n",
283
- ")\n",
284
- "generated_ids_trimmed = [\n",
285
- " out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)\n",
286
- "]\n",
287
- "output_text = processor.batch_decode(\n",
288
- " generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
289
- ")\n",
290
- "print(output_text)"
291
- ]
292
- },
293
- {
294
- "cell_type": "markdown",
295
- "id": "21d37d33-85ad-421c-a014-523ddbec5a2f",
296
- "metadata": {},
297
- "source": [
298
- "Alternatively, you can directly provide the template and in-context examples to `.apply_chat_template()`, rather then manually preparing the prompt via `construct_messages()`. This is a bit more convenient and so we will use this from now on."
299
- ]
300
- },
301
- {
302
- "cell_type": "code",
303
- "execution_count": null,
304
- "id": "27fecb60-b0eb-46bc-ba2d-ef6a32d04f0c",
305
- "metadata": {
306
- "tags": []
307
- },
308
- "outputs": [
309
- {
310
- "name": "stdout",
311
- "output_type": "stream",
312
- "text": [
313
- "<|im_start|>system\n",
314
- "You are NuExtract, an information extraction tool created by NuMind.<|im_end|>\n",
315
- "<|im_start|>user\n",
316
- "# Template:\n",
317
- "{\"names\": [\"verbatim-string\"]}\n",
318
- "# Context:\n",
319
- "John went to the restaurant with Mary. James went to the cinema.<|im_end|>\n",
320
- "<|im_start|>assistant\n",
321
- "\n",
322
- "['{\"names\": [\"John\", \"Mary\", \"James\"]}']\n"
323
- ]
324
- }
325
- ],
326
- "source": [
327
- "template = \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\"\n",
328
- "document = \"John went to the restaurant with Mary. James went to the cinema.\"\n",
329
- "\n",
330
- "# prepare the user message content\n",
331
- "messages = [{\"role\": \"user\", \"content\": document}]\n",
332
- "text = processor.tokenizer.apply_chat_template(\n",
333
- " messages,\n",
334
- " template=template, # template is specified here\n",
335
- " tokenize=False,\n",
336
- " add_generation_prompt=True,\n",
337
- ")\n",
338
- "\n",
339
- "print(text)\n",
340
- "\n",
341
- "image_inputs = process_vision_info(messages)[0]\n",
342
- "inputs = processor(\n",
343
- " text=[text],\n",
344
- " images=image_inputs,\n",
345
- " padding=True,\n",
346
- " return_tensors=\"pt\",\n",
347
- ").to(\"cuda\")\n",
348
- "\n",
349
- "# Inference: Generation of the output\n",
350
- "generated_ids = model.generate(\n",
351
- " **inputs,\n",
352
- " **generation_config\n",
353
- ")\n",
354
- "generated_ids_trimmed = [\n",
355
- " out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)\n",
356
- "]\n",
357
- "output_text = processor.batch_decode(\n",
358
- " generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
359
- ")\n",
360
- "\n",
361
- "print(output_text)"
362
- ]
363
- },
364
- {
365
- "cell_type": "markdown",
366
- "id": "c18002f2-447a-45c2-90f6-0ff5a63c5dc2",
367
- "metadata": {},
368
- "source": [
369
- "### In-Context Examples\n",
370
- "\n",
371
- "Sometimes the model might not perform as well as we want because our task is challenging or involves some degree of ambiguity. Alternatively, we may want the model to follow some specific formatting, or just give it a bit more help. In cases like this it can be valuable to provide \"in-context examples\" to help NuExtract better understand the task.\n",
372
- "\n",
373
- "To do so, we can provide a list `examples` to `apply_chat_template()` (or `construct_messages()`) which contains dictionaries of input/output pairs. In the example below, we show to the model that we want the extracted names to be in captial letters with `-` on either side (for the sake of illustration)."
374
- ]
375
- },
376
- {
377
- "cell_type": "code",
378
- "execution_count": null,
379
- "id": "146baa04-2206-4eca-86bd-66f94924e293",
380
- "metadata": {
381
- "tags": []
382
- },
383
- "outputs": [],
384
- "source": [
385
- "template = \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\"\n",
386
- "document = \"John went to the restaurant with Mary. James went to the cinema.\"\n",
387
- "examples = [\n",
388
- " {\n",
389
- " \"input\": \"Stephen is the manager at Susan's store.\",\n",
390
- " \"output\": \"\"\"{\"names\": [\"-STEPHEN-\", \"-SUSAN-\"]}\"\"\"\n",
391
- " }\n",
392
- "]\n",
393
- "\n",
394
- "messages = [{\"role\": \"user\", \"content\": document}]\n",
395
- "text = processor.tokenizer.apply_chat_template(\n",
396
- " messages,\n",
397
- " template=template,\n",
398
- " examples=examples, # examples provided here\n",
399
- " tokenize=False,\n",
400
- " add_generation_prompt=True,\n",
401
- ")\n",
402
- "\n",
403
- "image_inputs = process_vision_info(messages)[0]\n",
404
- "inputs = processor(\n",
405
- " text=[text],\n",
406
- " images=image_inputs,\n",
407
- " padding=True,\n",
408
- " return_tensors=\"pt\",\n",
409
- ").to(\"cuda\")"
410
- ]
411
- },
412
- {
413
- "cell_type": "markdown",
414
- "id": "1d2561cc-a662-4898-b226-41d12394fdb4",
415
- "metadata": {},
416
- "source": [
417
- "We can see below that the in-context example has now been included in the model prompt, specifically between the template and context components."
418
- ]
419
- },
420
- {
421
- "cell_type": "code",
422
- "execution_count": null,
423
- "id": "79b10e44-5bb4-40eb-af74-e013897948ea",
424
- "metadata": {
425
- "tags": []
426
- },
427
- "outputs": [
428
- {
429
- "name": "stdout",
430
- "output_type": "stream",
431
- "text": [
432
- "<|im_start|>system\n",
433
- "You are NuExtract, an information extraction tool created by NuMind.<|im_end|>\n",
434
- "<|im_start|>user\n",
435
- "# Template:\n",
436
- "{\"names\": [\"verbatim-string\"]}\n",
437
- "# Examples:\n",
438
- "## Input:\n",
439
- "Stephen is the manager at Susan's store.\n",
440
- "## Output:\n",
441
- "{\"names\": [\"-STEPHEN-\", \"-SUSAN-\"]}\n",
442
- "# Context:\n",
443
- "John went to the restaurant with Mary. James went to the cinema.<|im_end|>\n",
444
- "<|im_start|>assistant\n",
445
- "\n"
446
- ]
447
- }
448
- ],
449
- "source": [
450
- "print(text)"
451
- ]
452
- },
453
- {
454
- "cell_type": "code",
455
- "execution_count": null,
456
- "id": "3f1364bf-2588-4935-a478-7a85b87866fc",
457
- "metadata": {
458
- "tags": []
459
- },
460
- "outputs": [
461
- {
462
- "name": "stdout",
463
- "output_type": "stream",
464
- "text": [
465
- "['{\"names\": [\"-JOHN-\", \"-MARY-\", \"-JAMES-\"]}']\n"
466
- ]
467
- }
468
- ],
469
- "source": [
470
- "# Inference: Generation of the output\n",
471
- "generated_ids = model.generate(\n",
472
- " **inputs,\n",
473
- " **generation_config\n",
474
- ")\n",
475
- "generated_ids_trimmed = [\n",
476
- " out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)\n",
477
- "]\n",
478
- "output_text = processor.batch_decode(\n",
479
- " generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
480
- ")\n",
481
- "print(output_text)"
482
- ]
483
- },
484
- {
485
- "cell_type": "markdown",
486
- "id": "bc0c3ecf-80ee-40b7-b240-12b5a9e10042",
487
- "metadata": {},
488
- "source": [
489
- "To get even better performance, add multiple in-context examples to your input."
490
- ]
491
- },
492
- {
493
- "cell_type": "markdown",
494
- "id": "99faf117-18b7-4b5e-8cd0-b6f8941db581",
495
- "metadata": {
496
- "tags": []
497
- },
498
- "source": [
499
- "### Image Inputs\n",
500
- "\n",
501
- "If we want to give image inputs to NuExtract, instead of text, we simply provide a dictionary specifying the desired image file as the message content, instead of a string. E.g. `{\"type\": \"image\", \"image\": \"file://image.jpg\"}`.\n",
502
- "\n",
503
- "You can also specify an image URL (e.g. `{\"type\": \"image\", \"image\": \"http://path/to/your/image.jpg\"}`) or base64 encoding (e.g. `{\"type\": \"image\", \"image\": \"data:image;base64,/9j/...\"}`).\n",
504
- "\n",
505
- "First, we will need a modified version of `process_vision_info()` that handles image-based in-context examples as well as primary inputs."
506
- ]
507
- },
508
- {
509
- "cell_type": "code",
510
- "execution_count": null,
511
- "id": "1aaf1912-4f50-483f-a9a0-f5f57caf66e0",
512
- "metadata": {
513
- "tags": []
514
- },
515
- "outputs": [],
516
- "source": [
517
- "def process_all_vision_info(messages, examples=None):\n",
518
- " \"\"\"\n",
519
- " Process vision information from both messages and in-context examples, supporting batch processing.\n",
520
- "\n",
521
- " Args:\n",
522
- " messages: List of message dictionaries (single input) OR list of message lists (batch input)\n",
523
- " examples: Optional list of example dictionaries (single input) OR list of example lists (batch)\n",
524
- "\n",
525
- " Returns:\n",
526
- " A flat list of all images in the correct order:\n",
527
- " - For single input: example images followed by message images\n",
528
- " - For batch input: interleaved as (item1 examples, item1 input, item2 examples, item2 input, etc.)\n",
529
- " - Returns None if no images were found\n",
530
- " \"\"\"\n",
531
- " from qwen_vl_utils import process_vision_info, fetch_image\n",
532
- "\n",
533
- " # Helper function to extract images from examples\n",
534
- " def extract_example_images(example_item):\n",
535
- " if not example_item:\n",
536
- " return []\n",
537
- "\n",
538
- " # Handle both list of examples and single example\n",
539
- " examples_to_process = example_item if isinstance(example_item, list) else [example_item]\n",
540
- " images = []\n",
541
- "\n",
542
- " for example in examples_to_process:\n",
543
- " if isinstance(example.get('input'), dict) and example['input'].get('type') == 'image':\n",
544
- " images.append(fetch_image(example['input']))\n",
545
- "\n",
546
- " return images\n",
547
- "\n",
548
- " # Normalize inputs to always be batched format\n",
549
- " is_batch = messages and isinstance(messages[0], list)\n",
550
- " messages_batch = messages if is_batch else [messages]\n",
551
- " is_batch_examples = examples and isinstance(examples, list) and (isinstance(examples[0], list) or examples[0] is None)\n",
552
- " examples_batch = examples if is_batch_examples else ([examples] if examples is not None else None)\n",
553
- "\n",
554
- " # Ensure examples batch matches messages batch if provided\n",
555
- " if examples and len(examples_batch) != len(messages_batch):\n",
556
- " if not is_batch and len(examples_batch) == 1:\n",
557
- " # Single example set for a single input is fine\n",
558
- " pass\n",
559
- " else:\n",
560
- " raise ValueError(\"Examples batch length must match messages batch length\")\n",
561
- "\n",
562
- " # Process all inputs, maintaining correct order\n",
563
- " all_images = []\n",
564
- " for i, message_group in enumerate(messages_batch):\n",
565
- " # Get example images for this input\n",
566
- " if examples and i < len(examples_batch):\n",
567
- " input_example_images = extract_example_images(examples_batch[i])\n",
568
- " all_images.extend(input_example_images)\n",
569
- "\n",
570
- " # Get message images for this input\n",
571
- " input_message_images = process_vision_info(message_group)[0] or []\n",
572
- " all_images.extend(input_message_images)\n",
573
- "\n",
574
- " return all_images if all_images else None\n"
575
- ]
576
- },
577
- {
578
- "cell_type": "markdown",
579
- "id": "8db9904c-451d-4ff4-b776-5a557abc1437",
580
- "metadata": {},
581
- "source": [
582
- "In the example below, we give an image of a receipt from Trader Joe's (`data/1.jpg`) and ask the model to extract the name of the store. We also provide an ICL example of a receipt from Walmart (`data/0.jpg`)."
583
- ]
584
- },
585
- {
586
- "cell_type": "code",
587
- "execution_count": null,
588
- "id": "fd11e855-594b-4fd7-af01-7695a65b75a9",
589
- "metadata": {
590
- "tags": []
591
- },
592
- "outputs": [],
593
- "source": [
594
- "from pathlib import Path\n",
595
- "\n",
596
- "project_root = Path.cwd().parent if Path.cwd().name == \"notebooks\" else Path.cwd()\n",
597
- "samples_dir = project_root / \"data\" / \"samples\"\n",
598
- "sample_images = sorted(\n",
599
- " path for path in samples_dir.iterdir()\n",
600
- " if path.suffix.lower() in {\".jpg\", \".jpeg\", \".png\", \".webp\"}\n",
601
- ")\n",
602
- "if not sample_images:\n",
603
- " raise FileNotFoundError(f\"No supported images found in {samples_dir}\")\n",
604
- "\n",
605
- "# Pick a different entry from sample_images to use another file.\n",
606
- "selected_image = sample_images[0]\n",
607
- "input_image_uri = selected_image.resolve().as_uri()\n",
608
- "print(f\"Using input image: {selected_image}\")\n",
609
- "\n",
610
- "template = \"\"\"{\"store\": \"verbatim-string\"}\"\"\"\n",
611
- "document = {\"type\": \"image\", \"image\": input_image_uri}\n",
612
- "examples = [\n",
613
- " {\n",
614
- " \"input\": {\"type\": \"image\", \"image\": input_image_uri},\n",
615
- " \"output\": \"\"\"{\"store\": \"WALMART\"}\"\"\"\n",
616
- " }\n",
617
- "]\n",
618
- "\n",
619
- "messages = [{\"role\": \"user\", \"content\": [document]}]\n",
620
- "text = processor.tokenizer.apply_chat_template(\n",
621
- " messages,\n",
622
- " template=template,\n",
623
- " examples=examples,\n",
624
- " tokenize=False,\n",
625
- " add_generation_prompt=True,\n",
626
- ")\n",
627
- "\n",
628
- "image_inputs = process_all_vision_info(messages, examples)\n",
629
- "inputs = processor(\n",
630
- " text=[text],\n",
631
- " images=image_inputs,\n",
632
- " padding=True,\n",
633
- " return_tensors=\"pt\",\n",
634
- ").to(\"cuda\")"
635
- ]
636
- },
637
- {
638
- "cell_type": "markdown",
639
- "id": "f14a8e8a-a091-410a-a964-9d275b936e75",
640
- "metadata": {},
641
- "source": [
642
- "Just like in the text-only case above, our in-context example has been included in the prompt before the main context."
643
- ]
644
- },
645
- {
646
- "cell_type": "code",
647
- "execution_count": null,
648
- "id": "ba00c8d6-56e2-43fc-a646-61f693c6b51a",
649
- "metadata": {
650
- "tags": []
651
- },
652
- "outputs": [
653
- {
654
- "name": "stdout",
655
- "output_type": "stream",
656
- "text": [
657
- "<|im_start|>system\n",
658
- "You are NuExtract, an information extraction tool created by NuMind.<|im_end|>\n",
659
- "<|im_start|>user\n",
660
- "# Template:\n",
661
- "{\"store\": \"verbatim-string\"}\n",
662
- "# Examples:\n",
663
- "## Input:\n",
664
- "<|vision_start|><|image_pad|><|vision_end|>\n",
665
- "## Output:\n",
666
- "{\"store\": \"WALMART\"}\n",
667
- "# Context:\n",
668
- "<|vision_start|><|image_pad|><|vision_end|><|im_end|>\n",
669
- "<|im_start|>assistant\n",
670
- "\n"
671
- ]
672
- }
673
- ],
674
- "source": [
675
- "print(text)"
676
- ]
677
- },
678
- {
679
- "cell_type": "markdown",
680
- "id": "4820e6da-544b-431a-86fb-a6f9f0eb6bdd",
681
- "metadata": {},
682
- "source": [
683
- "Now if we look at `image_inputs` we will see that it contains actual images. When we pass this along with `text` to `processor()` it automatically encodes the images and injects a tokenized representation into the image placeholders within `text`."
684
- ]
685
- },
686
- {
687
- "cell_type": "code",
688
- "execution_count": null,
689
- "id": "1aa9fba6-eb58-4401-abc2-79b7fcd205c0",
690
- "metadata": {
691
- "tags": []
692
- },
693
- "outputs": [
694
- {
695
- "name": "stdout",
696
- "output_type": "stream",
697
- "text": [
698
- "[<PIL.Image.Image image mode=RGB size=588x896 at 0x7FDD295E5600>, <PIL.Image.Image image mode=RGB size=476x980 at 0x7FDD295E4460>]\n"
699
- ]
700
- }
701
- ],
702
- "source": [
703
- "print(image_inputs)"
704
- ]
705
- },
706
- {
707
- "cell_type": "code",
708
- "execution_count": null,
709
- "id": "fbed800c-519b-4ee1-90d9-7c27e383d904",
710
- "metadata": {
711
- "tags": []
712
- },
713
- "outputs": [
714
- {
715
- "name": "stdout",
716
- "output_type": "stream",
717
- "text": [
718
- "['{\"store\": \"TRADER JOE\\'S\"}']\n"
719
- ]
720
- }
721
- ],
722
- "source": [
723
- "# Inference: Generation of the output\n",
724
- "generated_ids = model.generate(\n",
725
- " **inputs,\n",
726
- " **generation_config\n",
727
- ")\n",
728
- "generated_ids_trimmed = [\n",
729
- " out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)\n",
730
- "]\n",
731
- "output_text = processor.batch_decode(\n",
732
- " generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
733
- ")\n",
734
- "print(output_text)"
735
- ]
736
- },
737
- {
738
- "cell_type": "markdown",
739
- "id": "c6beb77f-2de5-4846-b244-2cda27f5f553",
740
- "metadata": {
741
- "tags": []
742
- },
743
- "source": [
744
- "### Batched Inference\n",
745
- "\n",
746
- "Finally, we can run batched inference over a list of input examples, regardless of whether they contain text, images, and/or ICL examples."
747
- ]
748
- },
749
- {
750
- "cell_type": "code",
751
- "execution_count": null,
752
- "id": "947b8090-236c-4821-b295-5a8fb5247943",
753
- "metadata": {
754
- "tags": []
755
- },
756
- "outputs": [
757
- {
758
- "name": "stdout",
759
- "output_type": "stream",
760
- "text": [
761
- "{\"store_name\": \"WAL-MART\"}\n",
762
- "{\"store_name\": \"Walmart\"}\n",
763
- "{\"names\": [\"John\", \"Mary\", \"James\"]}\n",
764
- "{\"names\": [\"-JOHN-\", \"-MARY-\", \"-JAMES-\"]}\n"
765
- ]
766
- }
767
- ],
768
- "source": [
769
- "inputs = [\n",
770
- " # image input with no ICL examples\n",
771
- " {\n",
772
- " \"document\": {\"type\": \"image\", \"image\": input_image_uri},\n",
773
- " \"template\": \"\"\"{\"store_name\": \"verbatim-string\"}\"\"\",\n",
774
- " },\n",
775
- " # image input with 1 ICL example\n",
776
- " {\n",
777
- " \"document\": {\"type\": \"image\", \"image\": input_image_uri},\n",
778
- " \"template\": \"\"\"{\"store_name\": \"verbatim-string\"}\"\"\",\n",
779
- " \"examples\": [\n",
780
- " {\n",
781
- " \"input\": {\"type\": \"image\", \"image\": input_image_uri},\n",
782
- " \"output\": \"\"\"{\"store_name\": \"Trader Joe's\"}\"\"\",\n",
783
- " }\n",
784
- " ],\n",
785
- " },\n",
786
- " # text input with no ICL examples\n",
787
- " {\n",
788
- " \"document\": {\"type\": \"text\", \"text\": \"John went to the restaurant with Mary. James went to the cinema.\"},\n",
789
- " \"template\": \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\",\n",
790
- " },\n",
791
- " # text input with ICL example\n",
792
- " {\n",
793
- " \"document\": {\"type\": \"text\", \"text\": \"John went to the restaurant with Mary. James went to the cinema.\"},\n",
794
- " \"template\": \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\",\n",
795
- " \"examples\": [\n",
796
- " {\n",
797
- " \"input\": \"Stephen is the manager at Susan's store.\",\n",
798
- " \"output\": \"\"\"{\"names\": [\"-STEPHEN-\", \"-SUSAN-\"]}\"\"\"\n",
799
- " }\n",
800
- " ],\n",
801
- " },\n",
802
- "]\n",
803
- "\n",
804
- "# messages should be a list of lists for batch processing\n",
805
- "messages = [[{\"role\": \"user\", \"content\": [x['document']]}] for x in inputs]\n",
806
- "\n",
807
- "# apply chat template to each example individually\n",
808
- "texts = [\n",
809
- " processor.tokenizer.apply_chat_template(\n",
810
- " messages[i], # Now this is a list containing one message\n",
811
- " template=x['template'],\n",
812
- " examples=x.get('examples', None),\n",
813
- " tokenize=False,\n",
814
- " add_generation_prompt=True)\n",
815
- " for i, x in enumerate(inputs)\n",
816
- "]\n",
817
- "\n",
818
- "image_inputs = process_all_vision_info(messages, [x.get('examples') for x in inputs])\n",
819
- "inputs = processor(\n",
820
- " text=texts,\n",
821
- " images=image_inputs,\n",
822
- " padding=True,\n",
823
- " return_tensors=\"pt\",\n",
824
- ").to(\"cuda\")\n",
825
- "\n",
826
- "# Batch Inference\n",
827
- "generated_ids = model.generate(**inputs, **generation_config)\n",
828
- "generated_ids_trimmed = [\n",
829
- " out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)\n",
830
- "]\n",
831
- "output_texts = processor.batch_decode(\n",
832
- " generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
833
- ")\n",
834
- "for y in output_texts:\n",
835
- " print(y)"
836
- ]
837
- },
838
- {
839
- "cell_type": "code",
840
- "execution_count": null,
841
- "id": "cf456dcc-52ce-495d-872f-a641c6f40f7d",
842
- "metadata": {
843
- "tags": []
844
- },
845
- "outputs": [],
846
- "source": []
847
- }
848
- ],
849
- "metadata": {
850
- "environment": {
851
- "kernel": "conda-base-py",
852
- "name": "workbench-notebooks.m127",
853
- "type": "gcloud",
854
- "uri": "us-docker.pkg.dev/deeplearning-platform-release/gcr.io/workbench-notebooks:m127"
855
- },
856
- "kernelspec": {
857
- "display_name": ".venv",
858
- "language": "python",
859
- "name": "python3"
860
- },
861
- "language_info": {
862
- "codemirror_mode": {
863
- "name": "ipython",
864
- "version": 3
865
- },
866
- "file_extension": ".py",
867
- "mimetype": "text/x-python",
868
- "name": "python",
869
- "nbconvert_exporter": "python",
870
- "pygments_lexer": "ipython3",
871
- "version": "3.12.10"
872
- }
873
- },
874
- "nbformat": 4,
875
- "nbformat_minor": 5
876
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pyproject.toml CHANGED
@@ -5,12 +5,17 @@ description = "Add your description here"
5
  readme = "README.md"
6
  requires-python = ">=3.12,<3.13"
7
  dependencies = [
 
8
  "flash-attn",
 
9
  "gradio",
10
  "huggingface-hub",
11
- "qwen-vl-utils>=0.0.14",
12
- "torch==2.9.0",
13
- "transformers>=5.14.1",
 
 
 
14
  ]
15
 
16
  [tool.uv.sources]
 
5
  readme = "README.md"
6
  requires-python = ">=3.12,<3.13"
7
  dependencies = [
8
+ "accelerate>=1.14.0",
9
  "flash-attn",
10
+ "gguf>=0.19.0",
11
  "gradio",
12
  "huggingface-hub",
13
+ "ipykernel>=7.3.0",
14
+ "ipython>=9.15.0",
15
+ "ipywidgets>=8.1.8",
16
+ "openai>=2.46.0",
17
+ "torch==2.9.1",
18
+ "transformers",
19
  ]
20
 
21
  [tool.uv.sources]
uv.lock CHANGED
@@ -7,6 +7,24 @@ resolution-markers = [
7
  "sys_platform != 'emscripten' and sys_platform != 'win32'",
8
  ]
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  [[package]]
11
  name = "annotated-doc"
12
  version = "0.0.4"
@@ -39,20 +57,21 @@ wheels = [
39
  ]
40
 
41
  [[package]]
42
- name = "av"
43
- version = "18.0.0"
 
 
 
 
 
 
 
 
 
44
  source = { registry = "https://pypi.org/simple" }
45
- sdist = { url = "https://files.pythonhosted.org/packages/ae/a4/570a5a35c8638aba01e739925846c35fdd6b0756a15526766d0a4dd3b7df/av-18.0.0.tar.gz", hash = "sha256:4ef7e72c3d3a872584a1215173b16e0226811037f40dcdbf75992631098df1ba", size = 4340222, upload-time = "2026-07-02T06:37:58.907Z" }
46
  wheels = [
47
- { url = "https://files.pythonhosted.org/packages/18/4a/9e3463df030e063d757fa12f0f39be6541b45b06b5bad48c2ce361b924bf/av-18.0.0-cp311-abi3-macosx_11_0_x86_64.whl", hash = "sha256:149289d40e732a6e49c9530bc245b49d9964cfd1c8c9e06778703b7d5bba6b25", size = 22499354, upload-time = "2026-07-02T06:36:58.751Z" },
48
- { url = "https://files.pythonhosted.org/packages/77/b3/2576a44b4f39c7462ced4c17fec04c756f7b0f3c5cb940d124173e417d6a/av-18.0.0-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:35274c20d2ad3b4774fe632bcef2e34af79858ddf899352339cc3babbc13a484", size = 18175248, upload-time = "2026-07-02T06:37:01.741Z" },
49
- { url = "https://files.pythonhosted.org/packages/84/74/6732f17b96dc23fd23b876b2805435855abdc8a3b397142be4e581165de8/av-18.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4d683b7747a0ba9222b8a5f81e41db5f796e7f64473454ec4fe2548e083c2fa0", size = 33387843, upload-time = "2026-07-02T06:37:05.097Z" },
50
- { url = "https://files.pythonhosted.org/packages/6d/b9/7708c43fed7ae28b4a1bad060b4221e3334cd827cec24f7165902a6ac1f4/av-18.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ae56b40b6f8b067a8ad2dac664fbfbabac7f7a55b9a7bb031eb99289252bc017", size = 35536910, upload-time = "2026-07-02T06:37:08.806Z" },
51
- { url = "https://files.pythonhosted.org/packages/5a/94/eba99691d184f6a395a242d54dc370e2fd2265e95bbc98e2963a0fdbdd6c/av-18.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ea2e8ebbce521f21b55df9400e00d721623c9020ef158f5a188a96130be0743f", size = 38984619, upload-time = "2026-07-02T06:37:11.861Z" },
52
- { url = "https://files.pythonhosted.org/packages/c9/cf/0d7aee07fe16aa9ffdf96043c14bed5485a52c0dea4259de87aa306ecab4/av-18.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef96dabb3e50dac249913145dff5424b302b257fd95dcb64be3c7b7a8aef16d1", size = 34451176, upload-time = "2026-07-02T06:37:15.154Z" },
53
- { url = "https://files.pythonhosted.org/packages/76/92/810da80b12680d4c4fe235bd1b4003289be9213ac7f114b77b8ecf0e3b3e/av-18.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0f65518a184613e41536f29e8758c8e3d8293e46bf5bef108f04f925bbfa3f44", size = 36619869, upload-time = "2026-07-02T06:37:18.495Z" },
54
- { url = "https://files.pythonhosted.org/packages/11/85/0f121ff43dc5a70696676c98a8f1674e2fa787614c2abaacb15fa1a9bc99/av-18.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:aaf4d354d2beaa6651e4f92e54409a578bde64f79c0beef9a30b388d06f7c629", size = 27556236, upload-time = "2026-07-02T06:37:21.388Z" },
55
- { url = "https://files.pythonhosted.org/packages/8b/f6/2509754d4d2356abc6fc0ea3d57c12ade29bac23a1fb7fc215a53ca518fb/av-18.0.0-cp311-abi3-win_arm64.whl", hash = "sha256:adac2b3833b6cb9bd6cb52664a522b94db453615b3675b1dbb26e13fe1c80da6", size = 20221133, upload-time = "2026-07-02T06:37:23.88Z" },
56
  ]
57
 
58
  [[package]]
@@ -82,6 +101,29 @@ wheels = [
82
  { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
83
  ]
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  [[package]]
86
  name = "charset-normalizer"
87
  version = "3.4.9"
@@ -125,6 +167,46 @@ wheels = [
125
  { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
126
  ]
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  [[package]]
129
  name = "einops"
130
  version = "0.8.2"
@@ -134,6 +216,15 @@ wheels = [
134
  { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" },
135
  ]
136
 
 
 
 
 
 
 
 
 
 
137
  [[package]]
138
  name = "fastapi"
139
  version = "0.139.2"
@@ -186,6 +277,21 @@ wheels = [
186
  { url = "https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl", hash = "sha256:11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2", size = 203402, upload-time = "2026-04-29T20:42:36.842Z" },
187
  ]
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  [[package]]
190
  name = "gradio"
191
  version = "6.20.0"
@@ -344,6 +450,92 @@ wheels = [
344
  { url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2", size = 61538, upload-time = "2022-09-14T00:24:23.22Z" },
345
  ]
346
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  [[package]]
348
  name = "jinja2"
349
  version = "3.1.6"
@@ -356,6 +548,71 @@ wheels = [
356
  { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
357
  ]
358
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  [[package]]
360
  name = "markdown-it-py"
361
  version = "4.2.0"
@@ -387,6 +644,18 @@ wheels = [
387
  { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
388
  ]
389
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  [[package]]
391
  name = "mdurl"
392
  version = "0.1.2"
@@ -405,6 +674,15 @@ wheels = [
405
  { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
406
  ]
407
 
 
 
 
 
 
 
 
 
 
408
  [[package]]
409
  name = "networkx"
410
  version = "3.6.1"
@@ -419,22 +697,32 @@ name = "numarkapp"
419
  version = "0.1.0"
420
  source = { virtual = "." }
421
  dependencies = [
 
422
  { name = "flash-attn" },
 
423
  { name = "gradio" },
424
  { name = "huggingface-hub" },
425
- { name = "qwen-vl-utils" },
 
 
 
426
  { name = "torch" },
427
  { name = "transformers" },
428
  ]
429
 
430
  [package.metadata]
431
  requires-dist = [
 
432
  { name = "flash-attn", url = "https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.28/flash_attn-2.8.3+cu130torch2.9-cp312-cp312-win_amd64.whl" },
 
433
  { name = "gradio" },
434
  { name = "huggingface-hub" },
435
- { name = "qwen-vl-utils", specifier = ">=0.0.14" },
436
- { name = "torch", specifier = "==2.9.0", index = "https://download.pytorch.org/whl/cu130" },
437
- { name = "transformers", specifier = ">=5.14.1" },
 
 
 
438
  ]
439
 
440
  [[package]]
@@ -605,6 +893,25 @@ wheels = [
605
  { url = "https://files.pythonhosted.org/packages/86/91/8b486ba85f71a2859dd705a4ec6aab38c37a389b8b7f94343db027732999/nvidia_nvtx-13.0.39-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cddd2e08b35144f1000631c3880c9ebbcb8a2863d762e76f92d47d30ecaf87cc", size = 148037, upload-time = "2025-08-04T10:18:31.763Z" },
606
  ]
607
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
608
  [[package]]
609
  name = "orjson"
610
  version = "3.11.9"
@@ -658,6 +965,27 @@ wheels = [
658
  { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" },
659
  ]
660
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
661
  [[package]]
662
  name = "pillow"
663
  version = "12.3.0"
@@ -675,6 +1003,70 @@ wheels = [
675
  { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" },
676
  ]
677
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  [[package]]
679
  name = "pydantic"
680
  version = "2.13.4"
@@ -787,18 +1179,24 @@ wheels = [
787
  ]
788
 
789
  [[package]]
790
- name = "qwen-vl-utils"
791
- version = "0.0.14"
792
  source = { registry = "https://pypi.org/simple" }
793
  dependencies = [
794
- { name = "av" },
795
- { name = "packaging" },
796
- { name = "pillow" },
797
- { name = "requests" },
798
  ]
799
- sdist = { url = "https://files.pythonhosted.org/packages/b6/b1/ad4fc2260a3badd278b38d642f3b987412f1f6682f0ef2b31b0572d5caa8/qwen_vl_utils-0.0.14.tar.gz", hash = "sha256:9c7cad5ae803b3a10f8bb7194deb12aeacdd032f92f4224e880c73587a7346ad", size = 8453, upload-time = "2025-09-23T09:38:57.532Z" }
800
  wheels = [
801
- { url = "https://files.pythonhosted.org/packages/c4/43/80f67e0336cb2fc725f8e06f7fe35c1d0fe946f4d2b8b2175e797e07349e/qwen_vl_utils-0.0.14-py3-none-any.whl", hash = "sha256:5e28657bfd031e56bd447c5901b58ddfc3835285ed100f4c56580e0ade054e96", size = 8120, upload-time = "2025-09-23T09:38:56.297Z" },
 
 
 
 
 
 
 
 
 
802
  ]
803
 
804
  [[package]]
@@ -925,6 +1323,29 @@ wheels = [
925
  { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
926
  ]
927
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
928
  [[package]]
929
  name = "starlette"
930
  version = "1.3.1"
@@ -987,7 +1408,7 @@ wheels = [
987
 
988
  [[package]]
989
  name = "torch"
990
- version = "2.9.0+cu130"
991
  source = { registry = "https://download.pytorch.org/whl/cu130" }
992
  dependencies = [
993
  { name = "filelock" },
@@ -1015,9 +1436,26 @@ dependencies = [
1015
  { name = "typing-extensions" },
1016
  ]
1017
  wheels = [
1018
- { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.9.0%2Bcu130-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3aef05b6247261f4a7c440be9a052c4be36c673c6721920181a4ac9a66d6c2a2", upload-time = "2025-10-14T17:30:43Z" },
1019
- { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.9.0%2Bcu130-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:cc241ffb20428f6a44c299ca06b934445606cf1fa48f3b68ef3af0a04c86bc3b", upload-time = "2025-10-14T17:30:43Z" },
1020
- { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.9.0%2Bcu130-cp312-cp312-win_amd64.whl", hash = "sha256:b9979a7c0a1c9544a857fc2390ebc89938f116eaaf6a359a0d46597402ca51da", upload-time = "2025-10-14T17:30:55Z" },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1021
  ]
1022
 
1023
  [[package]]
@@ -1032,6 +1470,15 @@ wheels = [
1032
  { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351, upload-time = "2024-08-03T22:35:36.644Z" },
1033
  ]
1034
 
 
 
 
 
 
 
 
 
 
1035
  [[package]]
1036
  name = "transformers"
1037
  version = "5.14.1"
@@ -1054,11 +1501,11 @@ wheels = [
1054
 
1055
  [[package]]
1056
  name = "triton"
1057
- version = "3.5.0"
1058
  source = { registry = "https://pypi.org/simple" }
1059
  wheels = [
1060
- { url = "https://files.pythonhosted.org/packages/87/9b/30988039e1e84df7554fba24e6a734d2d0e847af33cabdf9b532b3c51456/triton-3.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da21fccceafc163e3a5e857abe34351ef76345af06cabf9637a914742671f0b", size = 159946647, upload-time = "2025-10-15T19:15:56.325Z" },
1061
- { url = "https://files.pythonhosted.org/packages/f5/3a/e991574f3102147b642e49637e0281e9bb7c4ba254edb2bab78247c85e01/triton-3.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9e71db82261c4ffa3921cd050cd5faa18322d2d405c30eb56084afaff3b0833", size = 170476535, upload-time = "2025-10-13T16:38:05.18Z" },
1062
  ]
1063
 
1064
  [[package]]
@@ -1127,3 +1574,21 @@ sdist = { url = "https://files.pythonhosted.org/packages/a2/65/b7c6c443ccc58678c
1127
  wheels = [
1128
  { url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" },
1129
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  "sys_platform != 'emscripten' and sys_platform != 'win32'",
8
  ]
9
 
10
+ [[package]]
11
+ name = "accelerate"
12
+ version = "1.14.0"
13
+ source = { registry = "https://pypi.org/simple" }
14
+ dependencies = [
15
+ { name = "huggingface-hub" },
16
+ { name = "numpy" },
17
+ { name = "packaging" },
18
+ { name = "psutil" },
19
+ { name = "pyyaml" },
20
+ { name = "safetensors" },
21
+ { name = "torch" },
22
+ ]
23
+ sdist = { url = "https://files.pythonhosted.org/packages/8d/75/94cd5d389649578aca399e5aa822637eec18319a1dadc400ffe2f9a7493f/accelerate-1.14.0.tar.gz", hash = "sha256:41b9c4377a54e0b460a959b0defa1b736e4ca0a2373252d9a539964c2afe3c8d", size = 412167, upload-time = "2026-06-11T13:45:52.326Z" }
24
+ wheels = [
25
+ { url = "https://files.pythonhosted.org/packages/a8/db/253133d7e7cb40d3af384bb2f5c0b4a2b7fdcffbc95c688cc67a20a3c103/accelerate-1.14.0-py3-none-any.whl", hash = "sha256:e94390c2863b873be18f623f9df48a0d8fe5eff13ea7f1a00092b0a7904888c6", size = 389246, upload-time = "2026-06-11T13:45:50.477Z" },
26
+ ]
27
+
28
  [[package]]
29
  name = "annotated-doc"
30
  version = "0.0.4"
 
57
  ]
58
 
59
  [[package]]
60
+ name = "appnope"
61
+ version = "0.1.4"
62
+ source = { registry = "https://pypi.org/simple" }
63
+ sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" }
64
+ wheels = [
65
+ { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" },
66
+ ]
67
+
68
+ [[package]]
69
+ name = "asttokens"
70
+ version = "3.0.2"
71
  source = { registry = "https://pypi.org/simple" }
72
+ sdist = { url = "https://files.pythonhosted.org/packages/25/1e/faf0f247f6f881b98fc4d6d07e14085cb89d13665084e6d6ac1dc2c03d0b/asttokens-3.0.2.tar.gz", hash = "sha256:3ecdbd8f2cc195f53ccada3a613538bb5f9ef6f6869129f13e03c30a677b8fe2", size = 63136, upload-time = "2026-07-12T03:31:49.084Z" }
73
  wheels = [
74
+ { url = "https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" },
 
 
 
 
 
 
 
 
75
  ]
76
 
77
  [[package]]
 
101
  { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
102
  ]
103
 
104
+ [[package]]
105
+ name = "cffi"
106
+ version = "2.1.0"
107
+ source = { registry = "https://pypi.org/simple" }
108
+ dependencies = [
109
+ { name = "pycparser", marker = "implementation_name != 'PyPy'" },
110
+ ]
111
+ sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" }
112
+ wheels = [
113
+ { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" },
114
+ { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" },
115
+ { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" },
116
+ { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" },
117
+ { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" },
118
+ { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" },
119
+ { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" },
120
+ { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" },
121
+ { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" },
122
+ { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" },
123
+ { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" },
124
+ { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" },
125
+ ]
126
+
127
  [[package]]
128
  name = "charset-normalizer"
129
  version = "3.4.9"
 
167
  { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
168
  ]
169
 
170
+ [[package]]
171
+ name = "comm"
172
+ version = "0.2.3"
173
+ source = { registry = "https://pypi.org/simple" }
174
+ sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" }
175
+ wheels = [
176
+ { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" },
177
+ ]
178
+
179
+ [[package]]
180
+ name = "debugpy"
181
+ version = "1.8.21"
182
+ source = { registry = "https://pypi.org/simple" }
183
+ sdist = { url = "https://files.pythonhosted.org/packages/f2/aa/12037145b7a56eaa5b29b41872f7a21b538e807e13f32c4d3c46e59be084/debugpy-1.8.21.tar.gz", hash = "sha256:a3c53278e84c94e11bd87c53970ec391d1a67396c8b22609fcac576520e611a6", size = 1697577, upload-time = "2026-06-01T19:30:35.156Z" }
184
+ wheels = [
185
+ { url = "https://files.pythonhosted.org/packages/a2/df/bf625547431a9cadc9f4cbfeda38866e2b17f6aed147b625377e87834449/debugpy-1.8.21-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:9f96713896f39c3dff0ee841f47320c3f2983d33c341e009361bb0ebc79adc4e", size = 2483609, upload-time = "2026-06-01T19:30:50.794Z" },
186
+ { url = "https://files.pythonhosted.org/packages/bf/09/59324b903599031ff9faaec1758292409f6561a0ec2492fe4b703327705a/debugpy-1.8.21-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:c193d474f0a211191f2b4449d2d06157c689013035bd952f3b617e0ef422b176", size = 3968900, upload-time = "2026-06-01T19:30:52.341Z" },
187
+ { url = "https://files.pythonhosted.org/packages/14/cd/27f65b805d7fe005c44e1a36b9183ecdfbcdbf9d3e721a5115d461ecc7ee/debugpy-1.8.21-cp312-cp312-win32.whl", hash = "sha256:4743373c1cac7f9e74a1b9915bf1dbe0e900eca657ffb170ae07ac8363205ae9", size = 5336340, upload-time = "2026-06-01T19:30:54.047Z" },
188
+ { url = "https://files.pythonhosted.org/packages/77/1d/c84e30c0c674184948b66f076ab271c01d940618a2824c23cd035a27bc20/debugpy-1.8.21-cp312-cp312-win_amd64.whl", hash = "sha256:bd7ba9dd3daa7c2f942c6ca8d4695a16bf9ac16b63615261c7982bc74f7ed20c", size = 5374751, upload-time = "2026-06-01T19:30:55.891Z" },
189
+ { url = "https://files.pythonhosted.org/packages/95/51/67e7cf11a53e40694f720457d5b3a1cdaaa3d5a9a633e482f225456b93ff/debugpy-1.8.21-py2.py3-none-any.whl", hash = "sha256:b1e37d333663c8851516a47364ef473da127f9caebe4417e6df6f5825a7e9a92", size = 5352888, upload-time = "2026-06-01T19:31:25.186Z" },
190
+ ]
191
+
192
+ [[package]]
193
+ name = "decorator"
194
+ version = "5.3.1"
195
+ source = { registry = "https://pypi.org/simple" }
196
+ sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" }
197
+ wheels = [
198
+ { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" },
199
+ ]
200
+
201
+ [[package]]
202
+ name = "distro"
203
+ version = "1.9.0"
204
+ source = { registry = "https://pypi.org/simple" }
205
+ sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
206
+ wheels = [
207
+ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
208
+ ]
209
+
210
  [[package]]
211
  name = "einops"
212
  version = "0.8.2"
 
216
  { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" },
217
  ]
218
 
219
+ [[package]]
220
+ name = "executing"
221
+ version = "2.2.1"
222
+ source = { registry = "https://pypi.org/simple" }
223
+ sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" }
224
+ wheels = [
225
+ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" },
226
+ ]
227
+
228
  [[package]]
229
  name = "fastapi"
230
  version = "0.139.2"
 
277
  { url = "https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl", hash = "sha256:11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2", size = 203402, upload-time = "2026-04-29T20:42:36.842Z" },
278
  ]
279
 
280
+ [[package]]
281
+ name = "gguf"
282
+ version = "0.19.0"
283
+ source = { registry = "https://pypi.org/simple" }
284
+ dependencies = [
285
+ { name = "numpy" },
286
+ { name = "pyyaml" },
287
+ { name = "requests" },
288
+ { name = "tqdm" },
289
+ ]
290
+ sdist = { url = "https://files.pythonhosted.org/packages/48/ae/17f1308ae45cd7b08ebb521747d5b23f4efc4d172038a4e228dd5106c3ff/gguf-0.19.0.tar.gz", hash = "sha256:dbadcd6cc7ccd44256f2229fe7c2dff5e8aa5cf0612ab987fd2b1a57e428923f", size = 111220, upload-time = "2026-05-06T13:04:03.667Z" }
291
+ wheels = [
292
+ { url = "https://files.pythonhosted.org/packages/b3/bb/d71d6da82763528c2c2ed6b59a9d6142c6595545a4c448e2085d155e88c2/gguf-0.19.0-py3-none-any.whl", hash = "sha256:70bcd10edfe697fb2dad6e40af2234b9d8ece9a41a99761405121ebda1c3c1cd", size = 118475, upload-time = "2026-05-06T13:04:02.588Z" },
293
+ ]
294
+
295
  [[package]]
296
  name = "gradio"
297
  version = "6.20.0"
 
450
  { url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2", size = 61538, upload-time = "2022-09-14T00:24:23.22Z" },
451
  ]
452
 
453
+ [[package]]
454
+ name = "ipykernel"
455
+ version = "7.3.0"
456
+ source = { registry = "https://pypi.org/simple" }
457
+ dependencies = [
458
+ { name = "appnope", marker = "sys_platform == 'darwin'" },
459
+ { name = "comm" },
460
+ { name = "debugpy" },
461
+ { name = "ipython" },
462
+ { name = "jupyter-client" },
463
+ { name = "jupyter-core" },
464
+ { name = "matplotlib-inline" },
465
+ { name = "nest-asyncio2" },
466
+ { name = "packaging" },
467
+ { name = "psutil" },
468
+ { name = "pyzmq" },
469
+ { name = "tornado" },
470
+ { name = "traitlets" },
471
+ ]
472
+ sdist = { url = "https://files.pythonhosted.org/packages/3d/c4/e4a38f579de4225a561305666f7541cdabb30075def2aa1ac17bd73c1fb5/ipykernel-7.3.0.tar.gz", hash = "sha256:9acaaaf97d16355166e4085afe9d225bfbdf2b7ef520f9df3be8f2b248275e09", size = 184899, upload-time = "2026-06-10T08:41:25.481Z" }
473
+ wheels = [
474
+ { url = "https://files.pythonhosted.org/packages/3d/02/77b271f5dc58bfbc0b577c877b2365d1ffea2afe66a80c13f2312820348c/ipykernel-7.3.0-py3-none-any.whl", hash = "sha256:897eb64da762549ef610698fca5e9675195ec6ac8ec7f19d81ce1ca20c876057", size = 120583, upload-time = "2026-06-10T08:41:23.648Z" },
475
+ ]
476
+
477
+ [[package]]
478
+ name = "ipython"
479
+ version = "9.15.0"
480
+ source = { registry = "https://pypi.org/simple" }
481
+ dependencies = [
482
+ { name = "colorama", marker = "sys_platform == 'win32'" },
483
+ { name = "decorator" },
484
+ { name = "ipython-pygments-lexers" },
485
+ { name = "jedi" },
486
+ { name = "matplotlib-inline" },
487
+ { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
488
+ { name = "prompt-toolkit" },
489
+ { name = "psutil", marker = "sys_platform != 'cygwin' and sys_platform != 'emscripten'" },
490
+ { name = "pygments" },
491
+ { name = "stack-data" },
492
+ { name = "traitlets" },
493
+ ]
494
+ sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" }
495
+ wheels = [
496
+ { url = "https://files.pythonhosted.org/packages/40/3a/948263ca3b9d65bb2b1b0c521b3a49fad5d59ada58724bd87d2bd5ff3f36/ipython-9.15.0-py3-none-any.whl", hash = "sha256:515ad9c3cdf0c932a5a9f6245419e8aba706b7bd03c3e1d3a1c83d9351d6aa6e", size = 630895, upload-time = "2026-06-26T11:03:33.809Z" },
497
+ ]
498
+
499
+ [[package]]
500
+ name = "ipython-pygments-lexers"
501
+ version = "1.1.1"
502
+ source = { registry = "https://pypi.org/simple" }
503
+ dependencies = [
504
+ { name = "pygments" },
505
+ ]
506
+ sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" }
507
+ wheels = [
508
+ { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" },
509
+ ]
510
+
511
+ [[package]]
512
+ name = "ipywidgets"
513
+ version = "8.1.8"
514
+ source = { registry = "https://pypi.org/simple" }
515
+ dependencies = [
516
+ { name = "comm" },
517
+ { name = "ipython" },
518
+ { name = "jupyterlab-widgets" },
519
+ { name = "traitlets" },
520
+ { name = "widgetsnbextension" },
521
+ ]
522
+ sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739, upload-time = "2025-11-01T21:18:12.393Z" }
523
+ wheels = [
524
+ { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" },
525
+ ]
526
+
527
+ [[package]]
528
+ name = "jedi"
529
+ version = "0.20.0"
530
+ source = { registry = "https://pypi.org/simple" }
531
+ dependencies = [
532
+ { name = "parso" },
533
+ ]
534
+ sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" }
535
+ wheels = [
536
+ { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" },
537
+ ]
538
+
539
  [[package]]
540
  name = "jinja2"
541
  version = "3.1.6"
 
548
  { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
549
  ]
550
 
551
+ [[package]]
552
+ name = "jiter"
553
+ version = "0.16.0"
554
+ source = { registry = "https://pypi.org/simple" }
555
+ sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431, upload-time = "2026-06-29T13:05:13.657Z" }
556
+ wheels = [
557
+ { url = "https://files.pythonhosted.org/packages/83/2b/52ace16ed031354f0539749a49e4bf33797d82bea5137910835fa4b09793/jiter-0.16.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:67c3bc1760f8c99d805dcab4e644027142a53b1d5d861f18780ebdbd5d40b72a", size = 306943, upload-time = "2026-06-29T13:03:14.035Z" },
558
+ { url = "https://files.pythonhosted.org/packages/94/2e/34957c2c1b661c252ba9bcc60ae0bddc27e0f7202c6073326a13c5390eec/jiter-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5af7780e4a26bd7d0d989592bf9ef12ebf806b74ab709223ecca37c749872ea9", size = 307779, upload-time = "2026-06-29T13:03:15.418Z" },
559
+ { url = "https://files.pythonhosted.org/packages/88/6c/59bd309cab4460c54cf1079f3eb7fe7af6a4c895c5c957a53378693bad2b/jiter-0.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5bf78d0e05e45cfdd66558893938d59afe3d1b1a824a202039b20e607d25a72", size = 335826, upload-time = "2026-06-29T13:03:17.11Z" },
560
+ { url = "https://files.pythonhosted.org/packages/3b/8c/f5ef7b65f0df47afa16596969defb281ebb86e96df346d62be6fd853d620/jiter-0.16.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4444a83f946605990c98f625cdd3d2725bfb818158760c5748c653170a20e0e", size = 362573, upload-time = "2026-06-29T13:03:18.781Z" },
561
+ { url = "https://files.pythonhosted.org/packages/2b/0b/ace4354da061ee38844a0c27dc2c21eecd27aea119e8da324bea987522d0/jiter-0.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a23f0e4f957e1be65752d2dfac9a5a06b1917af8dc85deb639c3b9d02e31290", size = 457979, upload-time = "2026-06-29T13:03:20.293Z" },
562
+ { url = "https://files.pythonhosted.org/packages/55/40/c0253d3772eb9dcd8e6606ee9b2d53ec8e5b814589c47f140aa585f21eaa/jiter-0.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c22a488f7b9218e245a0025a9ba6b100e2e54700831cf4cf16833a27fba3ad01", size = 372302, upload-time = "2026-06-29T13:03:21.739Z" },
563
+ { url = "https://files.pythonhosted.org/packages/a8/d2/4839422241aa12860ce597b20068727094ba0bc480723c74924ca5bad483/jiter-0.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46add52f4ad47a08bfb1219f3e673da972191489a33016edefdb5ea55bfa8c48", size = 343805, upload-time = "2026-06-29T13:03:23.384Z" },
564
+ { url = "https://files.pythonhosted.org/packages/e2/59/e196888a05befdda7dbe299b722d56f2f6eec65402bc34c0a3306d595feb/jiter-0.16.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9c8a956fd72c2cf1e730d01ea080341f13aa0a97a4a33b51abebe725b7ae9ca9", size = 351107, upload-time = "2026-06-29T13:03:24.815Z" },
565
+ { url = "https://files.pythonhosted.org/packages/ec/74/4cd9e0fca65232136400354b630fbfcd2de634e22ccbb96567725981b548/jiter-0.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:561926e0573ffe4a32498420a76d64b16c513e1ab413b9d28158a8764ac701e5", size = 388441, upload-time = "2026-06-29T13:03:26.266Z" },
566
+ { url = "https://files.pythonhosted.org/packages/d9/8c/554691e48bc711299c0a293dd8a6179e24b2d66a54dc295421fcf64569c0/jiter-0.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:44d019fa8cdaf89bf29c71b39e3712143fdd0ac76725c6ef954f9957a5ea8730", size = 516354, upload-time = "2026-06-29T13:03:28.02Z" },
567
+ { url = "https://files.pythonhosted.org/packages/a4/cb/01e9d69dc2cc6759d4f91e230b34489c4fdb2518992650633f9e20bece89/jiter-0.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0df91907609837f33341b8e6fe73b95991fdaa57caf1a0fbd343dffe826f386f", size = 547880, upload-time = "2026-06-29T13:03:29.534Z" },
568
+ { url = "https://files.pythonhosted.org/packages/79/70/2953195f1c6ad00f49fa67e13df7e60acb3dd4f387101bc15abccddd905e/jiter-0.16.0-cp312-cp312-win32.whl", hash = "sha256:51d7b836acb0108d7c77df1742332cac2a1fa04a74d6dacec46e7091f0e91274", size = 203473, upload-time = "2026-06-29T13:03:31.025Z" },
569
+ { url = "https://files.pythonhosted.org/packages/2d/05/2909a8b10699a4d560f8c502b6b2c5f3991b682b1922c1eedda242b225bd/jiter-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1878349266f8ee36ecb1375cc5ba2f115f35fd9f0a1a4119e725e379126647f7", size = 196905, upload-time = "2026-06-29T13:03:32.472Z" },
570
+ { url = "https://files.pythonhosted.org/packages/e9/a9/6b82bb1c8d7790d602489b967b982a909e5d092875a6c2ade96444c8dfc5/jiter-0.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:2ed5738ae4af18271a51a528b8811b0cbfa4a1858de9d83359e4169855d6a331", size = 190618, upload-time = "2026-06-29T13:03:34.672Z" },
571
+ { url = "https://files.pythonhosted.org/packages/98/ab/664fd8c4be028b2bedd3d2ff08769c4ede23d0dbc87a77c62384a0515b5d/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:f17d61a28b4b3e0e3e2ba98490c70501403b4d196f78732439160e7fd3678127", size = 303106, upload-time = "2026-06-29T13:05:07.118Z" },
572
+ { url = "https://files.pythonhosted.org/packages/1a/07/421f1d5b65493a76e16027b848aba6a7d28073ae75944fa4289cc914d39f/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:96e38eea538c8ddf853a35727c7be0741c76c13f04148ac5c116222f50ece3b3", size = 304658, upload-time = "2026-06-29T13:05:08.708Z" },
573
+ { url = "https://files.pythonhosted.org/packages/0a/db/bba1155f01a01c3c37a89425d571da751bbedf5c54247b831a04cb971798/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d284fb8d94d5855d60c44fefcab4bf966f1da6fada73992b01f6f0c9bc0c6702", size = 339719, upload-time = "2026-06-29T13:05:10.41Z" },
574
+ { url = "https://files.pythonhosted.org/packages/78/f7/18a1afcd64f35314b68c1f23afcd9994d0bc13e65cc77517afff4e83986d/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d613743df53199b1aa256a7d328340da6d7078aac7705a7db9d7a791e9cfd2", size = 343885, upload-time = "2026-06-29T13:05:12.087Z" },
575
+ ]
576
+
577
+ [[package]]
578
+ name = "jupyter-client"
579
+ version = "8.9.1"
580
+ source = { registry = "https://pypi.org/simple" }
581
+ dependencies = [
582
+ { name = "jupyter-core" },
583
+ { name = "python-dateutil" },
584
+ { name = "pyzmq" },
585
+ { name = "tornado" },
586
+ { name = "traitlets" },
587
+ { name = "typing-extensions" },
588
+ ]
589
+ sdist = { url = "https://files.pythonhosted.org/packages/7d/dc/5512503b088997c2250b8bf18258fba9d9ce5ead641183700960d3c9d342/jupyter_client-8.9.1.tar.gz", hash = "sha256:a58f730dd9e728ba16ba1d62ebccf7ffe1ebbdbce4e95cfae941b7321ae1f4fa", size = 359256, upload-time = "2026-06-09T13:15:01.033Z" }
590
+ wheels = [
591
+ { url = "https://files.pythonhosted.org/packages/3f/6f/56d39bf385c5c27988aebaf0c18a2a17e960575740100973511018bd904e/jupyter_client-8.9.1-py3-none-any.whl", hash = "sha256:0b7a295bc46e8751e9adae84781f726c851c1d911bd793edc4a3bde942e3da81", size = 109828, upload-time = "2026-06-09T13:14:58.835Z" },
592
+ ]
593
+
594
+ [[package]]
595
+ name = "jupyter-core"
596
+ version = "5.9.1"
597
+ source = { registry = "https://pypi.org/simple" }
598
+ dependencies = [
599
+ { name = "platformdirs" },
600
+ { name = "traitlets" },
601
+ ]
602
+ sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" }
603
+ wheels = [
604
+ { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" },
605
+ ]
606
+
607
+ [[package]]
608
+ name = "jupyterlab-widgets"
609
+ version = "3.0.16"
610
+ source = { registry = "https://pypi.org/simple" }
611
+ sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423, upload-time = "2025-11-01T21:11:29.724Z" }
612
+ wheels = [
613
+ { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" },
614
+ ]
615
+
616
  [[package]]
617
  name = "markdown-it-py"
618
  version = "4.2.0"
 
644
  { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
645
  ]
646
 
647
+ [[package]]
648
+ name = "matplotlib-inline"
649
+ version = "0.2.2"
650
+ source = { registry = "https://pypi.org/simple" }
651
+ dependencies = [
652
+ { name = "traitlets" },
653
+ ]
654
+ sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" }
655
+ wheels = [
656
+ { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" },
657
+ ]
658
+
659
  [[package]]
660
  name = "mdurl"
661
  version = "0.1.2"
 
674
  { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
675
  ]
676
 
677
+ [[package]]
678
+ name = "nest-asyncio2"
679
+ version = "1.7.2"
680
+ source = { registry = "https://pypi.org/simple" }
681
+ sdist = { url = "https://files.pythonhosted.org/packages/b4/73/731debf26e27e0a0323d7bda270dc2f634b398e38f040a09da1f4351d0aa/nest_asyncio2-1.7.2.tar.gz", hash = "sha256:1921d70b92cc4612c374928d081552efb59b83d91b2b789d935c665fa01729a8", size = 14743, upload-time = "2026-02-13T00:34:04.386Z" }
682
+ wheels = [
683
+ { url = "https://files.pythonhosted.org/packages/c5/3c/3179b85b0e1c3659f0369940200cd6d0fa900e6cefcc7ea0bc6dd0e29ffb/nest_asyncio2-1.7.2-py3-none-any.whl", hash = "sha256:f5dfa702f3f81f6a03857e9a19e2ba578c0946a4ad417b4c50a24d7ba641fe01", size = 7843, upload-time = "2026-02-13T00:34:02.691Z" },
684
+ ]
685
+
686
  [[package]]
687
  name = "networkx"
688
  version = "3.6.1"
 
697
  version = "0.1.0"
698
  source = { virtual = "." }
699
  dependencies = [
700
+ { name = "accelerate" },
701
  { name = "flash-attn" },
702
+ { name = "gguf" },
703
  { name = "gradio" },
704
  { name = "huggingface-hub" },
705
+ { name = "ipykernel" },
706
+ { name = "ipython" },
707
+ { name = "ipywidgets" },
708
+ { name = "openai" },
709
  { name = "torch" },
710
  { name = "transformers" },
711
  ]
712
 
713
  [package.metadata]
714
  requires-dist = [
715
+ { name = "accelerate", specifier = ">=1.14.0" },
716
  { name = "flash-attn", url = "https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.28/flash_attn-2.8.3+cu130torch2.9-cp312-cp312-win_amd64.whl" },
717
+ { name = "gguf", specifier = ">=0.19.0" },
718
  { name = "gradio" },
719
  { name = "huggingface-hub" },
720
+ { name = "ipykernel", specifier = ">=7.3.0" },
721
+ { name = "ipython", specifier = ">=9.15.0" },
722
+ { name = "ipywidgets", specifier = ">=8.1.8" },
723
+ { name = "openai", specifier = ">=2.46.0" },
724
+ { name = "torch", specifier = "==2.9.1", index = "https://download.pytorch.org/whl/cu130" },
725
+ { name = "transformers" },
726
  ]
727
 
728
  [[package]]
 
893
  { url = "https://files.pythonhosted.org/packages/86/91/8b486ba85f71a2859dd705a4ec6aab38c37a389b8b7f94343db027732999/nvidia_nvtx-13.0.39-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cddd2e08b35144f1000631c3880c9ebbcb8a2863d762e76f92d47d30ecaf87cc", size = 148037, upload-time = "2025-08-04T10:18:31.763Z" },
894
  ]
895
 
896
+ [[package]]
897
+ name = "openai"
898
+ version = "2.46.0"
899
+ source = { registry = "https://pypi.org/simple" }
900
+ dependencies = [
901
+ { name = "anyio" },
902
+ { name = "distro" },
903
+ { name = "httpx" },
904
+ { name = "jiter" },
905
+ { name = "pydantic" },
906
+ { name = "sniffio" },
907
+ { name = "tqdm" },
908
+ { name = "typing-extensions" },
909
+ ]
910
+ sdist = { url = "https://files.pythonhosted.org/packages/af/ac/f725c4efbda8657d02be684607e5a2e5ce362e4790fdbcbdfb7c15018647/openai-2.46.0.tar.gz", hash = "sha256:0421e0735ac41451cad894af4cddf0435bfbf8cbc538ac0e15b3c062f2ddc06a", size = 1114628, upload-time = "2026-07-17T02:48:06.05Z" }
911
+ wheels = [
912
+ { url = "https://files.pythonhosted.org/packages/ea/7b/206238ebcb50b235942b1c66dba4974776f2057402a8d91c399be587d66a/openai-2.46.0-py3-none-any.whl", hash = "sha256:672381db55efb3a1e2610f29304c130cccdd0b319bace4d492b2443cb64c1e7c", size = 1637556, upload-time = "2026-07-17T02:48:03.695Z" },
913
+ ]
914
+
915
  [[package]]
916
  name = "orjson"
917
  version = "3.11.9"
 
965
  { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" },
966
  ]
967
 
968
+ [[package]]
969
+ name = "parso"
970
+ version = "0.8.7"
971
+ source = { registry = "https://pypi.org/simple" }
972
+ sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" }
973
+ wheels = [
974
+ { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" },
975
+ ]
976
+
977
+ [[package]]
978
+ name = "pexpect"
979
+ version = "4.9.0"
980
+ source = { registry = "https://pypi.org/simple" }
981
+ dependencies = [
982
+ { name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
983
+ ]
984
+ sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
985
+ wheels = [
986
+ { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" },
987
+ ]
988
+
989
  [[package]]
990
  name = "pillow"
991
  version = "12.3.0"
 
1003
  { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" },
1004
  ]
1005
 
1006
+ [[package]]
1007
+ name = "platformdirs"
1008
+ version = "4.10.1"
1009
+ source = { registry = "https://pypi.org/simple" }
1010
+ sdist = { url = "https://files.pythonhosted.org/packages/52/cd/4f25b2f95b23f5d2c9c1fe43e49841bff5800562149b2666afc09309aa8f/platformdirs-4.10.1.tar.gz", hash = "sha256:ceab4084426fe6319ce18e86deada8ab1b7487c7aee7040c55e277c9ae793695", size = 31678, upload-time = "2026-07-18T03:53:43.808Z" }
1011
+ wheels = [
1012
+ { url = "https://files.pythonhosted.org/packages/ec/73/6fd0bb9ce84138c3857f12e9de63bc901852975a092d545f18087a204aa2/platformdirs-4.10.1-py3-none-any.whl", hash = "sha256:0e4eff26be2d75293977f7cddc153fd9b8eaa7fb0c7b64ffe4076cb443117443", size = 22906, upload-time = "2026-07-18T03:53:42.576Z" },
1013
+ ]
1014
+
1015
+ [[package]]
1016
+ name = "prompt-toolkit"
1017
+ version = "3.0.52"
1018
+ source = { registry = "https://pypi.org/simple" }
1019
+ dependencies = [
1020
+ { name = "wcwidth" },
1021
+ ]
1022
+ sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
1023
+ wheels = [
1024
+ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
1025
+ ]
1026
+
1027
+ [[package]]
1028
+ name = "psutil"
1029
+ version = "7.2.2"
1030
+ source = { registry = "https://pypi.org/simple" }
1031
+ sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" }
1032
+ wheels = [
1033
+ { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" },
1034
+ { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" },
1035
+ { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" },
1036
+ { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" },
1037
+ { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" },
1038
+ { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" },
1039
+ { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" },
1040
+ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
1041
+ ]
1042
+
1043
+ [[package]]
1044
+ name = "ptyprocess"
1045
+ version = "0.7.0"
1046
+ source = { registry = "https://pypi.org/simple" }
1047
+ sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" }
1048
+ wheels = [
1049
+ { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" },
1050
+ ]
1051
+
1052
+ [[package]]
1053
+ name = "pure-eval"
1054
+ version = "0.2.3"
1055
+ source = { registry = "https://pypi.org/simple" }
1056
+ sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" }
1057
+ wheels = [
1058
+ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" },
1059
+ ]
1060
+
1061
+ [[package]]
1062
+ name = "pycparser"
1063
+ version = "3.0"
1064
+ source = { registry = "https://pypi.org/simple" }
1065
+ sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
1066
+ wheels = [
1067
+ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
1068
+ ]
1069
+
1070
  [[package]]
1071
  name = "pydantic"
1072
  version = "2.13.4"
 
1179
  ]
1180
 
1181
  [[package]]
1182
+ name = "pyzmq"
1183
+ version = "27.1.0"
1184
  source = { registry = "https://pypi.org/simple" }
1185
  dependencies = [
1186
+ { name = "cffi", marker = "implementation_name == 'pypy'" },
 
 
 
1187
  ]
1188
+ sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" }
1189
  wheels = [
1190
+ { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" },
1191
+ { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" },
1192
+ { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" },
1193
+ { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" },
1194
+ { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" },
1195
+ { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" },
1196
+ { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" },
1197
+ { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" },
1198
+ { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" },
1199
+ { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" },
1200
  ]
1201
 
1202
  [[package]]
 
1323
  { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
1324
  ]
1325
 
1326
+ [[package]]
1327
+ name = "sniffio"
1328
+ version = "1.3.1"
1329
+ source = { registry = "https://pypi.org/simple" }
1330
+ sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
1331
+ wheels = [
1332
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
1333
+ ]
1334
+
1335
+ [[package]]
1336
+ name = "stack-data"
1337
+ version = "0.6.3"
1338
+ source = { registry = "https://pypi.org/simple" }
1339
+ dependencies = [
1340
+ { name = "asttokens" },
1341
+ { name = "executing" },
1342
+ { name = "pure-eval" },
1343
+ ]
1344
+ sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" }
1345
+ wheels = [
1346
+ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" },
1347
+ ]
1348
+
1349
  [[package]]
1350
  name = "starlette"
1351
  version = "1.3.1"
 
1408
 
1409
  [[package]]
1410
  name = "torch"
1411
+ version = "2.9.1+cu130"
1412
  source = { registry = "https://download.pytorch.org/whl/cu130" }
1413
  dependencies = [
1414
  { name = "filelock" },
 
1436
  { name = "typing-extensions" },
1437
  ]
1438
  wheels = [
1439
+ { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.9.1%2Bcu130-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:6e7f84cb10c7e7d9f862c318f056d64840544ab4f0bcbf8cf7ed6047fe04051f", upload-time = "2026-01-26T16:37:40Z" },
1440
+ { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.9.1%2Bcu130-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e70e1b18881e6b3c1ce402d0a989da39f956a3a057526e03c354df23d704ce9b", upload-time = "2026-01-26T16:37:43Z" },
1441
+ { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.9.1%2Bcu130-cp312-cp312-win_amd64.whl", hash = "sha256:cd3232a562ad2a2699d48130255e1b24c07dfe694a40dcd24fad683c752de121", upload-time = "2026-01-26T16:37:50Z" },
1442
+ ]
1443
+
1444
+ [[package]]
1445
+ name = "tornado"
1446
+ version = "6.5.7"
1447
+ source = { registry = "https://pypi.org/simple" }
1448
+ sdist = { url = "https://files.pythonhosted.org/packages/64/24/95ec527ad67b76d59299e5465b3935d05e4294b7e0290a3924b7487df30b/tornado-6.5.7.tar.gz", hash = "sha256:66c513a76cda70d53907bc27cf1447557699c2e95aa48ba27a442ff61c3ddfc2", size = 519252, upload-time = "2026-06-08T17:34:51.232Z" }
1449
+ wheels = [
1450
+ { url = "https://files.pythonhosted.org/packages/02/dc/c7043cab6fed8ae159fc1923ce829ada35c4dbd797d408a43858ffaf9639/tornado-6.5.7-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:148b2eb15c2c765a50796172c1e499649b35f30d2e3c3d3e15913cfa56bfb163", size = 448543, upload-time = "2026-06-08T17:34:38.052Z" },
1451
+ { url = "https://files.pythonhosted.org/packages/92/4f/090b1431e5a43df696feceffc268c5383cc079ecb5f08ce58f917109aafe/tornado-6.5.7-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9da38de27f1da3b78a966f0dae12b5a1ea9afe72ca805d84ff06508272ddf100", size = 446707, upload-time = "2026-06-08T17:34:39.594Z" },
1452
+ { url = "https://files.pythonhosted.org/packages/37/d8/ef374952fd5da67d4463122c2b8e5a96536ec10b4b339254c6dcde81d01c/tornado-6.5.7-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8d759e71906ee783f8867b93bf26a265743da4c1e2f4a018464c1ba019862972", size = 449774, upload-time = "2026-06-08T17:34:41.204Z" },
1453
+ { url = "https://files.pythonhosted.org/packages/35/37/d434c73f4c6e014b745b9b37085f34f40c022f007efff3d7fe65991899f3/tornado-6.5.7-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a46347a18f23fb92b396beebe0fb78f61dda0cc302445202c16203d8a18848b", size = 450745, upload-time = "2026-06-08T17:34:42.531Z" },
1454
+ { url = "https://files.pythonhosted.org/packages/b6/2b/56b9aff361d7f1ab728a805ec7d7ea835f8807afa9f5cc690ea0e630efb9/tornado-6.5.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7778b30bef919231265e91c69963ce0f49a1e9c07ac900bbe75b19ce2575ba92", size = 450578, upload-time = "2026-06-08T17:34:43.787Z" },
1455
+ { url = "https://files.pythonhosted.org/packages/02/30/a7444fb23aa76860a14198fab96ac79f1866b0a6e19e26c4381b0938e50f/tornado-6.5.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e726f0c75da7726eec023aa62751ff8878bd2737e34fbdd33b1ae5897d2200f5", size = 449985, upload-time = "2026-06-08T17:34:45.326Z" },
1456
+ { url = "https://files.pythonhosted.org/packages/5c/42/5f0e56c01e8d9d36f4e23f367b85ae6cae0c1ecddd5e6977d8388ad27488/tornado-6.5.7-cp39-abi3-win32.whl", hash = "sha256:f8de3bf12d3efdd0cbe7c8887868198f8a91415e3f29fcf258d9b8eb7b1d9ae4", size = 451047, upload-time = "2026-06-08T17:34:46.784Z" },
1457
+ { url = "https://files.pythonhosted.org/packages/c9/a4/b393076ffb21b469eec5b328a0534cf03a3b90bfc6b1f09507cdd075d938/tornado-6.5.7-cp39-abi3-win_amd64.whl", hash = "sha256:de942f843533a039ef9fa3d9c88c7cd8a7c94553fb5ad0154270989b3d99a2c4", size = 451485, upload-time = "2026-06-08T17:34:48.248Z" },
1458
+ { url = "https://files.pythonhosted.org/packages/71/2e/7b1c769803121b809112cf9a00681c472eae1d80e32d7ec0e0bd61d0d0e1/tornado-6.5.7-cp39-abi3-win_arm64.whl", hash = "sha256:ff934fce95643af5f11efdae618eaa73d469dc588641e5c8d19295a0c65c4796", size = 450506, upload-time = "2026-06-08T17:34:49.702Z" },
1459
  ]
1460
 
1461
  [[package]]
 
1470
  { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351, upload-time = "2024-08-03T22:35:36.644Z" },
1471
  ]
1472
 
1473
+ [[package]]
1474
+ name = "traitlets"
1475
+ version = "5.15.1"
1476
+ source = { registry = "https://pypi.org/simple" }
1477
+ sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" }
1478
+ wheels = [
1479
+ { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" },
1480
+ ]
1481
+
1482
  [[package]]
1483
  name = "transformers"
1484
  version = "5.14.1"
 
1501
 
1502
  [[package]]
1503
  name = "triton"
1504
+ version = "3.5.1"
1505
  source = { registry = "https://pypi.org/simple" }
1506
  wheels = [
1507
+ { url = "https://files.pythonhosted.org/packages/db/53/2bcc46879910991f09c063eea07627baef2bc62fe725302ba8f46a2c1ae5/triton-3.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:275a045b6ed670dd1bd005c3e6c2d61846c74c66f4512d6f33cc027b11de8fd4", size = 159940689, upload-time = "2025-11-11T17:51:55.938Z" },
1508
+ { url = "https://files.pythonhosted.org/packages/f2/50/9a8358d3ef58162c0a415d173cfb45b67de60176e1024f71fbc4d24c0b6d/triton-3.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2c6b915a03888ab931a9fd3e55ba36785e1fe70cbea0b40c6ef93b20fc85232", size = 170470207, upload-time = "2025-11-11T17:41:00.253Z" },
1509
  ]
1510
 
1511
  [[package]]
 
1574
  wheels = [
1575
  { url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" },
1576
  ]
1577
+
1578
+ [[package]]
1579
+ name = "wcwidth"
1580
+ version = "0.8.2"
1581
+ source = { registry = "https://pypi.org/simple" }
1582
+ sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" }
1583
+ wheels = [
1584
+ { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" },
1585
+ ]
1586
+
1587
+ [[package]]
1588
+ name = "widgetsnbextension"
1589
+ version = "4.0.15"
1590
+ source = { registry = "https://pypi.org/simple" }
1591
+ sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402, upload-time = "2025-11-01T21:15:55.178Z" }
1592
+ wheels = [
1593
+ { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503, upload-time = "2025-11-01T21:15:53.565Z" },
1594
+ ]