Skip to content

camel-docling: honour the OCR headers in the CLI command - #29595

Merged
davsclaus merged 1 commit into
apache:mainfrom
WMK666666:docling-ocr-headers
Nov 12, 2026
Merged

davsclaus merged 1 commit into
apache:mainfrom
WMK666666:docling-ocr-headers

Conversation

@WMK666666

@WMK666666 WMK666666 commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

The component docs have a Using headers to control processing section that sets three headers per
message on a plain docling: endpoint:

.setHeader("CamelDoclingOperation", constant(DoclingOperations.CONVERT_TO_HTML))
.setHeader("CamelDoclingEnableOCR", constant(true))
.setHeader("CamelDoclingOCRLanguage", constant("es"))
.to("docling:CONVERT_TO_MARKDOWN")  // Operation will be overridden by header

CamelDoclingOperation is honoured. The two OCR headers are declared in DoclingHeaders with
@Metadata, documented in that section (and in the YAML tab next to it), and read by nothing: a search over
src/main finds no use of DoclingHeaders.ENABLE_OCR or DoclingHeaders.OCR_LANGUAGE outside their
declaration. The example therefore did nothing — every message was OCRed exactly as the endpoint was
configured, whichever header the route set.

Change

Both paths read them now:

  • addOcrArguments takes the exchange, and the headers override enableOCR and ocrLanguage for that
    message; unset headers fall back to the endpoint configuration as before.
  • applyConfigurationToOptions applies the same two overrides before it fills the docling-serve request,
    so the request and the CLI command agree.

The two values are resolved once per message, on the thread that owns the exchange, into a small holder that
buildConvertRequest and buildStructuredDataRequest take instead of the exchange. The batch operations
hand the exchange to up to batchParallelism workers at once (convertDocumentsBatch,
convertStructuredDataBatch), and a batch that times out does not stop the tasks already running, so a
worker that read the headers itself would be reading an exchange another thread may still be using. The
values are the same for every document of a batch, so reading them once before the workers start costs
nothing and removes the sharing.

This is the same shape as CamelDoclingUseAsyncMode, which already overrides useAsyncMode per message.

The case worth having is a route that mostly sees text PDFs but occasionally a scanned attachment: it can
turn OCR on for that one message instead of adding a second endpoint. Symmetrically, an endpoint with OCR
on can skip it for a message that does not need it. A header that turns OCR off does not pass a language
on, and a language header alone turns OCR on unless the endpoint has disabled it with enableOCR=false.

The language is docling's own identifier

The first version of this PR checked the value against [A-Za-z0-9_\- ,]+ and threw for anything else.
That rejected values docling accepts — iso:zh-Hant, script/Latin, eng+deu — and it applied to the
ocrLanguage option as well, so docling:convert?...&ocrLanguage=iso:de worked before the PR and failed
after it. The check is gone.

It is a different thing from CamelDoclingCustomArguments, which is validated: a custom argument can be a
flag (--to, --output) that overrides a producer-managed option, or a path, which is why
validateCustomArguments checks flags against an allowlist and paths for traversal. The OCR language is
neither. It is a single element of the List<String> handed to ProcessBuilder — no shell is involved, so
;, | and $() never reach an interpreter — and docling's identifiers legitimately contain : (iso:),
/ (script/Latin, which the path check would reject as a path) and + (eng+deu). There is no subset
worth allowing that does not cut off valid input, so the value goes through as it is.

Tests

DoclingOcrHeadersTest builds the CLI command and the docling-serve request for a message with and without
the headers, and for an endpoint whose options say the opposite:

case result
no headers, default endpoint --ocr-lang en
CamelDoclingEnableOCR=false --no-ocr, no --ocr-lang
CamelDoclingOCRLanguage=es --ocr-lang es
endpoint enableOCR=false&ocrLanguage=de, headers true/es --ocr-lang es, no --no-ocr
endpoint enableOCR=false, header CamelDoclingOCRLanguage=es --no-ocr, no --ocr-lang; doOcr=false, no language
CamelDoclingOCRLanguage=iso:zh-Hant --ocr-lang iso:zh-Hant, and iso:zh-Hant in the serve request
script/Latin, eng+deu, en, de passed through unchanged

Reverting addOcrArguments to read only the configuration fails the header cases and leaves the first
green. Putting the language check back fails exactly the two iso:zh-Hant cases and leaves the other ten
green. Dropping the enableOCR=false fallback in resolveOcrSettings fails exactly the new serve case and
leaves the other thirteen green: a language header would otherwise turn OCR back on for that message.

mvn -pl components/camel-ai/camel-docling test: 100 tests, 2 failures, both
DoclingCustomArgsValidationTest on Windows (/etc/cron.d is not an absolute path there), which fail the
same way without this change.

@gnodet-bot gnodet-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.

LGTM. Clean, minimal fix that wires up two documented-but-dead per-message OCR headers (CamelDoclingEnableOCR, CamelDoclingOCRLanguage) to the CLI command builder. The logic is correct — enableOcr falls back from header → endpoint config, ocrLanguage does the same via ObjectHelper.isEmpty. The test covers all four meaningful cases and correctly exercises addOcrArguments through buildDoclingCommand. No issues.

Note: The docling-serve path (applyConfigurationToOptions) still ignores the OCR headers. The PR description calls this out explicitly — it's a known scope limitation, not a bug here.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@davsclaus davsclaus 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.

The CLI change is correct: the header falls back to the endpoint configuration, the same shape as CamelDoclingUseAsyncMode, and the test covers the cases that matter. One gap to close here rather than later:

  1. With useDoclingServe=true the headers are still silently ignored. The descriptions in DoclingHeaders.java:38-42 ("Whether to include OCR processing", "Language for OCR processing") don't say they are CLI-only, so users get the same silent no-op this PR fixes, on the other path. The exchange is in scope there: convertUsingDoclingServe(inputPath, outputFormat, exchange) already reads USE_ASYNC_MODE from it; only applyConfigurationToOptions lacks it. Please either pass the two overrides down into buildConvertRequest/applyConfigurationToOptions for single-document conversions, or at minimum say "docling CLI only" in both @Metadata descriptions and regenerate (docling.json, the catalog copy, the endpoint DSL header names).
  2. Suggestion: the CamelDoclingOCRLanguage value goes straight into the command (DoclingProducer.java:2290-2299), while CamelDoclingCustomArguments values are validated (rejectShellMetacharacters, validatePathSafety). There is no shell (ProcessBuilder) and Camel* headers are filtered from HTTP input by default, so this is hardening only, but rejecting values that start with -, or limiting them to letters, _, - and ,, would keep it consistent.
  3. Suggestion: headers that were documented but had no effect now change behaviour for routes that already set them; a short note in camel-4x-upgrade-guide-4_24.adoc would help.

Claude Code on behalf of davsclaus. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.

@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-ai/camel-docling
  • docs

🔬 Scalpel shadow comparison — Scalpel: 10 of 722 tested, 26 compile-only — current: 10 all tested

Maveniverse Scalpel detected 10 affected modules (current approach: 10).

Skip-tests mode would test 10 modules (2 direct + 9 downstream), skip tests for 26 (generated code, meta-modules)

Modules Scalpel would test (10)
  • camel-docling ← components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java, components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingOcrHeadersTest.java
  • camel-jbang-mcp ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-mcp ← downstream of org.apache.camel:camel-jbang-core
  • camel-jbang-plugin-tui ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-validate ← downstream of org.apache.camel:camel-yaml-dsl-validator
  • camel-jbang-run ← downstream of org.apache.camel:camel-catalog
  • camel-langchain4j-ingest ← downstream of org.apache.camel:camel-docling
  • camel-launcher-container ← downstream of org.apache.camel:camel-launcher
  • camel-yaml-dsl-validator ← downstream of org.apache.camel:camel-catalog
  • camel-yaml-dsl-validator-maven-plugin ← downstream of org.apache.camel:camel-yaml-dsl-validator
Modules with tests skipped (26)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (37 modules, 6m 32s total)

Total reactor time: 6m 32s

Module Duration Status
Camel :: JBang :: Plugin :: TUI 57.5s SUCCESS
Camel :: Launcher 54.4s SUCCESS
Camel :: JBang :: MCP 35.4s SUCCESS
Camel :: AI :: LangChain4j :: Ingest 24.9s SUCCESS
Camel :: Component DSL 23.9s SUCCESS
Camel :: JBang :: Plugin :: Validate 22.2s SUCCESS
Camel :: YAML DSL :: Validator 20.0s SUCCESS
Camel :: YAML DSL 18.2s SUCCESS
Camel :: JBang :: Run 17.8s SUCCESS
Camel :: AI :: Docling 17.5s SUCCESS
Camel :: Catalog :: Camel Catalog 16.7s SUCCESS
Camel :: Kamelet Main 11.8s SUCCESS
Camel :: Docs 10.7s SUCCESS
Camel :: JBang :: Plugin :: Testing 8.5s SUCCESS
Camel :: JBang :: Plugin :: Kubernetes 7.9s SUCCESS
Camel :: Catalog :: Camel Report Maven Plugin 7.9s SUCCESS
Camel :: Catalog :: Camel Route Parser 5.3s SUCCESS
Camel :: YAML DSL :: Deserializers 5.1s SUCCESS
Camel :: All Components Sync point 4.1s SUCCESS
Camel :: Catalog :: Suggest (deprecated) 3.8s SUCCESS
Camel :: JBang :: Plugin :: Generate 3.8s SUCCESS
Camel :: YAML DSL :: Validator Maven Plugin 2.7s SUCCESS
Camel :: Catalog :: Maven 2.3s SUCCESS
Camel :: YAML DSL :: Maven Plugins 1.9s SUCCESS
Camel :: Catalog :: Dummy Component 1.3s SUCCESS
Camel :: Assembly 1.2s SUCCESS
Camel :: JBang :: Integration tests 0.8s SUCCESS
Camel :: Coverage 0.8s SUCCESS
Camel :: Catalog :: Console 0.7s SUCCESS
Camel :: JBang :: Plugin :: Edit 0.7s SUCCESS
Camel :: Endpoint DSL :: Support 0.6s SUCCESS
Camel :: JBang :: Plugin :: MCP 0.6s SUCCESS
Camel :: JBang :: Main 0.5s SUCCESS
Camel :: Launcher :: Container 0.5s SUCCESS
Camel :: Endpoint DSL n/a
Camel :: Integration Tests n/a
Camel :: JBang :: Core n/a

Top 20 slowest modules:

  • Camel :: JBang :: Plugin :: TUI (57.5s)
  • Camel :: Launcher (54.4s)
  • Camel :: JBang :: MCP (35.4s)
  • Camel :: AI :: LangChain4j :: Ingest (24.9s)
  • Camel :: Component DSL (23.9s)
  • Camel :: JBang :: Plugin :: Validate (22.2s)
  • Camel :: YAML DSL :: Validator (20.0s)
  • Camel :: YAML DSL (18.2s)
  • Camel :: JBang :: Run (17.8s)
  • Camel :: AI :: Docling (17.5s)
  • Camel :: Catalog :: Camel Catalog (16.7s)
  • Camel :: Kamelet Main (11.8s)
  • Camel :: Docs (10.7s)
  • Camel :: JBang :: Plugin :: Testing (8.5s)
  • Camel :: JBang :: Plugin :: Kubernetes (7.9s)
  • Camel :: Catalog :: Camel Report Maven Plugin (7.9s)
  • Camel :: Catalog :: Camel Route Parser (5.3s)
  • Camel :: YAML DSL :: Deserializers (5.1s)
  • Camel :: All Components Sync point (4.1s)
  • Camel :: Catalog :: Suggest (deprecated) (3.8s)

⚙️ View full build and test results

@oscerd oscerd 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.

This makes the CLI path match what the component docs already tell people to do with CamelDoclingEnableOCR and CamelDoclingOCRLanguage. Reading the header first and falling back to the endpoint's enableOCR only when the header is absent is the right precedence, and the test cases are the ones that matter: an endpoint with OCR on but a single message turning it off (and asserting no language leaks into the command), and an endpoint off/German with a message asking for Spanish. Honouring the per-message headers in the command the same way the non-CLI path does is the consistent behaviour. LGTM.

Claude Code on behalf of oscerd

@WMK666666

Copy link
Copy Markdown
Contributor Author

All three points are in 193e024.

1. The serve path. Wired rather than documented as CLI-only. The exchange is indeed in scope, so convertUsingDoclingServe passes it into buildConvertRequest, which passes it into applyConfigurationToOptions, and the headers override doOcr/ocrLang with the same precedence as the CLI:

  • a header wins over the endpoint configuration;
  • a header that turns OCR off does not carry a language along;
  • a language header alone turns OCR on;
  • with no header, the endpoint configuration applies as before, including leaving doOcr unset so the server keeps its own defaults when nothing is configured.

I carried the exchange through the batch and metadata operations as well, so a route behaves the same whichever operation it sends — extractMetadataUsingApi and the batch workers were the two places that dropped it.

2. The language check is in, shared by both paths: a value that starts with -, or one with characters outside letters, digits, _, -, , and spaces, fails with an IllegalArgumentException naming the value. Comma separated lists with spaces (en, de) still pass. Empty is treated as "not set" rather than as a value.

3. Upgrade guide. An entry under a camel-docling heading: the headers now override the endpoint, and a route that sets a language the component cannot pass on now fails instead of passing it through.

Tests: DoclingOcrHeadersTest covers both paths — the four CLI cases as before, plus the serve request with no headers, OCR turned off, a language header, the fallback to the endpoint's language, and the doOcr option still being sent — and the rejection on each path. Reverse-checked: removing the header block fails only the four serve cases, removing either validation call fails only its own cases, and the CLI cases stay green throughout.

Regression: 103 tests in the component, 2 failures in DoclingCustomArgsValidationTest that fail the same way on an unmodified tree here (POSIX absolute paths are not absolute on Windows).

@Croway

Croway commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Reading the headers on both paths looks good. One regression and two small things:

  1. validateOcrLanguage rejects languages that docling accepts (DoclingProducer.java:170, :2345). The allow-list [A-Za-z0-9_,\- ]+ rejects:

    • iso:zh-Hant, the BCP-47 form docling convert --help documents since docling 2.127.0;
    • script/Latin and eng+deu, Tesseract IDs listed in docling's own tesseract_ocr_cli_model.py.

    It also applies to the endpoint ocrLanguage option, not only the header. A route with ocrLanguage=iso:de works on main (--ocr-lang iso:de) and fails with IllegalArgumentException on this branch.

    The check also adds no protection:

    • The command runs through ProcessBuilder, so no shell is involved.
    • docling's CLI parser (typer) takes the token after --ocr-lang as its value even when it starts with -. --ocr-lang --no-ocr gives ocr_lang='--no-ocr' with OCR still on.
    • docling already validates Tesseract IDs itself (_VALID_LANG_RE = ^[a-zA-Z0-9_/][a-zA-Z0-9_/+-]*$).

    I'd drop the check. If you keep it, apply it only to the header, not to the route author's ocrLanguage, and allow at least :, / and +.

  2. Upgrade guide (camel-4x-upgrade-guide-4_24.adoc:7944): "a language header without an OCR header turns OCR on" is only true when the endpoint has OCR on. With enableOCR=false and only CamelDoclingOCRLanguage=es, the CLI still gets --no-ocr and docling-serve gets doOcr=false. Please reword it, or change the code if OCR should turn on in that case.

  3. Description: it still says "Only the CLI command is touched" and doesn't mention the docling-serve path or the new language check. The earlier LGTMs were given on the CLI-only version.

Claude Code on behalf of Croway

@WMK666666

Copy link
Copy Markdown
Contributor Author

Both points are taken in 0e90f55.

The language check is gone. @Croway is right: it rejected iso:zh-Hant, script/Latin and eng+deu,
which docling accepts, and it applied to the ocrLanguage option too — docling:convert?...&ocrLanguage=iso:de
worked before this PR and failed after it. That is a regression I introduced, thanks for catching it.

@davsclaus — on the point that the value reaches the command unvalidated while CamelDoclingCustomArguments
values are checked: the two are not the same shape. A custom argument can be a flag (--to, --output)
that overrides a producer-managed option, or a path, which is why validateCustomArguments checks flags
against an allowlist and paths for traversal. The language is neither. It is one element of the
List<String> given to ProcessBuilder, so there is no shell and ;, |, $() never reach an
interpreter; and docling's own identifiers contain : (iso:zh-Hant), / (script/Latin, which
validatePathSafety would reject as a path) and + (eng+deu). Any allowlist I could write cuts off valid
input, so the value goes through untouched.

The serve-path gap you raised is closed: applyConfigurationToOptions reads both headers, so
useDoclingServe=true is no longer a silent no-op. The description now covers both paths.

Tests: DoclingOcrHeadersTest adds doclingLanguageIdentifiersReachTheCli and
doclingLanguageIdentifiersReachTheServeRequest (iso:zh-Hant on both paths) and keeps en, de. Putting
the old check back fails exactly those two and leaves the other ten green.

mvn -pl components/camel-ai/camel-docling test: 98 tests, 2 failures, both
DoclingCustomArgsValidationTest on Windows (/etc/cron.d is not an absolute path there), the same two
that fail without this change. formatter:validate and impsort:check pass.

WMK666666 added a commit to WMK666666/camel that referenced this pull request Nov 11, 2026
The check sat inside buildConvertRequest, which apache#29595 rewrites into a
three-argument overload: the two pull requests would have conflicted on
the method body for no reason. mapToOutputFormat is where the question
"can docling-serve return this format" is answered, and it is untouched
by apache#29595, so the rejection lives there.

The test asks the map for the format instead of building a request, so
neither PR needs the other's signature.
@github-actions github-actions Bot added the docs label Nov 11, 2026

@davsclaus davsclaus 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.

Re-review of 0e90f55. Since d18a549 there are two commits: the serve path now reads the headers, and the language check was added and then removed again.

All three of my earlier points are handled:

  1. Serve path: applyConfigurationToOptions (DoclingProducer.java:1597-1635) now reads CamelDoclingEnableOCR/CamelDoclingOCRLanguage with the same precedence as the CLI. With no header it still leaves doOcr unset, so the server keeps its defaults as before. The exchange is now passed into the metadata and batch operations as well.
  2. Language check: dropped after Croway showed it rejected valid docling identifiers (iso:zh-Hant, script/Latin, eng+deu) and also hit the route author's ocrLanguage. I agree with WMK666666's reasoning: the value is one ProcessBuilder argument (no shell), docling validates it itself, and an allow-list would only reject valid input. doclingLanguageIdentifiersReachTheCli/...ServeRequest pin that down.
  3. Upgrade guide: the entry is in (camel-4x-upgrade-guide-4_24.adoc:7938). It now says correctly that enableOCR=false keeps winning over a language-only header (Croway's point 2), and the description covers both paths (Croway's point 3).

New, from passing the exchange down:

  1. Important: the batch workers now read headers from the same Exchange in parallel. convertDocumentsBatch and convertStructuredDataBatch hand exchange to up to parallelism CompletableFuture.supplyAsync tasks (DoclingProducer.java:958-960, :1357), and each one calls exchange.getIn().getHeader(...) in applyConfigurationToOptions (:1612, :1621). An exchange should only be used by one thread at a time. On timeout, f.cancel(true) (:994-1013) does not stop a task that is already running, so a worker can still be reading while the route goes on with the exchange. The values are the same for every document, so read the two headers once on the calling thread and pass the result down instead of the Exchange. For example, resolve a small holder (Boolean enableOcr, String ocrLanguage) in processBatchConversion/the structured-data batch method and give buildConvertRequest/buildStructuredDataRequest that holder. The single-document paths can resolve it from the exchange as they do now.
  2. Suggestion: the rule from the upgrade guide, that enableOCR=false wins over a language-only header, is not covered by a test. Two lines in DoclingOcrHeadersTest would cover it: commandFor(OCR_OFF, null, "es") contains --no-ocr and no --ocr-lang, and serveOptions(OCR_OFF, null, "es") has doOcr=false and no ocrLang.
  3. #29591 and #29594 also append a camel-docling entry at the end of the 4.24 upgrade guide. Whichever PR merges second needs a trivial rebase. With #29594's newest commit, DoclingProducer.java no longer conflicts with this PR.

CI on 0e90f55 is approved and queued, but has not run yet.

Claude Code on behalf of Claus Ibsen. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.

@WMK666666

Copy link
Copy Markdown
Contributor Author

Both points are in 80d7804.

1. The exchange is no longer handed to the workers. You are right that the values are the same for every
document, so they are now resolved once, on the thread that owns the exchange, into a small immutable holder:

  • OcrSettings resolveOcrSettings(Exchange) holds the two decisions: the endpoint configuration first, then
    the two headers over it, exactly the precedence that was there before. It sits where
    applyConfigurationToOptions used to read the headers.
  • processBatchConversion and processBatchStructuredData call it once, before the executor is created, and
    pass the holder into convertDocumentsBatch/convertStructuredDataBatch instead of the exchange. Neither
    of those two methods mentions exchange any more (checked: zero occurrences in both bodies), so no worker
    touches it, and a cancelled task that keeps running has nothing to read.
  • buildConvertRequest, buildStructuredDataRequest and applyConfigurationToOptions take the holder, so
    the request cannot be built from an exchange at all any more — there is no Exchange overload left to
    reintroduce the same bug from.
  • The single-document paths call resolveOcrSettings(exchange) where they already have the exchange
    (convertUsingDoclingServe, processConvertToJson, processExtractStructuredData,
    processSubmitAsyncConversion, extractMetadataUsingApi), so their behaviour is unchanged.

The CLI path is untouched: buildDoclingCommand is built once per message on the message's own thread, and
that path renders the decision as --no-ocr / --ocr-lang.

2. The enableOCR=false rule is covered, with the two cases you named — commandFor(OCR_OFF, null, "es")
and serveOptions(OCR_OFF, null, "es"), i.e. no EnableOCR header, only a language, on an endpoint that
disabled OCR: --no-ocr and no --ocr-lang, doOcr=false and no language.

Reverse-checked: deleting the if (doOcr == null && !configuration.isEnableOCR()) fallback in
resolveOcrSettings fails exactly aLanguageHeaderDoesNotTurnOnOcrThatTheEndpointDisabledInTheServeRequest
and leaves the other thirteen green — without it the language header would set doOcr=true for that
message. The CLI case stays green either way, as it reads isEnableOCR() directly.

DoclingOcrHeadersTest: 14 tests green. mvn -pl components/camel-ai/camel-docling test: 100 tests, 2
failures, both DoclingCustomArgsValidationTest on Windows (/etc/cron.d is not an absolute path there),
the same two that fail without this change. formatter:validate and impsort:check pass.

3. The upgrade-guide entry stays where it is; #29591 and #29594 append their own at the end of the same
file, so whichever lands second gets the trivial rebase. Happy to do it here if this one comes in first.

The description is updated for the holder — the "Change" section explains why the headers are read before
the workers start, and the test table gained the two new cases.

@WMK666666

Copy link
Copy Markdown
Contributor Author

A note on the Build and test check (it is red on 0e90f55 and again on 80d7804 once approved): the failure
is not in this module.

  • Failing test: camel-jbang-core ExportMainApplicationPropertiesTest.shouldExportUserPropertyOverride —
    expected: <test> but was: <world>, i.e. --property hello=test not overriding the sample
    application.properties. Three attempts (surefire.rerunFailingTestsCount=2), all fail.
  • Camel :: AI :: Docling ... SUCCESS [23.192 s] in the very same reactor run, so the component builds and
    its tests pass on the PR.
  • It is not specific to this branch: the same test fails on unrelated PRs in the same window —
    fix/camel-26653-minio-max-mesages-per-poll (run 43088298315) and feature/CAMEL-23832-cxf-descriptions
    (run 43073244596). camel-jbang-core has no dependency on camel-docling.

So I read it as a main-branch problem, not something this PR introduces. Nothing to change here for it, but
the check will show red until main is fixed or the run is retried.

@davsclaus davsclaus 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.

Thanks @WMK666666, re-review of 80d7804. Both open points from my last review are handled:

  1. Batch workers reading the exchange: fixed. resolveOcrSettings(exchange) (DoclingProducer.java:1615) resolves the two OCR decisions once, into the immutable OcrSettings holder. processBatchConversion and processBatchStructuredData call it on the caller's thread and pass the holder down, so neither convertDocumentsBatch nor convertStructuredDataBatch touches the exchange any more, including inside the supplyAsync workers. The request builders and applyConfigurationToOptions take only the holder, so the old shape cannot creep back in. The single-document paths resolve it where they already hold the exchange, and the precedence logic moved over unchanged. Batch is docling-serve only, so the CLI path needed no change, as you said.
  2. The enableOCR=false rule is now pinned on both paths: aLanguageHeaderDoesNotTurnOnOcrThatTheEndpointDisabledOnTheCli and ...InTheServeRequest (DoclingOcrHeadersTest.java:122, :130).
  3. Upgrade guide overlap: as expected, #29591 merged first, and the PR now conflicts on the end of camel-4x-upgrade-guide-4_24.adoc. That file is the only conflict. Please rebase on main and keep both camel-docling sections (the apiKeyHeader one from main and your OCR-headers one). DoclingProducer.java merges cleanly with #29591 and #29593.

No new problems in this commit. Approving; it can merge once the rebase is pushed and CI is green.

Claude Code on behalf of davsclaus. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.

…e request

Rebased on main now that apache#29591 and apache#29593 are in.

The only conflict was the end of the camel-docling section of the 4.24
upgrade guide, where apache#29591 appended its apiKeyHeader entry: both entries
are kept, the apiKeyHeader one first.

DoclingProducer.java also gained the bearer-token client from apache#29593; it
merged cleanly with the OCR header resolution, which sits in
resolveOcrSettings and applyConfigurationToOptions.

Content is otherwise the one approved in 80d7804: the two headers are
resolved once per message into OcrSettings on the thread that owns the
exchange, so no batch worker touches the exchange, and a language header
does not turn OCR back on when the endpoint disabled it.
@WMK666666

Copy link
Copy Markdown
Contributor Author

Rebased on main (de03c2d). The only conflict was where you said: the end of the camel-docling section
of camel-4x-upgrade-guide-4_24.adoc, because #29591 appended its apiKeyHeader entry there. Both entries
are kept — the apiKeyHeader one first, then the OCR headers one — with a blank line between them.

DoclingProducer.java merged cleanly, and it did have something to merge: #29593 added the
BearerTokenDoclingServeClient selection and getDoclingServeApi(), which now sit above
resolveOcrSettings. That is the only difference between the rebased file and the one you approved.

The diff is unchanged by the rebase, which is the check I used to confirm nothing was lost:
+339/-35 over the same three files (DoclingProducer.java +131/-35, DoclingOcrHeadersTest.java +196,
the upgrade guide +12), one commit on top of 009cff12.

CI will need another approval for the new commit; nothing else has changed.

@davsclaus davsclaus 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.

TL;DR

Approve: the force-push squashes and rebases the approved 80d7804 with no change to its content. Blockers: None. Other findings: 0

Thanks @WMK666666. The PR's own changed lines before and after the force-push are identical across DoclingProducer.java, DoclingOcrHeadersTest.java and the upgrade guide. The upgrade-guide conflict is resolved as asked: the apiKeyHeader section from #29591 comes first, then the OCR-headers section.

Heads-up: #29594 also appends a camel-docling section at the same spot in the upgrade guide, so whichever of the two merges second needs a trivial rebase.


Claude Code on behalf of davsclaus. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.

@davsclaus davsclaus added this to the 4.24.0 milestone Nov 12, 2026
@davsclaus davsclaus added the bug Something isn't working label Nov 12, 2026
@davsclaus
davsclaus merged commit 8548ae0 into apache:main Nov 12, 2026
7 checks passed
WMK666666 added a commit to WMK666666/camel that referenced this pull request Nov 12, 2026
…ocling-serve

Rebased on main now that apache#29595 is in, as asked.

The only conflict was the 4.24 upgrade guide, where apache#29595 appended its
OCR-headers entry after apache#29591's apiKeyHeader one: the file now carries
all three camel-docling sections, apiKeyHeader first, then the OCR
headers, then html_split_page.

DoclingProducer.java merged cleanly with the OcrSettings refactor from
apache#29595: the html_split_page rejection and the package-private
mapToOutputFormat sit below resolveOcrSettings and are untouched by it.
DoclingContentExtractor.java is unchanged on main.

Content is otherwise the version approved in 55f1def: DoclingContentExtractor
reads doctags_content when DocTags is requested, and mapToOutputFormat
rejects html_split_page with the reason and the way out, because
DocumentResponse has no field for the split pages.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants