Skip to content

fix(nemotron-parse): bound work on untrusted model output - #4923

Merged
dolfim-ibm merged 3 commits into
mainfrom
fix/nemotron-parse-untrusted-output
Oct 22, 2026
Merged

dolfim-ibm merged 3 commits into
mainfrom
fix/nemotron-parse-untrusted-output

Conversation

@dolfim-ibm

Copy link
Copy Markdown
Member

Follow-up to #4897. The mapper reads model output, which document content can steer, and which a remote server sends when the API engine is used. Three inputs used too much time or memory, or changed the table structure.

Changes

  • Span size (nemotron_parse_v3_5_utils._span): \multicolumn and \multirow spans are capped at _MAX_TABLE_GRID_CELLS + 1 before the span grid is built. Before this fix, \multicolumn{10000000}{c}{x} (about 100 characters) took 13 s and 1.2 GB, and larger values exhaust memory. A table above the limit becomes an empty table, as before.
  • Backtick runs (dots_utils._MARKDOWN_ESCAPE_PATTERN): the regex now reads the opening backtick run of a code span atomically ((?=(+))\1`, Python 3.10-compatible) and only at the start of a run. Before this fix, an unclosed run took cubic time: 2000 backticks took 15 s in both the Nemotron 3.5 and dots mappers. Now 20000 backticks take about 1 ms.
  • Cell markup (latex_tabular_to_html): cell text is HTML-escaped before the table HTML is built. Only inline formatting tags (b, br, em, i, strong, sub, sup, u) stay as markup. Before this fix, a cell such as a</td></tr><tr><td colspan="9">b added a row and a span to the table.

Checklist:

  • Documentation has been updated, if necessary.
  • Examples have been added, if necessary.
  • Tests have been added, if necessary.

Review evidence:

  • New tests: test_table_with_oversized_span_is_rejected_without_building_it, test_table_cell_markup_stays_cell_text, and two test_literal_markers_stay_literal cases (a nested code span and a 5000-backtick unclosed run).
  • test_table_cell_markup_stays_cell_text fails on main ((2, 10) != (1, 2)). I did not run the span test on main, because it exhausts memory there.
  • tests/test_nemotron_parse_v3_5.py, tests/test_dots_parser.py, tests/test_chandra_vlm.py: 107 passed. make validate passes.

Limits (not changed here):

- Cap \multicolumn and \multirow spans before the span grid is built.
  A span of 10^7 took 13 s and 1.2 GB; larger values exhaust memory.
- Read a code span's opening backtick run atomically in
  _MARKDOWN_ESCAPE_PATTERN. An unclosed run took cubic time
  (2000 backticks: 15 s) in the Nemotron 3.5 and dots mappers.
- Escape LaTeX table cell text before the HTML table is built, and
  keep only inline formatting tags. Cell text could add rows, cells
  and spans to the table.

Signed-off-by: Michele Dolfi <dol@zurich.ibm.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ DCO Check Passed

Thanks @dolfim-ibm, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 Merge protection satisfied — ready to merge.

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

@github-actions github-actions Bot added ai:ci-safe AI triage found no CI-safety concern (advisory) bug Something isn't working performance table structure vlm-pipeline labels Oct 20, 2026
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

AI triage (advisory)

Checked commit ab7c550e73. The analysis read the diff only. It did not run code from this pull request.

Warning

The AI analysis did not finish. Only the deterministic checks are shown.

CI safety: not assessed.

Possibly related pull requests

PR State Signal Assessment
#4908 open changes the same code not assessed
#4897 merged referenced as #4897 not assessed

The ai:* labels are advisory. They do not approve CI and they do not add tests:full. A new push runs this check again.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI first review (advisory): no blocker found

This review covers the full diff at 47d058e974. It read the code and did not run it.

I found no defect that should stop the merge. I read the span cap, the cell escaping, and the atomic backtick regex against the base helpers _add_html_table and _markdown_inline_html, and they hold. The span cap of 1001 always trips the existing grid check in docling/utils/chandra_utils.py. A raw & always splits the cell first, so the new escaping cannot double-escape an entity.

A maintainer makes the merge decision. This review never approves the pull request.

@github-actions github-actions Bot added the ai:review-lgtm AI first review found no blocker or major issue (advisory) label Oct 20, 2026
@codecov

codecov Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
docling/utils/nemotron_parse_v3_5_utils.py 90.90% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@dolfim-ibm
dolfim-ibm requested a review from cau-git October 20, 2026 21:27
…trusted-output

Signed-off-by: Michele Dolfi <dol@zurich.ibm.com>
@github-actions github-actions Bot removed ai:ci-safe AI triage found no CI-safety concern (advisory) ai:review-lgtm AI first review found no blocker or major issue (advisory) labels Oct 22, 2026
- Map LaTeX text commands and escape cell text in one pass.
  \textless{}b\textgreater{} became a <b> tag, because the commands
  became raw < and > before the escape.
- Keep <code> as an inline cell tag.
- Stop a row above _MAX_TABLE_GRID_CELLS columns before the span grid
  is built. 2000 spans of 1000 columns used 511 MB.

Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
@cau-git

cau-git commented Oct 22, 2026

Copy link
Copy Markdown
Member

I pushed ab7c550 with three fixes on top of this PR:

  • Escaped angle brackets in table cells: \textless{}b\textgreater{}x became bold text x. The < and > were changed before the escape step, so they became a real <b> tag. Now the cell text is <b>x</b>, as in the source.
  • <code> in table cells: this tag now stays markup. Before this fix, the cell showed the literal text <code>z</code>.
  • Many wide spans in one row: the span limit applied to each cell, but not to the full row. 2000 cells with a span of 1000 (54 KB of input) used 511 MB. Now a row above 1000 columns stops the table conversion early, and the table becomes empty, as before.

One limit remains: in a cell that also has *emphasis*, < in a code span shows as &lt;. I did not change this.

@dolfim-ibm
dolfim-ibm merged commit 3b0f25f into main Oct 22, 2026
47 of 51 checks passed
@dolfim-ibm
dolfim-ibm deleted the fix/nemotron-parse-untrusted-output branch October 22, 2026 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants