This is an automated email from the ASF dual-hosted git repository.

oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-kamelets.git


The following commit(s) were added to refs/heads/main by this push:
     new d107d3468 Fix #3015: Add LangChain4j document ingestion Kamelets 
(#3016)
d107d3468 is described below

commit d107d34685c5d3c72f1d63e505f05ab50c238d31
Author: Jiří Ondrušek <[email protected]>
AuthorDate: Tue Sep 15 11:23:35 2026 +0200

    Fix #3015: Add LangChain4j document ingestion Kamelets (#3016)
    
    The ingestion half of a RAG setup over the camel-langchain4j-ingest
    component (Camel 4.23): a sink that splits, batch-embeds and stores
    documents with identity metadata and optional dedup; generic Tika and
    Docling parse actions that capture the document id before the parse, so
    a document cannot forge its own identity; and a file source with
    knowledge-base semantics (read without consuming, edits re-delivered,
    raw bytes for parsers). Citrus tests cover each Kamelet and the
    compositions; they need Camel 4.23+ and fail on older versions by
    design, gating the merge until the catalog bumps
    (run with -Dcamel.version=4.23.0-SNAPSHOT).
    
    Co-authored-by: Claude Fable 5 <[email protected]>
---
 docs/modules/ROOT/nav.adoc                         |   4 +
 kamelets/docling-convert-action.kamelet.yaml       |  86 ++++++++++++++
 .../langchain4j-ingest-file-source.kamelet.yaml    | 101 +++++++++++++++++
 kamelets/langchain4j-ingest-sink.kamelet.yaml      | 123 +++++++++++++++++++++
 kamelets/tika-extract-text-action.kamelet.yaml     |  86 ++++++++++++++
 .../kamelets/docling-convert-action.kamelet.yaml   |  86 ++++++++++++++
 .../langchain4j-ingest-file-source.kamelet.yaml    | 101 +++++++++++++++++
 .../kamelets/langchain4j-ingest-sink.kamelet.yaml  | 123 +++++++++++++++++++++
 .../kamelets/tika-extract-text-action.kamelet.yaml |  86 ++++++++++++++
 .../src/test/java/CommonIT.java                    |   8 ++
 .../docling-convert-route.citrus.it.yaml           |  89 +++++++++++++++
 .../langchain4j-ingest/docling-convert-route.yaml  |  61 ++++++++++
 ...ain4j-ingest-file-register-route.citrus.it.yaml |  84 ++++++++++++++
 .../langchain4j-ingest-file-register-route.yaml    |  57 ++++++++++
 ...chain4j-ingest-file-source-route.citrus.it.yaml |  72 ++++++++++++
 .../langchain4j-ingest-file-source-route.yaml      |  42 +++++++
 ...ngchain4j-ingest-file-tika-route.citrus.it.yaml |  65 +++++++++++
 .../langchain4j-ingest-file-tika-route.yaml        |  58 ++++++++++
 .../langchain4j-ingest-sink-route.citrus.it.yaml   |  62 +++++++++++
 .../langchain4j-ingest-sink-route.yaml             |  58 ++++++++++
 .../resources/langchain4j-ingest/pdfs/pump.pdf     | Bin 0 -> 850 bytes
 .../tika-extract-text-route.citrus.it.yaml         |  62 +++++++++++
 .../tika-extract-text-route.yaml                   |  60 ++++++++++
 23 files changed, 1574 insertions(+)

diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index 662aa4cc9..7406fe27d 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -74,6 +74,7 @@
 * xref:dns-dig-action.adoc[]
 * xref:dns-ip-action.adoc[]
 * xref:dns-lookup-action.adoc[]
+* xref:docling-convert-action.adoc[]
 * xref:drop-field-action.adoc[]
 * xref:drop-header-action.adoc[]
 * xref:drop-headers-action.adoc[]
@@ -164,6 +165,8 @@
 * xref:kubernetes-namespaces-source.adoc[]
 * xref:kubernetes-nodes-source.adoc[]
 * xref:kubernetes-pods-source.adoc[]
+* xref:langchain4j-ingest-file-source.adoc[]
+* xref:langchain4j-ingest-sink.adoc[]
 * xref:log-action.adoc[]
 * xref:log-sink.adoc[]
 * xref:mail-imap-source.adoc[]
@@ -245,6 +248,7 @@
 * xref:telegram-sink.adoc[]
 * xref:telegram-source.adoc[]
 * xref:throttle-action.adoc[]
+* xref:tika-extract-text-action.adoc[]
 * xref:timer-source.adoc[]
 * xref:timestamp-router-action.adoc[]
 * xref:topic-name-matches-filter-action.adoc[]
diff --git a/kamelets/docling-convert-action.kamelet.yaml 
b/kamelets/docling-convert-action.kamelet.yaml
new file mode 100644
index 000000000..d1a00f80e
--- /dev/null
+++ b/kamelets/docling-convert-action.kamelet.yaml
@@ -0,0 +1,86 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+  name: docling-convert-action
+  labels:
+    camel.apache.org/kamelet.type: "action"
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "4.22.1-SNAPSHOT"
+    camel.apache.org/kamelet.icon: 
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3QgeD0iOCIgeT0iNiIgd2lkdGg9IjI4IiBoZWlnaHQ9IjM2IiByeD0iMyIgZmlsbD0iIzhmYjRmZSIvPjxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjIwIiBoZWlnaHQ9IjMiIGZpbGw9IiNmZmYiLz48cmVjdCB4PSIxMiIgeT0iMTkiIHdpZHRoPSIyMCIgaGVpZ2h0PSIzIiBmaWxsPSIjZmZmIi8+PHJlY3QgeD0iMTIiIHk9IjI2IiB3aWR0aD0iMTQiIGhlaWdodD0iMyIgZmlsbD0iI2ZmZiIvPjxjaXJjbGUgY3g9IjQ2IiBjeT0iMjAiIHI9IjQiIGZpbG
 [...]
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.group: "Actions"
+    camel.apache.org/kamelet.namespace: "AI"
+spec:
+  definition:
+    title: "Docling Convert Action"
+    description: |-
+      Convert PDF, office and similar documents to structure-preserving 
markdown with
+      Docling, OCR included.
+
+      Before the conversion, the value of the `documentIdHeader` header is 
captured into the
+      `CamelLangChain4jIngestDocumentId` exchange property, so a converted 
document cannot
+      forge its own identity in steps that read it later (the 
langchain4j-ingest sink does);
+      the body is pinned to bytes and the `CamelDocling*` control headers are 
swept, because
+      none of them may be decided by a consumer-delivered payload. By default 
the `docling`
+      component executes a local docling CLI; set `doclingServeUrl` to use a 
Docling Serve
+      instance instead - the natural fit for self-contained Pipes.
+    type: object
+    properties:
+      documentIdHeader:
+        title: Document Id Header
+        description: Name of the header carrying the stable document id, 
captured into the
+          CamelLangChain4jIngestDocumentId exchange property before the 
conversion.
+        type: string
+        default: CamelLangChain4jIngestDocumentId
+      doclingServeUrl:
+        title: Docling Serve URL
+        description: Address of a Docling Serve instance to convert through, 
such as
+          http://docling-serve:5001; when unset, the docling component's own 
configuration
+          applies (a local docling CLI by default).
+        type: string
+      useDoclingServe:
+        title: Use Docling Serve
+        description: Whether to convert through a Docling Serve instance 
instead of a local
+          docling CLI; set together with doclingServeUrl.
+        type: boolean
+  dependencies:
+    - "camel:core"
+    - "camel:docling"
+    - "camel:kamelet"
+  template:
+    from:
+      uri: "kamelet:source"
+      steps:
+        # captured before the conversion, so a document cannot forge its own 
identity
+        - setProperty:
+            name: "CamelLangChain4jIngestDocumentId"
+            simple: "${header[{{documentIdHeader}}]}"
+        - convertBodyTo:
+            type: "byte[]"
+        - removeHeaders:
+            pattern: "CamelDocling*"
+        - to:
+            uri: "docling:convert"
+            parameters:
+              operation: "CONVERT_TO_MARKDOWN"
+              contentInBody: "true"
+              useDoclingServe: "{{?useDoclingServe}}"
+              doclingServeUrl: "{{?doclingServeUrl}}"
diff --git a/kamelets/langchain4j-ingest-file-source.kamelet.yaml 
b/kamelets/langchain4j-ingest-file-source.kamelet.yaml
new file mode 100644
index 000000000..5114b2e42
--- /dev/null
+++ b/kamelets/langchain4j-ingest-file-source.kamelet.yaml
@@ -0,0 +1,101 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+  name: langchain4j-ingest-file-source
+  labels:
+    camel.apache.org/kamelet.type: "source"
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "4.22.1-SNAPSHOT"
+    camel.apache.org/kamelet.icon: 
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3QgeD0iOCIgeT0iNiIgd2lkdGg9IjI4IiBoZWlnaHQ9IjM2IiByeD0iMyIgZmlsbD0iIzhmYjRmZSIvPjxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjIwIiBoZWlnaHQ9IjMiIGZpbGw9IiNmZmYiLz48cmVjdCB4PSIxMiIgeT0iMTkiIHdpZHRoPSIyMCIgaGVpZ2h0PSIzIiBmaWxsPSIjZmZmIi8+PHJlY3QgeD0iMTIiIHk9IjI2IiB3aWR0aD0iMTQiIGhlaWdodD0iMyIgZmlsbD0iI2ZmZiIvPjxjaXJjbGUgY3g9IjQ2IiBjeT0iMjAiIHI9IjQiIGZpbG
 [...]
+    camel.apache.org/kamelet.group: "LangChain4j Ingest"
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.namespace: "AI"
+spec:
+  definition:
+    title: "LangChain4j Ingest File Source"
+    description: |-
+      Watch a directory as the source of an ingestion pipeline, with the 
defaults a knowledge
+      base needs: documents are left in place (noop), unchanged files are 
remembered in a
+      duplicate register keyed on path, modification time and size, a file 
still being copied
+      in is waited for (readLock=changed), and the file name is exposed as the 
document id in
+      the CamelLangChain4jIngestDocumentId header.
+
+      Mind the two dedup layers when composing: this source re-delivers an 
edited file (its
+      register key changes with the modification time), but a 
langchain4j-ingest-sink with an
+      idempotentRepository is first-write-wins by document id - the 
re-delivered edit would
+      be skipped. Use a version-aware id, or no sink repository, when 
documents change.
+    required:
+      - directory
+    type: object
+    properties:
+      directory:
+        title: Directory
+        description: The directory to ingest documents from.
+        type: string
+        pattern: "[^?#]+"
+      recursive:
+        title: Recursive
+        description: Whether subdirectories are ingested too.
+        type: boolean
+        default: true
+      delay:
+        title: Delay
+        description: Milliseconds between directory polls; the file endpoint's 
default is
+          500ms, which is aggressive for a knowledge-base directory.
+        type: integer
+      charset:
+        title: Charset
+        description: Character set for reading text documents, for example 
UTF-8. Leave
+          unset when the source feeds a parser action 
(tika-extract-text-action or
+          docling-convert-action), which must receive the raw bytes - a charset
+          conversion would corrupt a binary document such as a PDF.
+        type: string
+      idempotentRepository:
+        title: Idempotent Repository
+        description: An IdempotentRepository bean remembering already consumed 
files, as a
+          `#bean:name` reference; the register is keyed on path, modification 
time and
+          size, so an edited file is consumed again. When unset, the file 
endpoint's
+          default in-memory register (1000 entries) applies - eviction on a 
large directory
+          re-ingests files, so supply a sized or persistent repository for a 
real knowledge
+          base.
+        type: string
+  dependencies:
+    - "camel:core"
+    - "camel:file"
+    - "camel:kamelet"
+  template:
+    from:
+      uri: "file:{{directory}}"
+      parameters:
+        noop: "true"
+        idempotent: "true"
+        # an edited file gets a new key and re-ingests
+        idempotentKey: "${file:absolute.path}:${file:modified}:${file:size}"
+        recursive: "{{recursive}}"
+        readLock: "changed"
+        delay: "{{?delay}}"
+        charset: "{{?charset}}"
+        idempotentRepository: "{{?idempotentRepository}}"
+      steps:
+        - setHeader:
+            name: "CamelLangChain4jIngestDocumentId"
+            simple: "${header.CamelFileName}"
+        - to: "kamelet:sink"
diff --git a/kamelets/langchain4j-ingest-sink.kamelet.yaml 
b/kamelets/langchain4j-ingest-sink.kamelet.yaml
new file mode 100644
index 000000000..768c3e1c4
--- /dev/null
+++ b/kamelets/langchain4j-ingest-sink.kamelet.yaml
@@ -0,0 +1,123 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+  name: langchain4j-ingest-sink
+  labels:
+    camel.apache.org/kamelet.type: "sink"
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "4.22.1-SNAPSHOT"
+    camel.apache.org/kamelet.icon: 
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3QgeD0iOCIgeT0iNiIgd2lkdGg9IjI4IiBoZWlnaHQ9IjM2IiByeD0iMyIgZmlsbD0iIzhmYjRmZSIvPjxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjIwIiBoZWlnaHQ9IjMiIGZpbGw9IiNmZmYiLz48cmVjdCB4PSIxMiIgeT0iMTkiIHdpZHRoPSIyMCIgaGVpZ2h0PSIzIiBmaWxsPSIjZmZmIi8+PHJlY3QgeD0iMTIiIHk9IjI2IiB3aWR0aD0iMTQiIGhlaWdodD0iMyIgZmlsbD0iI2ZmZiIvPjxjaXJjbGUgY3g9IjQ2IiBjeT0iMjAiIHI9IjQiIGZpbG
 [...]
+    camel.apache.org/kamelet.group: "LangChain4j Ingest"
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.namespace: "AI"
+spec:
+  definition:
+    title: "LangChain4j Ingest Sink"
+    description: |-
+      Ingest documents into a LangChain4j EmbeddingStore: the payload is split 
into overlapping
+      segments, embedded in batches and written to the store, each segment 
stamped with the
+      pipeline name and the document id.
+
+      The document id is read from the `CamelLangChain4jIngestDocumentId` 
exchange property
+      when a prior step captured it (the parser actions do, before the parse), 
else from the
+      header named by `documentIdHeader` - the property always wins, so a 
parsed document
+      cannot forge its own identity. Bean references are `#bean:name` values; 
when
+      `embeddingStore` or `embeddingModel` is not set, the single registry 
bean of that type is
+      used. With `idempotentRepository` set, the first write per document id 
wins - a
+      re-delivered edited document is skipped, so streams that carry updates 
need a
+      version-aware id.
+    type: object
+    properties:
+      pipelineName:
+        title: Pipeline Name
+        description: The pipeline name, stamped on every written segment.
+        type: string
+        pattern: "[A-Za-z0-9._-]+"
+        default: ingest
+      documentIdHeader:
+        title: Document Id Header
+        description: Name of the header carrying the stable document id, such 
as CamelAwsS3Key
+          for an S3 source or CamelKafkaKey for a Kafka one; the
+          CamelLangChain4jIngestDocumentId exchange property, when set, takes 
precedence.
+        type: string
+        default: CamelLangChain4jIngestDocumentId
+      maxSegmentSize:
+        title: Max Segment Size
+        description: Maximum size of one segment, in characters.
+        type: integer
+        default: 500
+      maxOverlapSize:
+        title: Max Overlap Size
+        description: How much of the previous segment each segment repeats, in 
characters.
+        type: integer
+        default: 50
+      embeddingBatchSize:
+        title: Embedding Batch Size
+        description: How many segments are embedded per request to the 
embedding model.
+        type: integer
+        default: 32
+      maxDocumentSize:
+        title: Max Document Size
+        description: Maximum size of one document in characters; unset means 
no limit. The
+          pipeline holds a document in memory whole, so set the cap when the 
source can
+          deliver oversized payloads. An oversized document fails the exchange 
cleanly.
+        type: integer
+      documentSplitter:
+        title: Document Splitter
+        description: A DocumentSplitter bean replacing the default recursive 
splitting, as a
+          `#bean:name` reference; maxSegmentSize and maxOverlapSize are then 
ignored.
+        type: string
+      embeddingStore:
+        title: Embedding Store
+        description: The EmbeddingStore bean to write to, as a `#bean:name` 
reference.
+        type: string
+      embeddingModel:
+        title: Embedding Model
+        description: The EmbeddingModel bean to embed with, as a `#bean:name` 
reference.
+        type: string
+      idempotentRepository:
+        title: Idempotent Repository
+        description: An IdempotentRepository bean remembering ingested 
document ids, as a
+          `#bean:name` reference. A duplicate delivery is answered skipped, 
first write wins.
+        type: string
+  dependencies:
+    - "camel:core"
+    - "camel:langchain4j-ingest"
+    - "camel:kamelet"
+  template:
+    from:
+      uri: "kamelet:source"
+      steps:
+        # the endpoint itself resolves the id - the exchange property (set 
pre-parse by the
+        # parser actions) wins over the header, so no re-capture happens here: 
reading the
+        # header after a parse could hand a crafted document its own identity
+        - to:
+            uri: "langchain4j-ingest:{{pipelineName}}"
+            parameters:
+              documentIdHeader: "{{documentIdHeader}}"
+              maxSegmentSize: "{{maxSegmentSize}}"
+              maxOverlapSize: "{{maxOverlapSize}}"
+              embeddingBatchSize: "{{embeddingBatchSize}}"
+              maxDocumentSize: "{{?maxDocumentSize}}"
+              documentSplitter: "{{?documentSplitter}}"
+              embeddingStore: "{{?embeddingStore}}"
+              embeddingModel: "{{?embeddingModel}}"
+              idempotentRepository: "{{?idempotentRepository}}"
diff --git a/kamelets/tika-extract-text-action.kamelet.yaml 
b/kamelets/tika-extract-text-action.kamelet.yaml
new file mode 100644
index 000000000..b3465fbb8
--- /dev/null
+++ b/kamelets/tika-extract-text-action.kamelet.yaml
@@ -0,0 +1,86 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+  name: tika-extract-text-action
+  labels:
+    camel.apache.org/kamelet.type: "action"
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "4.22.1-SNAPSHOT"
+    camel.apache.org/kamelet.icon: 
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3QgeD0iOCIgeT0iNiIgd2lkdGg9IjI4IiBoZWlnaHQ9IjM2IiByeD0iMyIgZmlsbD0iIzhmYjRmZSIvPjxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjIwIiBoZWlnaHQ9IjMiIGZpbGw9IiNmZmYiLz48cmVjdCB4PSIxMiIgeT0iMTkiIHdpZHRoPSIyMCIgaGVpZ2h0PSIzIiBmaWxsPSIjZmZmIi8+PHJlY3QgeD0iMTIiIHk9IjI2IiB3aWR0aD0iMTQiIGhlaWdodD0iMyIgZmlsbD0iI2ZmZiIvPjxjaXJjbGUgY3g9IjQ2IiBjeT0iMjAiIHI9IjQiIGZpbG
 [...]
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.group: "Actions"
+    camel.apache.org/kamelet.namespace: "AI"
+spec:
+  definition:
+    title: "Tika Extract Text Action"
+    description: |-
+      Extract plain text from PDF, office and similar documents in-process 
with Apache Tika.
+
+      Before the parse, the value of the `documentIdHeader` header is captured 
into the
+      `CamelLangChain4jIngestDocumentId` exchange property - Tika copies 
document metadata
+      over the headers, so a crafted document could otherwise forge its own 
identity in steps
+      that read it later (the langchain4j-ingest sink does). The extracted 
text is decoded at
+      a pinned UTF-8 (a document-injected charset header is swept before the 
conversion), and
+      after the parse all non-Camel headers are removed: past this action, 
parsed document
+      metadata is indistinguishable from caller headers, so it must not travel 
further. Each
+      format needs its Tika parser module on the classpath; camel-tika ships 
the HTML and
+      text modules, PDF needs org.apache.tika:tika-parser-pdf-module.
+    type: object
+    properties:
+      documentIdHeader:
+        title: Document Id Header
+        description: Name of the header carrying the stable document id, 
captured into the
+          CamelLangChain4jIngestDocumentId exchange property before the parse.
+        type: string
+        default: CamelLangChain4jIngestDocumentId
+  dependencies:
+    - "camel:core"
+    - "camel:tika"
+    - "camel:kamelet"
+  template:
+    from:
+      uri: "kamelet:source"
+      steps:
+        # captured before the parse: Tika copies document metadata over the 
headers, so a
+        # crafted document could otherwise forge its own identity
+        - setProperty:
+            name: "CamelLangChain4jIngestDocumentId"
+            simple: "${header[{{documentIdHeader}}]}"
+        # tika answers plain text (body only, blocks newline-separated) at the 
pinned encoding
+        - to: 
"tika:parse?tikaParseOutputFormat=text&tikaParseOutputEncoding=UTF-8"
+        # a message- or document-supplied charset header outranks the charset 
the conversion
+        # below sets, so it is swept before the bytes are decoded at the 
pinned UTF-8.
+        # These three steps are the declarative twin of the 
camel-langchain4j-ingest
+        # component's TikaTextDecode processor, reimplemented here on purpose 
so a generic
+        # parse action does not depend on that component - keep the two in step
+        - removeHeaders:
+            pattern: "CamelCharsetName"
+        - convertBodyTo:
+            type: "byte[]"
+        - convertBodyTo:
+            type: "java.lang.String"
+            charset: "UTF-8"
+        # past the parse, non-Camel headers are parsed document metadata 
(Content-Type,
+        # dc:* and friends) - Camel-namespace names were already filtered by 
camel-tika,
+        # the rest must not leak into later steps as if the caller had set them
+        - removeHeaders:
+            pattern: "*"
+            excludePattern: "Camel*"
diff --git 
a/library/camel-kamelets/src/main/resources/kamelets/docling-convert-action.kamelet.yaml
 
b/library/camel-kamelets/src/main/resources/kamelets/docling-convert-action.kamelet.yaml
new file mode 100644
index 000000000..d1a00f80e
--- /dev/null
+++ 
b/library/camel-kamelets/src/main/resources/kamelets/docling-convert-action.kamelet.yaml
@@ -0,0 +1,86 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+  name: docling-convert-action
+  labels:
+    camel.apache.org/kamelet.type: "action"
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "4.22.1-SNAPSHOT"
+    camel.apache.org/kamelet.icon: 
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3QgeD0iOCIgeT0iNiIgd2lkdGg9IjI4IiBoZWlnaHQ9IjM2IiByeD0iMyIgZmlsbD0iIzhmYjRmZSIvPjxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjIwIiBoZWlnaHQ9IjMiIGZpbGw9IiNmZmYiLz48cmVjdCB4PSIxMiIgeT0iMTkiIHdpZHRoPSIyMCIgaGVpZ2h0PSIzIiBmaWxsPSIjZmZmIi8+PHJlY3QgeD0iMTIiIHk9IjI2IiB3aWR0aD0iMTQiIGhlaWdodD0iMyIgZmlsbD0iI2ZmZiIvPjxjaXJjbGUgY3g9IjQ2IiBjeT0iMjAiIHI9IjQiIGZpbG
 [...]
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.group: "Actions"
+    camel.apache.org/kamelet.namespace: "AI"
+spec:
+  definition:
+    title: "Docling Convert Action"
+    description: |-
+      Convert PDF, office and similar documents to structure-preserving 
markdown with
+      Docling, OCR included.
+
+      Before the conversion, the value of the `documentIdHeader` header is 
captured into the
+      `CamelLangChain4jIngestDocumentId` exchange property, so a converted 
document cannot
+      forge its own identity in steps that read it later (the 
langchain4j-ingest sink does);
+      the body is pinned to bytes and the `CamelDocling*` control headers are 
swept, because
+      none of them may be decided by a consumer-delivered payload. By default 
the `docling`
+      component executes a local docling CLI; set `doclingServeUrl` to use a 
Docling Serve
+      instance instead - the natural fit for self-contained Pipes.
+    type: object
+    properties:
+      documentIdHeader:
+        title: Document Id Header
+        description: Name of the header carrying the stable document id, 
captured into the
+          CamelLangChain4jIngestDocumentId exchange property before the 
conversion.
+        type: string
+        default: CamelLangChain4jIngestDocumentId
+      doclingServeUrl:
+        title: Docling Serve URL
+        description: Address of a Docling Serve instance to convert through, 
such as
+          http://docling-serve:5001; when unset, the docling component's own 
configuration
+          applies (a local docling CLI by default).
+        type: string
+      useDoclingServe:
+        title: Use Docling Serve
+        description: Whether to convert through a Docling Serve instance 
instead of a local
+          docling CLI; set together with doclingServeUrl.
+        type: boolean
+  dependencies:
+    - "camel:core"
+    - "camel:docling"
+    - "camel:kamelet"
+  template:
+    from:
+      uri: "kamelet:source"
+      steps:
+        # captured before the conversion, so a document cannot forge its own 
identity
+        - setProperty:
+            name: "CamelLangChain4jIngestDocumentId"
+            simple: "${header[{{documentIdHeader}}]}"
+        - convertBodyTo:
+            type: "byte[]"
+        - removeHeaders:
+            pattern: "CamelDocling*"
+        - to:
+            uri: "docling:convert"
+            parameters:
+              operation: "CONVERT_TO_MARKDOWN"
+              contentInBody: "true"
+              useDoclingServe: "{{?useDoclingServe}}"
+              doclingServeUrl: "{{?doclingServeUrl}}"
diff --git 
a/library/camel-kamelets/src/main/resources/kamelets/langchain4j-ingest-file-source.kamelet.yaml
 
b/library/camel-kamelets/src/main/resources/kamelets/langchain4j-ingest-file-source.kamelet.yaml
new file mode 100644
index 000000000..5114b2e42
--- /dev/null
+++ 
b/library/camel-kamelets/src/main/resources/kamelets/langchain4j-ingest-file-source.kamelet.yaml
@@ -0,0 +1,101 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+  name: langchain4j-ingest-file-source
+  labels:
+    camel.apache.org/kamelet.type: "source"
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "4.22.1-SNAPSHOT"
+    camel.apache.org/kamelet.icon: 
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3QgeD0iOCIgeT0iNiIgd2lkdGg9IjI4IiBoZWlnaHQ9IjM2IiByeD0iMyIgZmlsbD0iIzhmYjRmZSIvPjxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjIwIiBoZWlnaHQ9IjMiIGZpbGw9IiNmZmYiLz48cmVjdCB4PSIxMiIgeT0iMTkiIHdpZHRoPSIyMCIgaGVpZ2h0PSIzIiBmaWxsPSIjZmZmIi8+PHJlY3QgeD0iMTIiIHk9IjI2IiB3aWR0aD0iMTQiIGhlaWdodD0iMyIgZmlsbD0iI2ZmZiIvPjxjaXJjbGUgY3g9IjQ2IiBjeT0iMjAiIHI9IjQiIGZpbG
 [...]
+    camel.apache.org/kamelet.group: "LangChain4j Ingest"
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.namespace: "AI"
+spec:
+  definition:
+    title: "LangChain4j Ingest File Source"
+    description: |-
+      Watch a directory as the source of an ingestion pipeline, with the 
defaults a knowledge
+      base needs: documents are left in place (noop), unchanged files are 
remembered in a
+      duplicate register keyed on path, modification time and size, a file 
still being copied
+      in is waited for (readLock=changed), and the file name is exposed as the 
document id in
+      the CamelLangChain4jIngestDocumentId header.
+
+      Mind the two dedup layers when composing: this source re-delivers an 
edited file (its
+      register key changes with the modification time), but a 
langchain4j-ingest-sink with an
+      idempotentRepository is first-write-wins by document id - the 
re-delivered edit would
+      be skipped. Use a version-aware id, or no sink repository, when 
documents change.
+    required:
+      - directory
+    type: object
+    properties:
+      directory:
+        title: Directory
+        description: The directory to ingest documents from.
+        type: string
+        pattern: "[^?#]+"
+      recursive:
+        title: Recursive
+        description: Whether subdirectories are ingested too.
+        type: boolean
+        default: true
+      delay:
+        title: Delay
+        description: Milliseconds between directory polls; the file endpoint's 
default is
+          500ms, which is aggressive for a knowledge-base directory.
+        type: integer
+      charset:
+        title: Charset
+        description: Character set for reading text documents, for example 
UTF-8. Leave
+          unset when the source feeds a parser action 
(tika-extract-text-action or
+          docling-convert-action), which must receive the raw bytes - a charset
+          conversion would corrupt a binary document such as a PDF.
+        type: string
+      idempotentRepository:
+        title: Idempotent Repository
+        description: An IdempotentRepository bean remembering already consumed 
files, as a
+          `#bean:name` reference; the register is keyed on path, modification 
time and
+          size, so an edited file is consumed again. When unset, the file 
endpoint's
+          default in-memory register (1000 entries) applies - eviction on a 
large directory
+          re-ingests files, so supply a sized or persistent repository for a 
real knowledge
+          base.
+        type: string
+  dependencies:
+    - "camel:core"
+    - "camel:file"
+    - "camel:kamelet"
+  template:
+    from:
+      uri: "file:{{directory}}"
+      parameters:
+        noop: "true"
+        idempotent: "true"
+        # an edited file gets a new key and re-ingests
+        idempotentKey: "${file:absolute.path}:${file:modified}:${file:size}"
+        recursive: "{{recursive}}"
+        readLock: "changed"
+        delay: "{{?delay}}"
+        charset: "{{?charset}}"
+        idempotentRepository: "{{?idempotentRepository}}"
+      steps:
+        - setHeader:
+            name: "CamelLangChain4jIngestDocumentId"
+            simple: "${header.CamelFileName}"
+        - to: "kamelet:sink"
diff --git 
a/library/camel-kamelets/src/main/resources/kamelets/langchain4j-ingest-sink.kamelet.yaml
 
b/library/camel-kamelets/src/main/resources/kamelets/langchain4j-ingest-sink.kamelet.yaml
new file mode 100644
index 000000000..768c3e1c4
--- /dev/null
+++ 
b/library/camel-kamelets/src/main/resources/kamelets/langchain4j-ingest-sink.kamelet.yaml
@@ -0,0 +1,123 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+  name: langchain4j-ingest-sink
+  labels:
+    camel.apache.org/kamelet.type: "sink"
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "4.22.1-SNAPSHOT"
+    camel.apache.org/kamelet.icon: 
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3QgeD0iOCIgeT0iNiIgd2lkdGg9IjI4IiBoZWlnaHQ9IjM2IiByeD0iMyIgZmlsbD0iIzhmYjRmZSIvPjxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjIwIiBoZWlnaHQ9IjMiIGZpbGw9IiNmZmYiLz48cmVjdCB4PSIxMiIgeT0iMTkiIHdpZHRoPSIyMCIgaGVpZ2h0PSIzIiBmaWxsPSIjZmZmIi8+PHJlY3QgeD0iMTIiIHk9IjI2IiB3aWR0aD0iMTQiIGhlaWdodD0iMyIgZmlsbD0iI2ZmZiIvPjxjaXJjbGUgY3g9IjQ2IiBjeT0iMjAiIHI9IjQiIGZpbG
 [...]
+    camel.apache.org/kamelet.group: "LangChain4j Ingest"
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.namespace: "AI"
+spec:
+  definition:
+    title: "LangChain4j Ingest Sink"
+    description: |-
+      Ingest documents into a LangChain4j EmbeddingStore: the payload is split 
into overlapping
+      segments, embedded in batches and written to the store, each segment 
stamped with the
+      pipeline name and the document id.
+
+      The document id is read from the `CamelLangChain4jIngestDocumentId` 
exchange property
+      when a prior step captured it (the parser actions do, before the parse), 
else from the
+      header named by `documentIdHeader` - the property always wins, so a 
parsed document
+      cannot forge its own identity. Bean references are `#bean:name` values; 
when
+      `embeddingStore` or `embeddingModel` is not set, the single registry 
bean of that type is
+      used. With `idempotentRepository` set, the first write per document id 
wins - a
+      re-delivered edited document is skipped, so streams that carry updates 
need a
+      version-aware id.
+    type: object
+    properties:
+      pipelineName:
+        title: Pipeline Name
+        description: The pipeline name, stamped on every written segment.
+        type: string
+        pattern: "[A-Za-z0-9._-]+"
+        default: ingest
+      documentIdHeader:
+        title: Document Id Header
+        description: Name of the header carrying the stable document id, such 
as CamelAwsS3Key
+          for an S3 source or CamelKafkaKey for a Kafka one; the
+          CamelLangChain4jIngestDocumentId exchange property, when set, takes 
precedence.
+        type: string
+        default: CamelLangChain4jIngestDocumentId
+      maxSegmentSize:
+        title: Max Segment Size
+        description: Maximum size of one segment, in characters.
+        type: integer
+        default: 500
+      maxOverlapSize:
+        title: Max Overlap Size
+        description: How much of the previous segment each segment repeats, in 
characters.
+        type: integer
+        default: 50
+      embeddingBatchSize:
+        title: Embedding Batch Size
+        description: How many segments are embedded per request to the 
embedding model.
+        type: integer
+        default: 32
+      maxDocumentSize:
+        title: Max Document Size
+        description: Maximum size of one document in characters; unset means 
no limit. The
+          pipeline holds a document in memory whole, so set the cap when the 
source can
+          deliver oversized payloads. An oversized document fails the exchange 
cleanly.
+        type: integer
+      documentSplitter:
+        title: Document Splitter
+        description: A DocumentSplitter bean replacing the default recursive 
splitting, as a
+          `#bean:name` reference; maxSegmentSize and maxOverlapSize are then 
ignored.
+        type: string
+      embeddingStore:
+        title: Embedding Store
+        description: The EmbeddingStore bean to write to, as a `#bean:name` 
reference.
+        type: string
+      embeddingModel:
+        title: Embedding Model
+        description: The EmbeddingModel bean to embed with, as a `#bean:name` 
reference.
+        type: string
+      idempotentRepository:
+        title: Idempotent Repository
+        description: An IdempotentRepository bean remembering ingested 
document ids, as a
+          `#bean:name` reference. A duplicate delivery is answered skipped, 
first write wins.
+        type: string
+  dependencies:
+    - "camel:core"
+    - "camel:langchain4j-ingest"
+    - "camel:kamelet"
+  template:
+    from:
+      uri: "kamelet:source"
+      steps:
+        # the endpoint itself resolves the id - the exchange property (set 
pre-parse by the
+        # parser actions) wins over the header, so no re-capture happens here: 
reading the
+        # header after a parse could hand a crafted document its own identity
+        - to:
+            uri: "langchain4j-ingest:{{pipelineName}}"
+            parameters:
+              documentIdHeader: "{{documentIdHeader}}"
+              maxSegmentSize: "{{maxSegmentSize}}"
+              maxOverlapSize: "{{maxOverlapSize}}"
+              embeddingBatchSize: "{{embeddingBatchSize}}"
+              maxDocumentSize: "{{?maxDocumentSize}}"
+              documentSplitter: "{{?documentSplitter}}"
+              embeddingStore: "{{?embeddingStore}}"
+              embeddingModel: "{{?embeddingModel}}"
+              idempotentRepository: "{{?idempotentRepository}}"
diff --git 
a/library/camel-kamelets/src/main/resources/kamelets/tika-extract-text-action.kamelet.yaml
 
b/library/camel-kamelets/src/main/resources/kamelets/tika-extract-text-action.kamelet.yaml
new file mode 100644
index 000000000..b3465fbb8
--- /dev/null
+++ 
b/library/camel-kamelets/src/main/resources/kamelets/tika-extract-text-action.kamelet.yaml
@@ -0,0 +1,86 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+  name: tika-extract-text-action
+  labels:
+    camel.apache.org/kamelet.type: "action"
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "4.22.1-SNAPSHOT"
+    camel.apache.org/kamelet.icon: 
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3QgeD0iOCIgeT0iNiIgd2lkdGg9IjI4IiBoZWlnaHQ9IjM2IiByeD0iMyIgZmlsbD0iIzhmYjRmZSIvPjxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjIwIiBoZWlnaHQ9IjMiIGZpbGw9IiNmZmYiLz48cmVjdCB4PSIxMiIgeT0iMTkiIHdpZHRoPSIyMCIgaGVpZ2h0PSIzIiBmaWxsPSIjZmZmIi8+PHJlY3QgeD0iMTIiIHk9IjI2IiB3aWR0aD0iMTQiIGhlaWdodD0iMyIgZmlsbD0iI2ZmZiIvPjxjaXJjbGUgY3g9IjQ2IiBjeT0iMjAiIHI9IjQiIGZpbG
 [...]
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.group: "Actions"
+    camel.apache.org/kamelet.namespace: "AI"
+spec:
+  definition:
+    title: "Tika Extract Text Action"
+    description: |-
+      Extract plain text from PDF, office and similar documents in-process 
with Apache Tika.
+
+      Before the parse, the value of the `documentIdHeader` header is captured 
into the
+      `CamelLangChain4jIngestDocumentId` exchange property - Tika copies 
document metadata
+      over the headers, so a crafted document could otherwise forge its own 
identity in steps
+      that read it later (the langchain4j-ingest sink does). The extracted 
text is decoded at
+      a pinned UTF-8 (a document-injected charset header is swept before the 
conversion), and
+      after the parse all non-Camel headers are removed: past this action, 
parsed document
+      metadata is indistinguishable from caller headers, so it must not travel 
further. Each
+      format needs its Tika parser module on the classpath; camel-tika ships 
the HTML and
+      text modules, PDF needs org.apache.tika:tika-parser-pdf-module.
+    type: object
+    properties:
+      documentIdHeader:
+        title: Document Id Header
+        description: Name of the header carrying the stable document id, 
captured into the
+          CamelLangChain4jIngestDocumentId exchange property before the parse.
+        type: string
+        default: CamelLangChain4jIngestDocumentId
+  dependencies:
+    - "camel:core"
+    - "camel:tika"
+    - "camel:kamelet"
+  template:
+    from:
+      uri: "kamelet:source"
+      steps:
+        # captured before the parse: Tika copies document metadata over the 
headers, so a
+        # crafted document could otherwise forge its own identity
+        - setProperty:
+            name: "CamelLangChain4jIngestDocumentId"
+            simple: "${header[{{documentIdHeader}}]}"
+        # tika answers plain text (body only, blocks newline-separated) at the 
pinned encoding
+        - to: 
"tika:parse?tikaParseOutputFormat=text&tikaParseOutputEncoding=UTF-8"
+        # a message- or document-supplied charset header outranks the charset 
the conversion
+        # below sets, so it is swept before the bytes are decoded at the 
pinned UTF-8.
+        # These three steps are the declarative twin of the 
camel-langchain4j-ingest
+        # component's TikaTextDecode processor, reimplemented here on purpose 
so a generic
+        # parse action does not depend on that component - keep the two in step
+        - removeHeaders:
+            pattern: "CamelCharsetName"
+        - convertBodyTo:
+            type: "byte[]"
+        - convertBodyTo:
+            type: "java.lang.String"
+            charset: "UTF-8"
+        # past the parse, non-Camel headers are parsed document metadata 
(Content-Type,
+        # dc:* and friends) - Camel-namespace names were already filtered by 
camel-tika,
+        # the rest must not leak into later steps as if the caller had set them
+        - removeHeaders:
+            pattern: "*"
+            excludePattern: "Camel*"
diff --git a/tests/camel-kamelets-itest/src/test/java/CommonIT.java 
b/tests/camel-kamelets-itest/src/test/java/CommonIT.java
index 04124894b..2e14332e9 100644
--- a/tests/camel-kamelets-itest/src/test/java/CommonIT.java
+++ b/tests/camel-kamelets-itest/src/test/java/CommonIT.java
@@ -94,6 +94,14 @@ public class CommonIT {
         return 
CitrusTestFactorySupport.factory(TestLoader.YAML).packageScan("http");
     }
 
+    @CitrusTestFactory
+    public Stream<DynamicTest> langchain4jIngest() {
+        // these tests need the camel-langchain4j-ingest component (Camel 
4.23+) and fail on
+        // an older Camel BY DESIGN: the red build is the guard that keeps the 
Kamelets from
+        // being merged into a catalog line whose Camel cannot run them
+        return 
CitrusTestFactorySupport.factory(TestLoader.YAML).packageScan("langchain4j-ingest");
+    }
+
     @CitrusTestFactory
     public Stream<DynamicTest> log() {
         return 
CitrusTestFactorySupport.factory(TestLoader.YAML).packageScan("log");
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/docling-convert-route.citrus.it.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/docling-convert-route.citrus.it.yaml
new file mode 100644
index 000000000..e44dbfb91
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/docling-convert-route.citrus.it.yaml
@@ -0,0 +1,89 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+name: docling-convert-route-test
+actions:
+  # drain any request a previous test may have left on the shared server
+  - purge:
+      endpoints:
+        - name: "httpServer"
+      timeout: 100
+  - createVariables:
+      variables:
+        - name: "docling.serve.url"
+          value: "http://localhost:${http.server.port}";
+        - name: "report.url"
+          value: "http://localhost:${http.server.port}/result/docling";
+
+  # Run a route feeding one document through the docling-convert-action into 
the
+  # sink Kamelet, with the docling component pointed at the Citrus HTTP 
server; the
+  # route reports the IngestResult back to the same server afterwards
+  - camel:
+      cli:
+        run:
+          waitForRunningState: false
+          args:
+            - "--dep=camel:groovy,camel:langchain4j-ingest"
+            - "--max-seconds=60"
+          integration:
+            file: "langchain4j-ingest/docling-convert-route.yaml"
+            systemProperties:
+              properties:
+                - name: "docling.serve.url"
+                  value: "${docling.serve.url}"
+                - name: "report.url"
+                  value: "${report.url}"
+
+  # The conversion request reaches the stub
+  - http:
+      server: "httpServer"
+      receiveRequest:
+        POST:
+          path: "/v1/convert/source"
+
+  # Answer the shape of a real docling-serve convert response
+  - http:
+      server: "httpServer"
+      sendResponse:
+        response:
+          status: 200
+          reasonPhrase: "OK"
+          version: "HTTP/1.1"
+          headers:
+            - name: "Content-Type"
+              value: "application/json"
+          body:
+            data: |
+              {"document":{"filename":"scan.pdf","md_content":"The EPSILON-2 
valve seals at 80 
bar.","json_content":null,"html_content":null,"text_content":null,"doctags_content":null},"status":"success","errors":[],"processing_time":0.01,"timings":{}}
+
+  # The reported IngestResult proves the stubbed markdown was embedded and 
stored
+  - http:
+      server: "httpServer"
+      receiveRequest:
+        POST:
+          path: "/result/docling"
+          type: "plaintext"
+          body:
+            data: "@contains('documentId=scan.pdf, segmentsWritten=1, 
outcome=INGESTED')@"
+
+  - http:
+      server: "httpServer"
+      sendResponse:
+        response:
+          status: 200
+          reasonPhrase: "OK"
+          version: "HTTP/1.1"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/docling-convert-route.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/docling-convert-route.yaml
new file mode 100644
index 000000000..bd99c155b
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/docling-convert-route.yaml
@@ -0,0 +1,61 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+# The docling action feeding the sink, against the test's stubbed Docling 
Serve instance (the real
+# image is a multi-gigabyte download): the action's own serve parameters point 
the
+# Kamelet at the Citrus HTTP server, which answers markdown; an INGESTED
+# outcome proves the converted markdown was split, embedded and stored.
+- beans:
+    - name: store
+      type: dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore
+    # a deterministic in-process stub model - the test asserts the conversion
+    # and pipeline mechanics, not embedding quality
+    - name: model
+      type: dev.langchain4j.model.embedding.EmbeddingModel
+      scriptLanguage: groovy
+      script: |
+        def embed = { String text ->
+            def random = new java.util.Random(text.hashCode())
+            float[] vector = new float[8]
+            for (int i = 0; i < 8; i++) {
+                vector[i] = random.nextFloat()
+            }
+            return dev.langchain4j.data.embedding.Embedding.from(vector)
+        }
+        return [
+            embedAll: { segments ->
+                dev.langchain4j.model.output.Response.from(segments.collect { 
embed(it.text()) })
+            }
+        ] as dev.langchain4j.model.embedding.EmbeddingModel
+- route:
+    id: docling-convert-route
+    from:
+      uri: "timer:ingest"
+      parameters:
+        repeatCount: 1
+      steps:
+        - setBody:
+            constant: "opaque binary payload, the stub answers regardless"
+        - setHeader:
+            name: "CamelLangChain4jIngestDocumentId"
+            constant: "scan.pdf"
+        - to: 
"kamelet:docling-convert-action?useDoclingServe=true&doclingServeUrl={{docling.serve.url}}"
+        - to: 
"kamelet:langchain4j-ingest-sink?embeddingStore=#bean:store&embeddingModel=#bean:model"
+        - log: "INGEST ${body}"
+        - convertBodyTo:
+            type: "java.lang.String"
+        - to: "{{report.url}}"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-register-route.citrus.it.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-register-route.citrus.it.yaml
new file mode 100644
index 000000000..af50ed9ff
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-register-route.citrus.it.yaml
@@ -0,0 +1,84 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+name: langchain4j-ingest-file-register-route-test
+actions:
+  # drain any request a previous test may have left on the shared server
+  - purge:
+      endpoints:
+        - name: "httpServer"
+      timeout: 100
+  - createVariables:
+      variables:
+        - name: "report.url"
+          value: "http://localhost:${http.server.port}/result/file-register";
+        - name: "ingest.dir"
+          value: "ingest-reg-citrus:randomNumber(5)"
+
+  # Run the register-semantics scenario: v1 is seeded, then overwritten by v2
+  - camel:
+      cli:
+        run:
+          waitForRunningState: false
+          args:
+            - "--max-seconds=60"
+          integration:
+            file: 
"langchain4j-ingest/langchain4j-ingest-file-register-route.yaml"
+            systemProperties:
+              properties:
+                - name: "report.url"
+                  value: "${report.url}"
+                - name: "ingest.dir"
+                  value: "${ingest.dir}"
+
+  # First delivery: the original file, consumed exactly once
+  - http:
+      server: "httpServer"
+      receiveRequest:
+        POST:
+          path: "/result/file-register"
+          type: "plaintext"
+          body:
+            data: "@contains('version one of the document')@"
+
+  - http:
+      server: "httpServer"
+      sendResponse:
+        response:
+          status: 200
+          reasonPhrase: "OK"
+          version: "HTTP/1.1"
+
+  # Second delivery: the EDITED file is re-delivered (register key includes the
+  # modification time); an unchanged-file re-delivery in between would arrive
+  # here instead and fail the assertion
+  - http:
+      server: "httpServer"
+      receiveRequest:
+        POST:
+          path: "/result/file-register"
+          type: "plaintext"
+          body:
+            data: "@contains('version two, edited in place')@"
+
+  - http:
+      server: "httpServer"
+      sendResponse:
+        response:
+          status: 200
+          reasonPhrase: "OK"
+          version: "HTTP/1.1"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-register-route.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-register-route.yaml
new file mode 100644
index 000000000..b136d34da
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-register-route.yaml
@@ -0,0 +1,57 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+# The file source's register semantics: the duplicate register is keyed on 
path,
+# modification time and size, so the SAME file is consumed once per version and
+# an EDITED file is re-delivered. A first seed writes v1, a second seed
+# overwrites it with v2 three seconds later - the test receives exactly v1 then
+# v2 (an unchanged-file re-delivery in between would break the v2 assertion).
+# The register is a named bean and the poll delay is set, exercising the
+# idempotentRepository and delay parameters end to end.
+- beans:
+    - name: register
+      type: 
org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository
+- route:
+    id: seed-v1
+    from:
+      uri: "timer:seed-v1"
+      parameters:
+        repeatCount: 1
+      steps:
+        - setBody:
+            constant: "version one of the document"
+        - to: "file:{{ingest.dir}}?fileName=doc.txt"
+- route:
+    id: seed-v2
+    from:
+      uri: "timer:seed-v2"
+      parameters:
+        repeatCount: 1
+        delay: 3000
+      steps:
+        - setBody:
+            constant: "version two, edited in place"
+        - to: "file:{{ingest.dir}}?fileName=doc.txt"
+- route:
+    id: langchain4j-ingest-file-register-route
+    from:
+      uri: 
"kamelet:langchain4j-ingest-file-source?directory={{ingest.dir}}&charset=UTF-8&idempotentRepository=#bean:register&delay=200"
+      steps:
+        - convertBodyTo:
+            type: "java.lang.String"
+        - log: "FILE-REGISTER ${body}"
+        - to: "{{report.url}}"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-source-route.citrus.it.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-source-route.citrus.it.yaml
new file mode 100644
index 000000000..1eea405c2
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-source-route.citrus.it.yaml
@@ -0,0 +1,72 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+name: langchain4j-ingest-file-source-route-test
+actions:
+  # drain any request a previous test may have left on the shared server
+  - purge:
+      endpoints:
+        - name: "httpServer"
+      timeout: 100
+  - createVariables:
+      variables:
+        - name: "report.url"
+          value: "http://localhost:${http.server.port}/result/file-source";
+        # a fresh directory per test run: a still-terminating integration from 
a
+        # previous local run must not re-consume this run's seed file
+        - name: "ingest.dir"
+          value: "ingest-in-citrus:randomNumber(5)"
+
+  # Run a route seeding one file into the directory watched by the
+  # langchain4j-ingest-file-source Kamelet; the source's output is reported
+  # back to the test's HTTP server
+  - camel:
+      cli:
+        run:
+          waitForRunningState: false
+          args:
+            - "--max-seconds=60"
+          integration:
+            file: 
"langchain4j-ingest/langchain4j-ingest-file-source-route.yaml"
+            systemProperties:
+              properties:
+                - name: "report.url"
+                  value: "${report.url}"
+                - name: "ingest.dir"
+                  value: "${ingest.dir}"
+
+  # The file content flows out of the source together with the document id
+  # header the ingest sinks read
+  - http:
+      server: "httpServer"
+      receiveRequest:
+        POST:
+          path: "/result/file-source"
+          headers:
+            - name: "docId"
+              value: "doc.txt"
+          type: "plaintext"
+          body:
+            data: "@contains('Sphinx of black quartz, judge my vow')@"
+
+  - http:
+      server: "httpServer"
+      sendResponse:
+        response:
+          status: 200
+          reasonPhrase: "OK"
+          version: "HTTP/1.1"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-source-route.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-source-route.yaml
new file mode 100644
index 000000000..9c6544fed
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-source-route.yaml
@@ -0,0 +1,42 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+# The file source: a seed route drops one file into the watched directory, the
+# Kamelet picks it up, leaves it in place and stamps the document id into the
+# CamelLangChain4jIngestDocumentId header - the contract the ingest sinks read.
+- route:
+    id: seed-file
+    from:
+      uri: "timer:seed"
+      parameters:
+        repeatCount: 1
+      steps:
+        - setBody:
+            constant: "Sphinx of black quartz, judge my vow"
+        - to: "file:{{ingest.dir}}?fileName=doc.txt"
+- route:
+    id: langchain4j-ingest-file-source-route
+    from:
+      uri: 
"kamelet:langchain4j-ingest-file-source?directory={{ingest.dir}}&charset=UTF-8"
+      steps:
+        - convertBodyTo:
+            type: "java.lang.String"
+        - log: "FILE-SOURCE ${body} 
id=${header.CamelLangChain4jIngestDocumentId}"
+        - setHeader:
+            name: "docId"
+            simple: "${header.CamelLangChain4jIngestDocumentId}"
+        - to: "{{report.url}}"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-tika-route.citrus.it.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-tika-route.citrus.it.yaml
new file mode 100644
index 000000000..93734ccc9
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-tika-route.citrus.it.yaml
@@ -0,0 +1,65 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+name: langchain4j-ingest-file-tika-route-test
+actions:
+  # drain any request a previous test may have left on the shared server
+  - purge:
+      endpoints:
+        - name: "httpServer"
+      timeout: 100
+  - createVariables:
+      variables:
+        - name: "report.url"
+          value: "http://localhost:${http.server.port}/result/file-tika";
+
+  # Run the file-source -> tika-action -> sink composition over a binary PDF; 
the
+  # PDF parser module is not part of camel-tika, so it is added to the run - 
the
+  # per-format module story the component docs describe. Its version tracks the
+  # Tika of the Camel release (4.0.0 for Camel 4.23): bump the two together
+  - camel:
+      cli:
+        run:
+          waitForRunningState: false
+          args:
+            - 
"--dep=camel:groovy,camel:langchain4j-ingest,org.apache.tika:tika-parser-pdf-module:4.0.0"
+            - "--max-seconds=60"
+          integration:
+            file: "langchain4j-ingest/langchain4j-ingest-file-tika-route.yaml"
+            systemProperties:
+              properties:
+                - name: "report.url"
+                  value: "${report.url}"
+
+  # INGESTED (not EMPTY) proves the PDF bytes reached Tika uncorrupted - with a
+  # charset conversion in the source this parse would find no text
+  - http:
+      server: "httpServer"
+      receiveRequest:
+        POST:
+          path: "/result/file-tika"
+          type: "plaintext"
+          body:
+            data: "@contains('documentId=pump.pdf, segmentsWritten=1, 
outcome=INGESTED')@"
+
+  - http:
+      server: "httpServer"
+      sendResponse:
+        response:
+          status: 200
+          reasonPhrase: "OK"
+          version: "HTTP/1.1"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-tika-route.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-tika-route.yaml
new file mode 100644
index 000000000..cd357bc82
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-file-tika-route.yaml
@@ -0,0 +1,58 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+# The composition the Kamelets are designed for: the file source feeds the tika
+# sink with a BINARY document (a checked-in one-page PDF). The source leaves
+# charset unset on purpose, so the sink receives raw bytes - a charset
+# round-trip would corrupt the PDF before Tika ever parses it, and the INGESTED
+# outcome (not EMPTY) proves the bytes arrived intact.
+- beans:
+    - name: store
+      type: dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore
+    # a deterministic in-process stub model - the test asserts the composition
+    # and parse mechanics, not embedding quality
+    - name: model
+      type: dev.langchain4j.model.embedding.EmbeddingModel
+      scriptLanguage: groovy
+      script: |
+        def embed = { String text ->
+            def random = new java.util.Random(text.hashCode())
+            float[] vector = new float[8]
+            for (int i = 0; i < 8; i++) {
+                vector[i] = random.nextFloat()
+            }
+            return dev.langchain4j.data.embedding.Embedding.from(vector)
+        }
+        return [
+            embedAll: { segments ->
+                dev.langchain4j.model.output.Response.from(segments.collect { 
embed(it.text()) })
+            }
+        ] as dev.langchain4j.model.embedding.EmbeddingModel
+- route:
+    id: langchain4j-ingest-file-tika-route
+    from:
+      uri: "kamelet:langchain4j-ingest-file-source?directory=pdfs"
+      steps:
+        - to: "kamelet:tika-extract-text-action"
+        - to: 
"kamelet:langchain4j-ingest-sink?embeddingStore=#bean:store&embeddingModel=#bean:model"
+        - log: "INGEST ${body}"
+        - convertBodyTo:
+            type: "java.lang.String"
+        # no Content-Type cleanup needed here: the tika action itself sweeps 
the parsed
+        # document's non-Camel headers (this report would arrive as 
application/pdf otherwise,
+        # so this test doubles as the proof of that sweep)
+        - to: "{{report.url}}"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-sink-route.citrus.it.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-sink-route.citrus.it.yaml
new file mode 100644
index 000000000..962d80bb6
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-sink-route.citrus.it.yaml
@@ -0,0 +1,62 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+name: langchain4j-ingest-sink-route-test
+actions:
+  # drain any request a previous test may have left on the shared server
+  - purge:
+      endpoints:
+        - name: "httpServer"
+      timeout: 100
+  - createVariables:
+      variables:
+        - name: "report.url"
+          value: "http://localhost:${http.server.port}/result/sink";
+
+  # Run a route feeding one document into the langchain4j-ingest-sink Kamelet;
+  # the route reports the IngestResult back to the test's HTTP server
+  - camel:
+      cli:
+        run:
+          waitForRunningState: false
+          args:
+            - "--dep=camel:groovy,camel:langchain4j-ingest"
+            - "--max-seconds=60"
+          integration:
+            file: "langchain4j-ingest/langchain4j-ingest-sink-route.yaml"
+            systemProperties:
+              properties:
+                - name: "report.url"
+                  value: "${report.url}"
+
+  # The reported IngestResult proves the document was split, embedded and 
stored
+  - http:
+      server: "httpServer"
+      receiveRequest:
+        POST:
+          path: "/result/sink"
+          type: "plaintext"
+          body:
+            data: "@contains('documentId=camels.txt, segmentsWritten=1, 
outcome=INGESTED')@"
+
+  - http:
+      server: "httpServer"
+      sendResponse:
+        response:
+          status: 200
+          reasonPhrase: "OK"
+          version: "HTTP/1.1"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-sink-route.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-sink-route.yaml
new file mode 100644
index 000000000..c1f1f40a9
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/langchain4j-ingest-sink-route.yaml
@@ -0,0 +1,58 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+# The plain sink: the payload is split, embedded and written to an in-memory
+# store; the IngestResult reply is logged for the test to verify. The model is
+# a deterministic in-process stub - the test asserts the pipeline mechanics,
+# not embedding quality, and needs no ONNX model download.
+- beans:
+    - name: store
+      type: dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore
+    - name: model
+      type: dev.langchain4j.model.embedding.EmbeddingModel
+      scriptLanguage: groovy
+      script: |
+        def embed = { String text ->
+            def random = new java.util.Random(text.hashCode())
+            float[] vector = new float[8]
+            for (int i = 0; i < 8; i++) {
+                vector[i] = random.nextFloat()
+            }
+            return dev.langchain4j.data.embedding.Embedding.from(vector)
+        }
+        return [
+            embedAll: { segments ->
+                dev.langchain4j.model.output.Response.from(segments.collect { 
embed(it.text()) })
+            }
+        ] as dev.langchain4j.model.embedding.EmbeddingModel
+- route:
+    id: langchain4j-ingest-sink-route
+    from:
+      uri: "timer:ingest"
+      parameters:
+        repeatCount: 1
+      steps:
+        - setBody:
+            constant: "Camels are resilient desert animals. They store fat in 
their humps."
+        - setHeader:
+            name: "CamelLangChain4jIngestDocumentId"
+            constant: "camels.txt"
+        - to: 
"kamelet:langchain4j-ingest-sink?embeddingStore=#bean:store&embeddingModel=#bean:model"
+        - log: "INGEST ${body}"
+        - convertBodyTo:
+            type: "java.lang.String"
+        - to: "{{report.url}}"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/pdfs/pump.pdf
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/pdfs/pump.pdf
new file mode 100644
index 000000000..110d9e609
Binary files /dev/null and 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/pdfs/pump.pdf
 differ
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/tika-extract-text-route.citrus.it.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/tika-extract-text-route.citrus.it.yaml
new file mode 100644
index 000000000..a68d554d7
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/tika-extract-text-route.citrus.it.yaml
@@ -0,0 +1,62 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+name: tika-extract-text-route-test
+actions:
+  # drain any request a previous test may have left on the shared server
+  - purge:
+      endpoints:
+        - name: "httpServer"
+      timeout: 100
+  - createVariables:
+      variables:
+        - name: "report.url"
+          value: "http://localhost:${http.server.port}/result/tika";
+
+  # Run a route feeding one HTML document through the tika-extract-text-action
+  # into the sink Kamelet; the route reports the IngestResult back to the 
test's HTTP server
+  - camel:
+      cli:
+        run:
+          waitForRunningState: false
+          args:
+            - "--dep=camel:groovy,camel:langchain4j-ingest"
+            - "--max-seconds=60"
+          integration:
+            file: "langchain4j-ingest/tika-extract-text-route.yaml"
+            systemProperties:
+              properties:
+                - name: "report.url"
+                  value: "${report.url}"
+
+  # INGESTED (not EMPTY) proves Tika extracted text from the HTML before the 
split
+  - http:
+      server: "httpServer"
+      receiveRequest:
+        POST:
+          path: "/result/tika"
+          type: "plaintext"
+          body:
+            data: "@contains('documentId=notice.html, segmentsWritten=1, 
outcome=INGESTED')@"
+
+  - http:
+      server: "httpServer"
+      sendResponse:
+        response:
+          status: 200
+          reasonPhrase: "OK"
+          version: "HTTP/1.1"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/tika-extract-text-route.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/tika-extract-text-route.yaml
new file mode 100644
index 000000000..d75aa182a
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/langchain4j-ingest/tika-extract-text-route.yaml
@@ -0,0 +1,60 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+# The tika action feeding the sink: an HTML document is parsed to plain text by
+# Apache Tika in the action, then split and embedded by the sink. An INGESTED 
outcome (not EMPTY)
+# proves the parse produced text; a failed parse would answer outcome=EMPTY.
+- beans:
+    - name: store
+      type: dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore
+    # a deterministic in-process stub model - the test asserts the parse and
+    # pipeline mechanics, not embedding quality
+    - name: model
+      type: dev.langchain4j.model.embedding.EmbeddingModel
+      scriptLanguage: groovy
+      script: |
+        def embed = { String text ->
+            def random = new java.util.Random(text.hashCode())
+            float[] vector = new float[8]
+            for (int i = 0; i < 8; i++) {
+                vector[i] = random.nextFloat()
+            }
+            return dev.langchain4j.data.embedding.Embedding.from(vector)
+        }
+        return [
+            embedAll: { segments ->
+                dev.langchain4j.model.output.Response.from(segments.collect { 
embed(it.text()) })
+            }
+        ] as dev.langchain4j.model.embedding.EmbeddingModel
+- route:
+    id: tika-extract-text-route
+    from:
+      uri: "timer:ingest"
+      parameters:
+        repeatCount: 1
+      steps:
+        - setBody:
+            constant: "<html><head><title>Secret</title></head><body><p>First 
paragraph about camels.</p></body></html>"
+        - setHeader:
+            name: "CamelLangChain4jIngestDocumentId"
+            constant: "notice.html"
+        - to: "kamelet:tika-extract-text-action"
+        - to: 
"kamelet:langchain4j-ingest-sink?embeddingStore=#bean:store&embeddingModel=#bean:model"
+        - log: "INGEST ${body}"
+        - convertBodyTo:
+            type: "java.lang.String"
+        - to: "{{report.url}}"

Reply via email to