This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new 5c60cf17b6 Support max_completion_tokens in OpenAI VLM parser (#3350)
5c60cf17b6 is described below
commit 5c60cf17b6b90346c360c53843a866c0a8abb968
Author: mstcomit <[email protected]>
AuthorDate: Fri Oct 16 20:06:25 2026 +0200
Support max_completion_tokens in OpenAI VLM parser (#3350)
Generated-by: GitHub Copilot
Co-authored-by: Michael Schobel-Thoma <[email protected]>
---
CHANGES.txt | 4 ++++
.../ROOT/pages/configuration/parsers/vlm-parsers.adoc | 6 +++++-
.../java/org/apache/tika/parser/vlm/OpenAIVLMParser.java | 5 ++++-
.../java/org/apache/tika/parser/vlm/VLMOCRConfig.java | 16 ++++++++++++++++
.../org/apache/tika/parser/vlm/OpenAIVLMParserTest.java | 8 ++++++++
.../tika/parser/vlm/VLMOperatorOnlyConfigTest.java | 2 +-
.../test/resources/config-examples/openai-vlm-full.json | 1 +
7 files changed, 39 insertions(+), 3 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 48dc600091..e2e97146b8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,9 @@
Release 4.2.0 - unreleased
+ * OpenAI-compatible VLM parser can send the output token limit as
+ max_completion_tokens for models that reject max_tokens. The new
+ useMaxCompletionTokens setting is opt-in; max_tokens remains the default.
+
* Config properties whose value reaches the host (paths, executables, class
names,
URLs, credentials, operator-sized limits) are marked @OperatorOnly and
refused in
per-request JSON before any setter runs. Newly locked: pdf-parser's
diff --git a/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
b/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
index 1e3aa0cef9..695c849c84 100644
--- a/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
+++ b/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
@@ -245,6 +245,10 @@ trusted callers. See
<<_per_request_configuration,Per-request configuration>>.
|`completionsPath`
|`/v1/chat/completions` (OpenAI/vLLM only)
|HTTP path appended to `baseUrl` for the chat-completions endpoint. Used by
the OpenAI-compatible parser only. Claude and Gemini hardcode their own API
paths (`/v1/messages` and `/v1beta/models/\{model}:generateContent`
respectively) and ignore this field.
+
+|`useMaxCompletionTokens`
+|`false` (OpenAI/vLLM only)
+|Send the output limit as `max_completion_tokens` instead of `max_tokens`.
Required for OpenAI / Azure OpenAI models that reject `max_tokens` (o-series,
GPT-5 and later); leave `false` for servers that only accept `max_tokens`.
|===
== Markdown-to-XHTML conversion
@@ -296,7 +300,7 @@ rejected with an exception:
|May be lowered, never raised above the initialization-time value — that
ceiling is what bounds
cost on a paid endpoint.
-|`maxImagePixels`, `maxRetries`, `allowRuntimePrompt`
+|`maxImagePixels`, `maxRetries`, `allowRuntimePrompt`, `useMaxCompletionTokens`
|Always blocked.
|`prompt`, `textRecognizer`
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/OpenAIVLMParser.java
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/OpenAIVLMParser.java
index 5a33752516..d72d767c91 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/OpenAIVLMParser.java
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/OpenAIVLMParser.java
@@ -51,6 +51,8 @@ import org.apache.tika.utils.StringUtils;
* {@code
/openai/deployments/{deployment}/chat/completions?api-version=2024-02-01}</li>
* <li>{@code apiKeyHeaderName} — {@code api-key}</li>
* <li>{@code apiKeyPrefix} — {@code ""} (empty string)</li>
+ * <li>{@code useMaxCompletionTokens} — {@code true} for models that reject
+ * {@code max_tokens} (o-series, GPT-5 and later)</li>
* </ul>
* <p>
* Configuration key: {@code "openai-vlm-parser"}
@@ -234,7 +236,8 @@ public class OpenAIVLMParser extends AbstractVLMParser {
String buildRequestJson(VLMOCRConfig config, String base64Data, String
mimeType) {
ObjectNode root = MAPPER.createObjectNode();
root.put("model", config.getModel());
- root.put("max_tokens", config.getMaxTokens());
+ root.put(config.isUseMaxCompletionTokens() ? "max_completion_tokens" :
"max_tokens",
+ config.getMaxTokens());
ArrayNode messages = root.putArray("messages");
ObjectNode userMessage = messages.addObject();
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/VLMOCRConfig.java
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/VLMOCRConfig.java
index 79ea76b38a..6eb174cd9e 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/VLMOCRConfig.java
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/VLMOCRConfig.java
@@ -61,6 +61,13 @@ public class VLMOCRConfig implements Serializable {
/** Maximum number of tokens the model may generate. */
private int maxTokens = 4096;
+ /**
+ * OpenAI-compatible parser only: send the limit as {@code
max_completion_tokens}
+ * instead of {@code max_tokens}. Newer OpenAI models (o-series, GPT-5 and
later)
+ * reject {@code max_tokens}; many other compatible servers only accept it.
+ */
+ private boolean useMaxCompletionTokens = false;
+
/**
* HTTP timeout in millis for the chat completions request.
* VLM inference can be slow; default is generous.
@@ -161,6 +168,15 @@ public class VLMOCRConfig implements Serializable {
this.maxTokens = maxTokens;
}
+ public boolean isUseMaxCompletionTokens() {
+ return useMaxCompletionTokens;
+ }
+
+ @OperatorOnly
+ public void setUseMaxCompletionTokens(boolean useMaxCompletionTokens) {
+ this.useMaxCompletionTokens = useMaxCompletionTokens;
+ }
+
public long getTimeoutMillis() {
return timeoutMillis;
}
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/OpenAIVLMParserTest.java
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/OpenAIVLMParserTest.java
index 6d1f28da99..ab9cbf1eec 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/OpenAIVLMParserTest.java
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/OpenAIVLMParserTest.java
@@ -260,6 +260,14 @@ public class OpenAIVLMParserTest {
assertEquals(2048, body.get("max_tokens").asInt());
}
+ @Test
+ void testMaxCompletionTokens() throws Exception {
+ config.setUseMaxCompletionTokens(true);
+ JsonNode body = MAPPER.readTree(parser.buildRequestJson(config,
"AAAA", "image/png"));
+ assertEquals(1024, body.get("max_completion_tokens").asInt());
+ assertNull(body.get("max_tokens"));
+ }
+
@Test
void testBuildRequestJson() {
String json = parser.buildRequestJson(config, "AAAA", "image/png");
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/VLMOperatorOnlyConfigTest.java
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/VLMOperatorOnlyConfigTest.java
index d355df5d81..f47182a77f 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/VLMOperatorOnlyConfigTest.java
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/VLMOperatorOnlyConfigTest.java
@@ -60,7 +60,7 @@ public class VLMOperatorOnlyConfigTest {
@ValueSource(strings = {"{\"model\": \"evil\"}", "{\"baseUrl\":
\"http://evil\"}",
"{\"completionsPath\": \"/evil\"}", "{\"apiKey\": \"evil\"}",
"{\"allowRuntimePrompt\": true}", "{\"maxRetries\": 99}",
- "{\"maxImagePixels\": 1}"})
+ "{\"maxImagePixels\": 1}", "{\"useMaxCompletionTokens\": true}"})
public void testPerRequestCannotSetOperatorOnlyFields(String json) {
Exception e = assertThrows(Exception.class, () -> perRequest(json));
assertTrue(e.getMessage().contains("at runtime"), e.getMessage());
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/openai-vlm-full.json
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/openai-vlm-full.json
index be2caef4a3..e7fbb2a7d4 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/openai-vlm-full.json
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/openai-vlm-full.json
@@ -7,6 +7,7 @@
"model": "jinaai/jina-vlm",
"prompt": "Extract all visible text from this image. Return the text
in markdown format, preserving the original structure (headings, lists, tables,
paragraphs). Do not describe the image. Only return the extracted text.",
"maxTokens": 4096,
+ "useMaxCompletionTokens": false,
"timeoutMillis": 300000,
"apiKey": "",
"inlineContent": true,