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 f27b0a9fb1 TIKA-4889: Deprecate and document still experimental in
4.1.0 (#3174)
f27b0a9fb1 is described below
commit f27b0a9fb104ede2a13a77888de565a1e78e0702
Author: Tim Allison <[email protected]>
AuthorDate: Tue Sep 15 15:54:35 2026 -0400
TIKA-4889: Deprecate and document still experimental in 4.1.0 (#3174)
---
CHANGES.txt | 19 +++-
docs/modules/ROOT/pages/configuration/index.adoc | 26 +++---
.../ROOT/pages/configuration/inference.adoc | 17 ++--
.../pages/configuration/parsers/tess4j-parser.adoc | 4 +-
.../parsers/tesseract-ocr-parser.adoc | 4 +-
.../pages/configuration/parsers/vlm-parsers.adoc | 25 +++--
.../ROOT/pages/developers/custom-ocr-engine.adoc | 6 +-
.../pages/migration-to-4x/chunk-strategies.adoc | 24 +++--
.../pages/migration-to-4x/migrating-to-4x.adoc | 39 +++++---
.../tika/inference/ImageEmbeddingConfig.java | 3 +
.../tika/inference/OpenAIImageEmbeddingParser.java | 13 ++-
.../config-examples/claude-vlm-basic.json | 9 +-
.../resources/config-examples/claude-vlm-full.json | 9 +-
.../config-examples/gemini-vlm-basic.json | 9 +-
.../resources/config-examples/gemini-vlm-full.json | 9 +-
.../config-examples/openai-vlm-basic.json | 9 +-
.../resources/config-examples/openai-vlm-full.json | 9 +-
.../apache/tika/config/loader/ParserLoader.java | 104 ++++++++++++---------
.../config/loader/ContentEnricherLoaderTest.java | 31 ++++--
.../sample-configs/vision/vlm-claude.json | 12 ++-
.../sample-configs/vision/vlm-gemini.json | 12 ++-
.../sample-configs/vision/vlm-openai.json | 11 ++-
22 files changed, 261 insertions(+), 143 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index bf78778206..1c7aeca008 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,19 @@
Release 4.1.0 - unreleased
+ * An inference engine named under "parsers" (an OCR engine, a VLM parser,
+ openai-image-embedding-parser) is deprecated: since 4.1.0 startup logs
+ one WARN per such entry saying what it does today, and in 4.2.0 the
+ entry fails config load. Configure engines under "engines" and name them
+ in "text-recognizers" or bind them with "inference". The whole-PDF VLM
+ shape (claude-vlm-parser with pdf-parser excluded) goes with it; the
+ replacement is pdf-parser "text": "OCR" with the VLM as the recognizer
+ (TIKA-4889).
+
+ * openai-image-embedding-parser is deprecated, removal in 4.2.0: the same
+ endpoint as an openai-embedding-engine with an IMAGES inference binding
+ embeds a document's images in requests of the engine's batch size
+ (TIKA-4889).
+
* PDF: "pdf-parser": {"rendering": {"dpi", "imageType", "imageFormat",
"imageQuality", "maxImagePixels"}} sets the page images emitted under
RENDER_PAGES_BEFORE_PARSE and RENDER_PAGES_AT_PAGE_END; a field left out
@@ -91,8 +105,9 @@ Release 4.1.0 - unreleased
* New "inference" config section: bind a named engine to an input (IMAGES)
and tasks (embed), and every image document Tika parses is embedded in
- one request per document tree; vectors land on the document the image
- belongs to, as it appears in the output. Per request, "inference" in
+ requests of the engine's batch size, per document tree, not one per
+ image; vectors land on the document the image belongs to, as it appears
+ in the output. Per request, "inference" in
parse-context selects bindings or switches inference off.
openai-image-embedding-parser keeps working, one request per image
(TIKA-4889).
diff --git a/docs/modules/ROOT/pages/configuration/index.adoc
b/docs/modules/ROOT/pages/configuration/index.adoc
index de6d6ddb2c..90b6b248fc 100644
--- a/docs/modules/ROOT/pages/configuration/index.adoc
+++ b/docs/modules/ROOT/pages/configuration/index.adoc
@@ -205,20 +205,22 @@ found on the classpath; among those the last registered
wins (user-supplied clas
after Tika's); an entry's `_mime-include`/`_mime-exclude` applies, and on the
`default-parser`
entry it applies to every engine inside it. When several engines in the
winning tier claim a
type, a WARN at startup names them and the winner; an engine under `parsers`
shadows a classpath
-engine silently, by design. Name the engine in `text-recognizers` to stop
depending on any of
-this. An `AutoDetectParser` built in code without the loader applies the same
rules at parse
+engine (and draws the deprecation WARN below). Name the engine in
`text-recognizers` to stop
+depending on any of this. An `AutoDetectParser` built in code without the
loader applies the same rules at parse
time.
-=== An engine under `parsers`
-
-Naming an engine under `parsers` still works, and is how 4.0 configured
Tesseract's options, but
-it means two things at once: the entry configures the engine, and the engine
is also a parser
-for the types no other parser there claims (`"parsers":
[{"tesseract-ocr-parser": {}}]` alone
-sends every image straight to OCR; `claude-vlm-parser` next to a
`default-parser` that excludes
-`pdf-parser` makes Claude the PDF parser). When every type the engine
advertises is claimed by
-another parser it is never dispatched to: startup logs that it then acts only
as the text
-recognizer, and warns if it never runs at all. Prefer `text-recognizers` for
the engine and keep
-`parsers` for parsers.
+=== An engine under `parsers` (deprecated)
+
+Naming an engine under `parsers` is how 4.0 configured Tesseract's options. It
is deprecated
+since 4.1.0 and unsupported in 4.2.0, when `parsers` holds parsers only and an
engine there
+fails config load. Until then it keeps working, with a WARN at startup that
says what the entry
+does today, because it means two things at once: the entry configures the
engine, and the engine
+is also a parser for the types no other parser there claims (`"parsers":
+[{"tesseract-ocr-parser": {}}]` alone sends every image straight to OCR;
`claude-vlm-parser`
+next to a `default-parser` that excludes `pdf-parser` makes Claude the PDF
parser). When every
+type the engine advertises is claimed by another parser it is never dispatched
to and acts only
+as the text recognizer, or never runs at all. Configure the engine under
`engines`, name it in
+`text-recognizers`, and keep `parsers` for parsers.
=== Replacing extracted text
diff --git a/docs/modules/ROOT/pages/configuration/inference.adoc
b/docs/modules/ROOT/pages/configuration/inference.adoc
index a868057578..f559595fe7 100644
--- a/docs/modules/ROOT/pages/configuration/inference.adoc
+++ b/docs/modules/ROOT/pages/configuration/inference.adoc
@@ -106,9 +106,9 @@ on a chat engine), or an unknown key fails config load.
Parsers do not call engines. Every image document Tika parses is offered to
one dispatcher
after its own parser has run, whatever container it came from and however
deep. The
dispatcher keeps the units of the whole document tree until the top-level
parse ends, then
-sends each binding's units to its engine in `maxBatchSize` requests: a docx
with twelve
-pictures is one embeddings request, not twelve; if one image in a batch is
rejected, the rest
-are retried one by one. Results land on the document a unit belongs to, as it
appears in the
+sends each binding's units to its engine in requests of up to `maxBatchSize`
units: a docx
+with twelve pictures is one embeddings request, not twelve, when the engine
takes twelve at
+once; if one image in a batch is rejected, the rest are retried one by one.
Results land on the document a unit belongs to, as it appears in the
output. In the recursive outputs (`/rmeta`, `-J`, pipes `RMETA`), where every
document has
its own metadata object, a picture in the body of a docx or an email puts its
vector on the
docx or the email, with an `embedded` locator naming the picture, whether the
docx is the
@@ -231,9 +231,10 @@ request" (see
xref:configuration/inference-recipes.adoc[Recipes]). Names only, s
xref:using-tika/server/index.adoc[preset]. A name not in the configured list
fails the request.
Engines cannot be defined or changed per request.
-== From `openai-image-embedding-parser`
+== From `openai-image-embedding-parser` (deprecated)
-The 4.0 `openai-image-embedding-parser` still works as a text-recognizers
entry and embeds one
-image per request. The same endpoint as an engine plus a binding on `IMAGES`
embeds a
-document's images in one request and puts the vectors where they belong; move
the `baseUrl`,
-`model` and `apiKey` to the engine and drop the parser entry.
+The 4.0 `openai-image-embedding-parser` is deprecated since 4.1.0 and removed
in 4.2.0. It
+still works as a text-recognizers entry, with a WARN at startup, and embeds
one image per
+request. The same endpoint as an engine plus a binding on `IMAGES` embeds a
document's images
+in requests of the engine's batch size and puts the vectors where they belong;
move the
+`baseUrl`, `model` and `apiKey` to the engine and drop the parser entry.
diff --git a/docs/modules/ROOT/pages/configuration/parsers/tess4j-parser.adoc
b/docs/modules/ROOT/pages/configuration/parsers/tess4j-parser.adoc
index 8c7397befe..0f0b2c05a1 100644
--- a/docs/modules/ROOT/pages/configuration/parsers/tess4j-parser.adoc
+++ b/docs/modules/ROOT/pages/configuration/parsers/tess4j-parser.adoc
@@ -87,8 +87,8 @@ JNA must be able to find `libtesseract` and `libleptonica` at
runtime.
The tess4j jar does *not* bundle these libraries for macOS or Linux.
If JNA cannot find them on the default library search path, the parser
advertises no media types: named under `text-recognizers` that fails
-config load with a message naming it; under `parsers` it is logged at
-startup and the parser does nothing.
+config load with a message naming it; under `parsers` (the deprecated
+4.0 shape) it is logged at startup and the parser does nothing.
You have several options:
diff --git
a/docs/modules/ROOT/pages/configuration/parsers/tesseract-ocr-parser.adoc
b/docs/modules/ROOT/pages/configuration/parsers/tesseract-ocr-parser.adoc
index 18f6da8b2a..d5db51f8e8 100644
--- a/docs/modules/ROOT/pages/configuration/parsers/tesseract-ocr-parser.adoc
+++ b/docs/modules/ROOT/pages/configuration/parsers/tesseract-ocr-parser.adoc
@@ -38,7 +38,7 @@ binary is found: the loader finds it among the loaded parsers
because it impleme
`TextRecognizer` and advertises the image types it reads, and logs the choice
at startup. When
another classpath engine such as Tess4J claims the same types, one is used and
a WARN at startup
names the collision and the winner; an engine named under `parsers` (a VLM
parser, for instance)
-takes precedence over Tesseract without a warning. To turn OCR off everywhere,
set
+takes precedence over Tesseract, with the deprecation WARN that shape draws
since 4.1.0. To turn OCR off everywhere, set
`"text-recognizers": []`. See xref:configuration/index.adoc[Configuration] for
how the list
behaves, and xref:developers/custom-ocr-engine.adoc[Wiring in Your Own OCR
Engine] to plug in
an engine Tika does not ship.
@@ -55,7 +55,7 @@ icon:github[]
https://github.com/apache/tika/blob/main/tika-parsers/tika-parsers
Every option with its default value and an inline comment describing it. Named
under
`text-recognizers`, the engine leaves every default parser loaded; the same
options apply to an
-entry under `parsers`, the 4.0 shape. ImageMagick is optional; it is used only
when
+entry under `parsers`, the 4.0 shape, which is deprecated since 4.1.0 and
unsupported in 4.2.0. ImageMagick is optional; it is used only when
`enableImagePreprocessing` or `applyRotation` is true.
[source,json]
diff --git a/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
b/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
index 109441fbe4..56ea18c93f 100644
--- a/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
+++ b/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
@@ -29,8 +29,9 @@ for earlier shapes.
Three implementations are provided out of the box. None is auto-loaded: each
must be
named explicitly in your configuration. (Changed in 4.1.0: `openai-vlm-parser`
previously
auto-registered via SPI.) To use a VLM as the OCR engine for embedded images
and rendered
-PDF pages, name it in the `text-recognizers` list — see
-xref:configuration/index.adoc[Configuration].
+PDF pages, configure it under `engines` and name it in the `text-recognizers`
list — see
+xref:configuration/index.adoc[Configuration]. Naming it under `parsers` is
deprecated since
+4.1.0 and unsupported in 4.2.0.
[cols="1,2,1"]
|===
@@ -50,9 +51,9 @@ xref:configuration/index.adoc[Configuration].
|===
All three enrich the standard image types (`image/png`, `image/jpeg`, ...).
-`ClaudeVLMParser` and `GeminiVLMParser` additionally declare
`application/pdf`, so they can process
-PDFs natively with the model's vision capabilities once the default
`pdf-parser` is excluded (see
-below); an enricher never displaces the parser for a type.
+`ClaudeVLMParser` and `GeminiVLMParser` additionally declare
`application/pdf`, so under `parsers`
+they can process PDFs natively with the model's vision capabilities once the
default `pdf-parser`
+is excluded (see below, deprecated); an enricher never displaces the parser
for a type.
== Module dependency
@@ -142,12 +143,18 @@ endpoint. The API key is passed as a `key` query
parameter.
Change `baseUrl` if you are using Vertex AI or a proxy.
-== Using a VLM parser for PDF parsing
+== Using a VLM parser for PDF parsing (deprecated)
Claude and Gemini can process entire PDFs with their vision capabilities.
To route PDFs to a VLM parser instead of the default `PDFParser`, exclude
the default and add the VLM parser:
+NOTE: This shape is deprecated since 4.1.0 and unsupported in 4.2.0, when an
engine under
+`parsers` fails config load. The 4.2 way to put a PDF in front of a VLM is the
PDF parser's
+`"text": "OCR"` with the VLM named in `text-recognizers`: every page is
rendered and transcribed,
+and the PDF's own structure (attachments, bookmarks, forms) is still parsed.
That sends page
+images, not the PDF file; there is no 4.2 shape yet for sending the file
itself.
+
[source,json]
----
include::example$vlm-pdf-parsing.json[]
@@ -156,9 +163,9 @@ include::example$vlm-pdf-parsing.json[]
You can substitute `gemini-vlm-parser` for `claude-vlm-parser` above.
Named under `parsers`, the VLM is also the text recognizer for the image types
it advertises, so
-embedded images in every other document type are sent to it too. Add a
`text-recognizers` list to
-choose a different engine for those, or `"text-recognizers": []` for none; see
-xref:configuration/index.adoc[Configuration].
+embedded images in every other document type are sent to it too, and startup
logs the deprecation
+WARN. Add a `text-recognizers` list to choose a different engine for those, or
+`"text-recognizers": []` for none; see
xref:configuration/index.adoc[Configuration].
== Configuration options reference
diff --git a/docs/modules/ROOT/pages/developers/custom-ocr-engine.adoc
b/docs/modules/ROOT/pages/developers/custom-ocr-engine.adoc
index 99c3731bf1..340456b036 100644
--- a/docs/modules/ROOT/pages/developers/custom-ocr-engine.adoc
+++ b/docs/modules/ROOT/pages/developers/custom-ocr-engine.adoc
@@ -271,9 +271,9 @@ page renders, exactly as a dispatched parser would be, so
the metadata tells you
== Common mistakes
-* Naming the engine under `parsers`. It still works, but the entry then means
two things at once
- (configure the engine, and parse the types nobody else claims), and startup
has to tell you
- which happened. `text-recognizers` says what you mean.
+* Naming the engine under `parsers`. Deprecated since 4.1.0 and unsupported in
4.2.0: the entry
+ means two things at once (configure the engine, and parse the types nobody
else claims), and
+ startup has to tell you which happened. `engines` plus `text-recognizers`
says what you mean.
* Creating a fresh `ParseContext` inside `parse`. The caller's context carries
the recursion guard
that stops an enricher from enriching its own output; pass the one you were
given.
* Claiming types you do not enrich. Every advertised type routes images to
you, and a type you
diff --git a/docs/modules/ROOT/pages/migration-to-4x/chunk-strategies.adoc
b/docs/modules/ROOT/pages/migration-to-4x/chunk-strategies.adoc
index 1f0a15966e..ad8027e683 100644
--- a/docs/modules/ROOT/pages/migration-to-4x/chunk-strategies.adoc
+++ b/docs/modules/ROOT/pages/migration-to-4x/chunk-strategies.adoc
@@ -123,17 +123,22 @@ pictures. Nothing to configure:
[source,json]
----
{
- "text-recognizers": [
- { "openai-image-embedding-parser": { "baseUrl": "http://clip-server:8000",
- "model": "jinaai/jina-clip-v2" } }
- ],
- "metadata-filters": [
- { "openai-embedding-filter": { "baseUrl": "http://embed-server:8000",
- "model": "text-embedding-3-small" } }
+ "engines": {
+ "clip": { "openai-embedding-engine": { "baseUrl":
"http://clip-server:8000",
+ "model": "jinaai/jina-clip-v2" } },
+ "text": { "openai-embedding-engine": { "baseUrl":
"http://embed-server:8000",
+ "model": "text-embedding-3-small" }
}
+ },
+ "inference": [
+ { "id": "pictures", "engine": "clip", "input": "IMAGES", "tasks":
["embed"] },
+ { "id": "text", "engine": "text", "input": "TEXT", "tasks": ["embed"] }
]
}
----
+(The 4.0 `openai-image-embedding-parser` and `openai-embedding-filter`,
deprecated since 4.1.0,
+place chunks by the same rule.)
+
The rules:
* `INLINE` and `RENDERING` children hand their chunks to the parent.
Attachments keep their
@@ -143,8 +148,9 @@ The rules:
* Every chunk written to a parent carries an `embedded` locator,
`{"id_path": "/1", "name": "image1.png"}`, naming the child it came from,
beside any
locator it already had.
-* `"liftToParent": false` on the embedder keeps every picture's vector on the
picture, for an
- index where each picture is a hit of its own.
+* `"liftToParent": false` on the deprecated `openai-image-embedding-parser`
keeps every
+ picture's vector on the picture, for an index where each picture is a hit of
its own; the
+ `IMAGES` binding has no such switch.
* In the recursive (`/rmeta`, `-J`, pipes `RMETA`) parse the wrapper keeps
every document's
metadata, so a chunk lands on its parent's. In the single-object parse
(`/tika` as JSON,
`-j`, pipes `CONCATENATE`) only the top-level document's metadata is handed
back, so every
diff --git a/docs/modules/ROOT/pages/migration-to-4x/migrating-to-4x.adoc
b/docs/modules/ROOT/pages/migration-to-4x/migrating-to-4x.adoc
index f74222b56a..ca04cb8671 100644
--- a/docs/modules/ROOT/pages/migration-to-4x/migrating-to-4x.adoc
+++ b/docs/modules/ROOT/pages/migration-to-4x/migrating-to-4x.adoc
@@ -249,22 +249,35 @@ What changes for you:
* A custom engine that still advertises the pseudo-types keeps working as a
text recognizer for the
real types, with a WARN at startup, until 5.0. Advertise the real types and
implement
`TextRecognizer` to silence it.
-* Put the engine in the top-level `text-recognizers` list rather than under
`parsers`; every
- default parser stays loaded. `"text-recognizers": []` turns enrichment off
everywhere. An engine
- left under `parsers` keeps working; startup logs when it acts only as the
enricher and warns
- when it never runs at all. Startup also logs one line per effective engine.
+* Put the engine under `engines` and name it in the top-level
`text-recognizers` list rather
+ than under `parsers`; every default parser stays loaded.
`"text-recognizers": []` turns
+ enrichment off everywhere. An engine left under `parsers` keeps working in
4.1.x with a
+ deprecation WARN at startup that says what the entry does (see below).
Startup also logs one
+ line per effective engine.
* VLM parsers gain `textRecognizer` (default `true`); set it to `false` for a
captioning or tagging
prompt so the PDF parser's `AUTO` OCR never replaces extracted text with the
VLM's output.
-=== Embedding filters deprecated (4.1.0+)
-
-`openai-embedding-filter` and `jina-embedding-filter` are deprecated and will
be removed in
-4.2.0. Configure the endpoint once under `engines` and bind it with a `TEXT`
-xref:configuration/inference.adoc#text[inference binding]; it embeds the whole
document tree in
-batched requests. Until then they keep working from the server-side config. A
request can no
-longer name them in a per-request `metadata-filters` list (4.0.x accepted a
caller-supplied
-`baseUrl` there); the flat per-request `{"openai-embedding-filter":
{"skipEmbedding": true}}`
-form is unchanged.
+=== Engines under `parsers` deprecated (4.1.0+)
+
+An inference engine named as a `parsers` entry, an OCR engine, a VLM parser or
the image
+embedding parser, is deprecated since 4.1.0 and fails config load in 4.2.0,
when `parsers`
+holds parsers only. Startup logs one WARN per such entry saying what it does
today: it parses
+the types no other parser claims, it acts only as the text recognizer, or it
never runs.
+Configure the engine under `engines` and name it in `text-recognizers` (or
bind it with
+`inference`); every default parser stays loaded. Sending a whole PDF to Claude
or Gemini by
+excluding `pdf-parser` goes with it: the 4.2 shape is the PDF parser's
`"text": "OCR"` with the
+VLM as recognizer, which sends rendered pages rather than the file.
+
+=== Embedding filters and the image embedding parser deprecated (4.1.0+)
+
+`openai-embedding-filter`, `jina-embedding-filter` and
`openai-image-embedding-parser` are
+deprecated and will be removed in 4.2.0. Configure the endpoint once under
`engines` and bind
+it with a `TEXT` or `IMAGES` xref:configuration/inference.adoc[inference
binding]; it embeds
+the whole document tree in batched requests and puts a picture's vector on the
document it is
+part of. Until then they keep working from the server-side config, with a WARN
at startup. A
+request can no longer name the filters in a per-request `metadata-filters`
list (4.0.x
+accepted a caller-supplied `baseUrl` there); the flat per-request
+`{"openai-embedding-filter": {"skipEmbedding": true}}` form is unchanged.
=== PDF text policy: `ocr.strategy` becomes `text` (4.1.0+)
diff --git
a/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/ImageEmbeddingConfig.java
b/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/ImageEmbeddingConfig.java
index 1d8da98f2f..f0589ccd7c 100644
---
a/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/ImageEmbeddingConfig.java
+++
b/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/ImageEmbeddingConfig.java
@@ -25,7 +25,10 @@ import org.apache.tika.utils.StringUtils;
/**
* Configuration for image embedding parsers that call a CLIP-like
* vector endpoint.
+ *
+ * @deprecated since 4.1.0, removed in 4.2.0 with {@link
OpenAIImageEmbeddingParser}.
*/
+@Deprecated
public class ImageEmbeddingConfig implements Serializable {
private static final long serialVersionUID = 1L;
diff --git
a/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/OpenAIImageEmbeddingParser.java
b/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/OpenAIImageEmbeddingParser.java
index 0dc5595dc7..af96034cca 100644
---
a/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/OpenAIImageEmbeddingParser.java
+++
b/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/OpenAIImageEmbeddingParser.java
@@ -80,7 +80,13 @@ import org.apache.tika.utils.StringUtils;
* {@link #parse}.
*
* @since Apache Tika 4.0
+ * @deprecated since 4.1.0, removed in 4.2.0. Configure the endpoint as an
+ * {@code openai-embedding-engine} under {@code "engines"} and bind it with an
{@code IMAGES}
+ * {@code "inference"} binding and the {@code embed} task: a document tree's
images go in
+ * requests of the engine's batch size instead of one per image, and the
vectors land where
+ * {@link ChunkTarget} puts them.
*/
+@Deprecated
@TikaComponent(name = "openai-image-embedding-parser", spi = false)
public class OpenAIImageEmbeddingParser implements Parser, Initializable,
Closeable,
ContentEnricher {
@@ -185,9 +191,10 @@ public class OpenAIImageEmbeddingParser implements Parser,
Initializable, Closea
@Override
public void initialize() throws TikaConfigException {
- LOG.info("openai-image-embedding-parser runs one request per image;
the \"engines\" + "
- + "\"inference\" shape (openai-embedding-engine, input IMAGES,
task embed) batches "
- + "a document's images into one request");
+ LOG.warn("openai-image-embedding-parser is deprecated since 4.1.0 and
will be removed in "
+ + "4.2.0: configure the endpoint as an openai-embedding-engine
under \"engines\" "
+ + "and bind it with an IMAGES \"inference\" binding (task
embed), which sends a "
+ + "document's images in requests of the engine's batch size");
this.httpClient = TikaHttpClient.build(30);
}
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/claude-vlm-basic.json
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/claude-vlm-basic.json
index 47d819bc2f..753912ab5e 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/claude-vlm-basic.json
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/claude-vlm-basic.json
@@ -1,10 +1,15 @@
{
- "text-recognizers": [
- {
+ "engines": {
+ "claude": {
"claude-vlm-parser": {
"apiKey": "YOUR_ANTHROPIC_API_KEY",
"model": "claude-sonnet-4-20250514"
}
}
+ },
+ "text-recognizers": [
+ {
+ "engine": "claude"
+ }
]
}
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/claude-vlm-full.json
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/claude-vlm-full.json
index 3982ab9079..7a8aa21285 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/claude-vlm-full.json
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/claude-vlm-full.json
@@ -1,6 +1,6 @@
{
- "text-recognizers": [
- {
+ "engines": {
+ "claude": {
"claude-vlm-parser": {
"baseUrl": "https://api.anthropic.com",
"model": "claude-sonnet-4-20250514",
@@ -17,5 +17,10 @@
"allowRuntimePrompt": false
}
}
+ },
+ "text-recognizers": [
+ {
+ "engine": "claude"
+ }
]
}
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/gemini-vlm-basic.json
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/gemini-vlm-basic.json
index 720336981e..8ac7472b04 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/gemini-vlm-basic.json
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/gemini-vlm-basic.json
@@ -1,10 +1,15 @@
{
- "text-recognizers": [
- {
+ "engines": {
+ "gemini": {
"gemini-vlm-parser": {
"apiKey": "your-gemini-api-key",
"model": "gemini-2.5-flash"
}
}
+ },
+ "text-recognizers": [
+ {
+ "engine": "gemini"
+ }
]
}
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/gemini-vlm-full.json
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/gemini-vlm-full.json
index f5fb16c967..a6990c20a6 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/gemini-vlm-full.json
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/gemini-vlm-full.json
@@ -1,6 +1,6 @@
{
- "text-recognizers": [
- {
+ "engines": {
+ "gemini": {
"gemini-vlm-parser": {
"baseUrl": "https://generativelanguage.googleapis.com",
"model": "gemini-2.5-flash",
@@ -17,5 +17,10 @@
"allowRuntimePrompt": false
}
}
+ },
+ "text-recognizers": [
+ {
+ "engine": "gemini"
+ }
]
}
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/openai-vlm-basic.json
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/openai-vlm-basic.json
index d719f4da62..03a348e69a 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/openai-vlm-basic.json
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/resources/config-examples/openai-vlm-basic.json
@@ -1,11 +1,16 @@
{
- "text-recognizers": [
- {
+ "engines": {
+ "vlm": {
"openai-vlm-parser": {
"baseUrl": "http://127.0.0.1:8000",
"model": "jinaai/jina-vlm",
"timeoutMillis": 300000
}
}
+ },
+ "text-recognizers": [
+ {
+ "engine": "vlm"
+ }
]
}
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 24e0adb110..be2caef4a3 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
@@ -1,6 +1,6 @@
{
- "text-recognizers": [
- {
+ "engines": {
+ "vlm": {
"openai-vlm-parser": {
"baseUrl": "http://127.0.0.1:8000",
"completionsPath": "/v1/chat/completions",
@@ -18,5 +18,10 @@
"allowRuntimePrompt": false
}
}
+ },
+ "text-recognizers": [
+ {
+ "engine": "vlm"
+ }
]
}
diff --git
a/tika-serialization/src/main/java/org/apache/tika/config/loader/ParserLoader.java
b/tika-serialization/src/main/java/org/apache/tika/config/loader/ParserLoader.java
index 3d9e816920..5d939b3462 100644
---
a/tika-serialization/src/main/java/org/apache/tika/config/loader/ParserLoader.java
+++
b/tika-serialization/src/main/java/org/apache/tika/config/loader/ParserLoader.java
@@ -150,8 +150,8 @@ public class ParserLoader extends
AbstractSpiComponentLoader<Parser> {
* Content enrichers are settled once, over the whole tree: the configured
list, or
* with none, one engine per type resolved from the enrichers among the
loaded parsers.
* Either way every {@link EnrichingParser} gets a composite (possibly
empty), the
- * effective engines are logged, and an enricher under {@code "parsers"}
that nothing
- * dispatches to is called out.
+ * effective engines are logged, and every engine under {@code "parsers"},
the deprecated
+ * shape, is called out with what the entry does today.
*/
@Override
protected Parser finish(Parser root, LoaderContext context) throws
TikaConfigException {
@@ -159,8 +159,8 @@ public class ParserLoader extends
AbstractSpiComponentLoader<Parser> {
CompositeContentEnricher enrichers =
configured != null ? configured :
ContentEnrichers.resolve(root);
logEnrichers(enrichers, configured != null);
- for (Parser inert : undispatchedEnrichers(root)) {
- logUndispatched(inert, enrichers, configured != null);
+ for (Parser engine : enginesUnderParsers(root)) {
+ warnEngineUnderParsers(engine, root, enrichers, configured !=
null);
}
if (configured != null) {
for (Map.Entry<List<Parser>, Set<MediaType>> e
@@ -205,66 +205,80 @@ public class ParserLoader extends
AbstractSpiComponentLoader<Parser> {
return names.toString();
}
- /**
- * Enrichers named directly under {@code "parsers"} that the composite
never dispatches
- * to: every type they advertise is claimed by another parser there, or
they advertise
- * none (engine unavailable, or told to skip). Both shapes look configured
and do
- * nothing as parsers.
- */
- static List<Parser> undispatchedEnrichers(Parser root) {
- List<Parser> inert = new ArrayList<>();
+ /** Engines (enrichers) named directly under {@code "parsers"}: the
deprecated 4.0 shape. */
+ static List<Parser> enginesUnderParsers(Parser root) {
+ List<Parser> engines = new ArrayList<>();
if (!(root instanceof CompositeParser composite) || root instanceof
DefaultParser) {
- return inert;
+ return engines;
+ }
+ for (Parser member : composite.getAllComponentParsers()) {
+ if (ContentEnrichers.isEnricher(member)) {
+ engines.add(member);
+ }
+ }
+ return engines;
+ }
+
+ /** The types the composite dispatches to this member as their parser. */
+ static Set<MediaType> parsedTypes(Parser root, Parser member) {
+ Set<MediaType> parsed = new TreeSet<>();
+ if (!(root instanceof CompositeParser composite)) {
+ return parsed;
}
ParseContext empty = new ParseContext();
Map<MediaType, Parser> dispatch = composite.getParsers(empty);
MediaTypeRegistry registry = composite.getMediaTypeRegistry();
- for (Parser member : composite.getAllComponentParsers()) {
- if (!ContentEnrichers.isEnricher(member)) {
- continue;
- }
- boolean dispatched = false;
- for (MediaType type : member.getSupportedTypes(empty)) {
- if (dispatch.get(registry.normalize(type)) == member) {
- dispatched = true;
- break;
- }
- }
- if (!dispatched) {
- inert.add(member);
+ for (MediaType type : member.getSupportedTypes(empty)) {
+ if (dispatch.get(registry.normalize(type)) == member) {
+ parsed.add(type);
}
}
- return inert;
+ return parsed;
}
- // the 4.0 shape still works, so it is INFO; an entry that never runs at
all is a WARN
- private static void logUndispatched(Parser inert, CompositeContentEnricher
enrichers,
+ /**
+ * One WARN per engine under "parsers": the deprecation, then what the
entry does today. A
+ * deprecated engine class is left to its own WARN, which already says
where to go.
+ */
+ private void warnEngineUnderParsers(Parser engine, Parser root,
+ CompositeContentEnricher enrichers,
boolean listConfigured) {
- String name = ParserUtils.getParserClassname(inert);
- Set<MediaType> advertised = inert.getSupportedTypes(new
ParseContext());
+ if (unwrapClass(engine).isAnnotationPresent(Deprecated.class)) {
+ return;
+ }
+ String name = ParserUtils.getParserClassname(engine);
+ boolean recognizer = ContentEnrichers.asTextRecognizer(engine) != null;
+ String role = recognizer ? "text recognizer" : "annotator";
+ String lead = name + " is named under \"parsers\", which is deprecated
for engines since "
+ + "4.1.0 and unsupported in 4.2.0: configure it under
\"engines\" and "
+ + (recognizer ? "name it in \"text-recognizers\". "
+ : "bind it with \"inference\" (or name it in
\"text-recognizers\" to run it on "
+ + "every image). ");
+ Set<MediaType> advertised = engine.getSupportedTypes(new
ParseContext());
if (advertised.isEmpty()) {
- LOG.info("{} under \"parsers\" advertises no media types (engine
unavailable, or "
- + "configured to skip) and never runs. To turn enrichment
off, set "
- + "\"text-recognizers\": [] instead.", name);
+ LOG.warn("{}It advertises no media types (engine unavailable, or
configured to skip) "
+ + "and never runs; to turn recognition off, set
\"text-recognizers\": [].",
+ lead);
return;
}
+ Set<MediaType> parsed = parsedTypes(root, engine);
Set<MediaType> enriching = new TreeSet<>();
for (MediaType type : enrichers.getSupportedTypes()) {
- if (enrichers.getEnrichers(type).contains(inert)) {
+ if (enrichers.getEnrichers(type).contains(engine)) {
enriching.add(type);
}
}
- if (enriching.isEmpty()) {
- LOG.warn("{} under \"parsers\" is never dispatched to (every type
it advertises is "
- + "claimed by another parser) and {}, so it never runs.
Name it under "
- + "\"text-recognizers\" to invoke it, or exclude the
parser that "
- + "claims its types to dispatch to it.", name,
listConfigured
- ? "\"text-recognizers\" does not name it"
- : "another enricher is preferred for those types");
+ if (!parsed.isEmpty()) {
+ LOG.warn("{}Today it is the parser for {}{}; in 4.2.0 the default
parser keeps those "
+ + "types and the engine is invoked on their images and
rendered pages.", lead,
+ parsed, enriching.isEmpty() ? "" : " and the " + role + "
for " + enriching);
+ } else if (!enriching.isEmpty()) {
+ LOG.warn("{}It is never dispatched to as a parser (every type it
advertises is claimed "
+ + "by another parser); it acts only as the {} for {}.",
lead, role, enriching);
} else {
- LOG.info("{} under \"parsers\" is never dispatched to (every type
it advertises is "
- + "claimed by another parser); it acts only as the text
recognizer for "
- + "{}. Name it under \"text-recognizers\" to say so.",
name, enriching);
+ LOG.warn("{}It never runs: every type it advertises is claimed by
another parser and "
+ + "{}.", lead, listConfigured ? "\"text-recognizers\" does
not name it"
+ : "another engine is preferred for those types");
}
}
diff --git
a/tika-serialization/src/test/java/org/apache/tika/config/loader/ContentEnricherLoaderTest.java
b/tika-serialization/src/test/java/org/apache/tika/config/loader/ContentEnricherLoaderTest.java
index 8b0c5ae05a..d963731e3e 100644
---
a/tika-serialization/src/test/java/org/apache/tika/config/loader/ContentEnricherLoaderTest.java
+++
b/tika-serialization/src/test/java/org/apache/tika/config/loader/ContentEnricherLoaderTest.java
@@ -286,28 +286,41 @@ public class ContentEnricherLoaderTest {
}
/**
- * An enricher under "parsers" whose every type another parser claims is
never dispatched
- * to; the loader names it so a config that looks like a parser choice is
not a silent
- * enricher choice.
+ * An engine under "parsers" is the deprecated shape; the loader finds it
whether it is
+ * dispatched to as a parser or only ever runs as an enricher, and tells
them apart.
*/
@Test
- public void testUndispatchedEnricherUnderParsersIsReported() throws
Exception {
+ public void testEngineUnderParsersIsFound() throws Exception {
TikaLoader loader = load("""
{
"parsers": [ {"minimal-test-parser": {}},
{"test-spi-enricher": {}} ]
}
""");
- java.util.List<Parser> inert =
ParserLoader.undispatchedEnrichers(loader.loadParsers());
- assertEquals(1, inert.size());
- assertTrue(inert.get(0) instanceof TestSpiEnricher);
+ Parser root = loader.loadParsers();
+ java.util.List<Parser> engines =
ParserLoader.enginesUnderParsers(root);
+ assertEquals(1, engines.size());
+ assertTrue(engines.get(0) instanceof TestSpiEnricher);
+ assertTrue(ParserLoader.parsedTypes(root, engines.get(0)).isEmpty(),
+ "the parser claims its type: never dispatched to");
loader = load("""
{
"parsers": [ {"test-spi-enricher": {}} ]
}
""");
-
assertTrue(ParserLoader.undispatchedEnrichers(loader.loadParsers()).isEmpty(),
- "alone it is the parser for its type");
+ root = loader.loadParsers();
+ engines = ParserLoader.enginesUnderParsers(root);
+ assertEquals(1, engines.size());
+
assertEquals(java.util.Set.of(MediaType.parse("application/test+minimal")),
+ ParserLoader.parsedTypes(root, engines.get(0)), "alone it is
the parser for its type");
+
+ loader = load("""
+ {
+ "parsers": [ {"default-parser": {}} ]
+ }
+ """);
+
assertTrue(ParserLoader.enginesUnderParsers(loader.loadParsers()).isEmpty(),
+ "an engine the classpath supplies is not the deprecated
shape");
}
/** Two text recognizers on one type both run; the loader names them at
startup. */
diff --git a/tika-server/docker-build/sample-configs/vision/vlm-claude.json
b/tika-server/docker-build/sample-configs/vision/vlm-claude.json
index 22b2311c2c..27c25e4a05 100644
--- a/tika-server/docker-build/sample-configs/vision/vlm-claude.json
+++ b/tika-server/docker-build/sample-configs/vision/vlm-claude.json
@@ -1,16 +1,18 @@
{
- // Vision-Language Model parsing via Anthropic's Claude API.
- // Claude can handle OCR images and PDFs natively (no rasterization needed).
+ // Vision-Language Model OCR via Anthropic's Claude API: every default parser
+ // stays loaded, and embedded images and rendered PDF pages go to Claude when
+ // the PDF parser's "text" policy asks for OCR (AUTO by default).
// Set apiKey to your Anthropic API key — DO NOT commit a real key.
// Prefer passing it via the ANTHROPIC_API_KEY env var and substituting it
// at container start, e.g. via an entrypoint shim or sidecar that templates
// this file. See docs: configuration/parsers/vlm-parsers.
- "parsers": [
- {
+ "engines": {
+ "claude": {
"claude-vlm-parser": {
"apiKey": "${ANTHROPIC_API_KEY}",
"model": "claude-sonnet-4-20250514"
}
}
- ]
+ },
+ "text-recognizers": [ { "engine": "claude" } ]
}
diff --git a/tika-server/docker-build/sample-configs/vision/vlm-gemini.json
b/tika-server/docker-build/sample-configs/vision/vlm-gemini.json
index 54de0705aa..323257383d 100644
--- a/tika-server/docker-build/sample-configs/vision/vlm-gemini.json
+++ b/tika-server/docker-build/sample-configs/vision/vlm-gemini.json
@@ -1,15 +1,17 @@
{
- // Vision-Language Model parsing via Google's Gemini generateContent API.
- // Gemini can handle OCR images and PDFs natively (no rasterization needed).
+ // Vision-Language Model OCR via Google's Gemini generateContent API: every
+ // default parser stays loaded, and embedded images and rendered PDF pages go
+ // to Gemini when the PDF parser's "text" policy asks for OCR (AUTO by
default).
// Set apiKey to your Google AI Studio API key — DO NOT commit a real key.
// Prefer GEMINI_API_KEY env var + a templating entrypoint, similar to the
// Claude config. See docs: configuration/parsers/vlm-parsers.
- "parsers": [
- {
+ "engines": {
+ "gemini": {
"gemini-vlm-parser": {
"apiKey": "${GEMINI_API_KEY}",
"model": "gemini-2.5-flash"
}
}
- ]
+ },
+ "text-recognizers": [ { "engine": "gemini" } ]
}
diff --git a/tika-server/docker-build/sample-configs/vision/vlm-openai.json
b/tika-server/docker-build/sample-configs/vision/vlm-openai.json
index ecb0e3e87d..d516f4bcc8 100644
--- a/tika-server/docker-build/sample-configs/vision/vlm-openai.json
+++ b/tika-server/docker-build/sample-configs/vision/vlm-openai.json
@@ -1,17 +1,20 @@
{
- // Vision-Language Model parsing via an OpenAI-compatible endpoint.
+ // Vision-Language Model OCR via an OpenAI-compatible endpoint: every default
+ // parser stays loaded, and embedded images and rendered PDF pages go to the
+ // model when the PDF parser's "text" policy asks for OCR (AUTO by default).
// Works with self-hosted backends (vLLM, Ollama, a local FastAPI wrapper)
// or against OpenAI's own chat-completions API. Set baseUrl to wherever
// the OpenAI-compatible endpoint is reachable from the tika container.
// If the endpoint requires authentication, also set apiKey.
// See docs: configuration/parsers/vlm-parsers.
- "parsers": [
- {
+ "engines": {
+ "vlm": {
"openai-vlm-parser": {
"baseUrl": "http://vlm-server:8000",
"model": "jinaai/jina-vlm",
"timeoutMillis": 300000
}
}
- ]
+ },
+ "text-recognizers": [ { "engine": "vlm" } ]
}