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

davsclaus pushed a commit to branch CAMEL-26205-langchain4j-embeddings-body
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 32b5c4b30581f2c7dea08f43670a15ec7a4afded
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 00:24:13 2026 +0100

    CAMEL-26205: camel-langchain4j-embeddings - embed bytes, stream and file 
bodies as text, and remove the headers of an earlier call
    
    The producer read the body as a TextSegment, and only a String converted to 
one, so a byte[],
    InputStream or file consumer body failed with no type conversion. A body, 
and each element of a
    List body, is now used as is when it is a TextSegment, converted by a 
TextSegment type converter
    when there is one, and otherwise read as text.
    
    The single path left an EMBEDDINGS header from an earlier batch call on the 
same exchange, so a
    later langchain4j-embeddingstore ADD took the batch path and stored the old 
batch. Each call now
    removes the result headers of an earlier call (the batch ones, the single 
ones, the token counts,
    the finish reason and the request model) before it sets its own.
    
    The TEXT_SEGMENT header javaType had a leading space, which went into the 
catalog.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../catalog/components/langchain4j-embeddings.json |   4 +-
 .../docs/langchain4j-embeddings-component.adoc     |  11 ++
 .../embeddings/langchain4j-embeddings.json         |   4 +-
 .../docs/langchain4j-embeddings-component.adoc     |  11 ++
 .../embeddings/LangChain4jEmbeddings.java          |   2 +-
 .../embeddings/LangChain4jEmbeddingsEndpoint.java  |  10 +-
 .../embeddings/LangChain4jEmbeddingsHeaders.java   |   2 +-
 .../embeddings/LangChain4jEmbeddingsProducer.java  |  44 ++++-
 .../embeddings/LangChain4jEmbeddingsBodyTest.java  | 189 +++++++++++++++++++++
 ...angChain4jEmbeddingsEndpointBuilderFactory.java |   2 +-
 10 files changed, 264 insertions(+), 15 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-embeddings.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-embeddings.json
index 892805beec83..663b96a6157d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-embeddings.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-embeddings.json
@@ -47,11 +47,11 @@
     "CamelLangChain4jEmbeddingsEmbedding": { "index": 6, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "dev.langchain4j.data.embedding.Embedding", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Embedding representation of a text", "constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#EMBEDDING"
 },
     "CamelLangChain4jEmbeddingsEmbeddings": { "index": 7, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "java.util.List<dev.langchain4j.data.embedding.Embedding>", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "List of embeddings from a batch embedAll operation", 
"constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#EMBEDDINGS"
 },
     "CamelLangChain4jEmbeddingsVector": { "index": 8, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "float[]", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "A dense vector embedding of a text", 
"constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#VECTOR"
 },
-    "CamelLangChain4jEmbeddingsTextSegment": { "index": 9, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": " dev.langchain4j.data.segment.TextSegment", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": "A 
TextSegment representation of the vector embedding input text", "constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#TEXT_SEGMENT"
 },
+    "CamelLangChain4jEmbeddingsTextSegment": { "index": 9, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "dev.langchain4j.data.segment.TextSegment", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": "A 
TextSegment representation of the vector embedding input text", "constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#TEXT_SEGMENT"
 },
     "CamelLangChain4jEmbeddingsTextSegments": { "index": 10, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "java.util.List<dev.langchain4j.data.segment.TextSegment>", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "List of text segments from a batch embedAll operation", 
"constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#TEXT_SEGMENTS"
 }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "text", "pojo", "list" ], "description": 
"The text to embed as a String or a TextSegment, or a non-empty List of them to 
embed in one batch. Bytes, a stream, a file, a Map and no body fail: convert 
the body to a String first" },
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo", 
"list" ], "description": "The text to embed as a String or a TextSegment, or 
bytes, a stream or a file read as text; or a non-empty List of them to embed in 
one batch. Another body is embedded as its toString(), and no body fails" },
     "producerOut": { "carriers": [ "unchanged" ], "description": "The body is 
kept. The vector goes in the CamelLangChain4jEmbeddingsVector header as a float 
array, with the Embedding and the TextSegment in the 
CamelLangChain4jEmbeddingsEmbedding and CamelLangChain4jEmbeddingsTextSegment 
headers; a List body gives Lists of them in the 
CamelLangChain4jEmbeddingsEmbeddings and CamelLangChain4jEmbeddingsTextSegments 
headers instead" }
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-embeddings-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-embeddings-component.adoc
index 4fd7aa0d8cf2..932839d7b77b 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-embeddings-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-embeddings-component.adoc
@@ -225,6 +225,17 @@ YAML::
 ----
 ====
 
+=== The text to embed
+
+The body is the text to embed: a `String` or a LangChain4j `TextSegment`. 
Bytes, a stream and a file (such as the
+body of a `file` consumer) are read as text, so a `from("file:...")` route 
needs no `convertBodyTo(String.class)`.
+A non-empty `List` of them is embedded in one batch.
+
+The body is kept. A single text sets the 
`CamelLangChain4jEmbeddingsEmbedding`, `CamelLangChain4jEmbeddingsVector`
+and `CamelLangChain4jEmbeddingsTextSegment` headers, and a `List` the 
`CamelLangChain4jEmbeddingsEmbeddings` and
+`CamelLangChain4jEmbeddingsTextSegments` headers. Each call removes the 
headers of an earlier call on the same
+exchange, so a single text after a batch is not stored as the old batch by a 
later embedding store `ADD`.
+
 === Integration with Vector Stores
 
 The LangChain4j Embeddings component works seamlessly with vector databases 
for RAG (Retrieval-Augmented Generation) workflows.
diff --git 
a/components/camel-ai/camel-langchain4j-embeddings/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/embeddings/langchain4j-embeddings.json
 
b/components/camel-ai/camel-langchain4j-embeddings/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/embeddings/langchain4j-embeddings.json
index 892805beec83..663b96a6157d 100644
--- 
a/components/camel-ai/camel-langchain4j-embeddings/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/embeddings/langchain4j-embeddings.json
+++ 
b/components/camel-ai/camel-langchain4j-embeddings/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/embeddings/langchain4j-embeddings.json
@@ -47,11 +47,11 @@
     "CamelLangChain4jEmbeddingsEmbedding": { "index": 6, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "dev.langchain4j.data.embedding.Embedding", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Embedding representation of a text", "constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#EMBEDDING"
 },
     "CamelLangChain4jEmbeddingsEmbeddings": { "index": 7, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "java.util.List<dev.langchain4j.data.embedding.Embedding>", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "List of embeddings from a batch embedAll operation", 
"constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#EMBEDDINGS"
 },
     "CamelLangChain4jEmbeddingsVector": { "index": 8, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "float[]", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "A dense vector embedding of a text", 
"constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#VECTOR"
 },
-    "CamelLangChain4jEmbeddingsTextSegment": { "index": 9, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": " dev.langchain4j.data.segment.TextSegment", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": "A 
TextSegment representation of the vector embedding input text", "constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#TEXT_SEGMENT"
 },
+    "CamelLangChain4jEmbeddingsTextSegment": { "index": 9, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "dev.langchain4j.data.segment.TextSegment", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": "A 
TextSegment representation of the vector embedding input text", "constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#TEXT_SEGMENT"
 },
     "CamelLangChain4jEmbeddingsTextSegments": { "index": 10, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "java.util.List<dev.langchain4j.data.segment.TextSegment>", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "List of text segments from a batch embedAll operation", 
"constantName": 
"org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders#TEXT_SEGMENTS"
 }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "text", "pojo", "list" ], "description": 
"The text to embed as a String or a TextSegment, or a non-empty List of them to 
embed in one batch. Bytes, a stream, a file, a Map and no body fail: convert 
the body to a String first" },
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo", 
"list" ], "description": "The text to embed as a String or a TextSegment, or 
bytes, a stream or a file read as text; or a non-empty List of them to embed in 
one batch. Another body is embedded as its toString(), and no body fails" },
     "producerOut": { "carriers": [ "unchanged" ], "description": "The body is 
kept. The vector goes in the CamelLangChain4jEmbeddingsVector header as a float 
array, with the Embedding and the TextSegment in the 
CamelLangChain4jEmbeddingsEmbedding and CamelLangChain4jEmbeddingsTextSegment 
headers; a List body gives Lists of them in the 
CamelLangChain4jEmbeddingsEmbeddings and CamelLangChain4jEmbeddingsTextSegments 
headers instead" }
   },
   "properties": {
diff --git 
a/components/camel-ai/camel-langchain4j-embeddings/src/main/docs/langchain4j-embeddings-component.adoc
 
b/components/camel-ai/camel-langchain4j-embeddings/src/main/docs/langchain4j-embeddings-component.adoc
index 4fd7aa0d8cf2..932839d7b77b 100644
--- 
a/components/camel-ai/camel-langchain4j-embeddings/src/main/docs/langchain4j-embeddings-component.adoc
+++ 
b/components/camel-ai/camel-langchain4j-embeddings/src/main/docs/langchain4j-embeddings-component.adoc
@@ -225,6 +225,17 @@ YAML::
 ----
 ====
 
+=== The text to embed
+
+The body is the text to embed: a `String` or a LangChain4j `TextSegment`. 
Bytes, a stream and a file (such as the
+body of a `file` consumer) are read as text, so a `from("file:...")` route 
needs no `convertBodyTo(String.class)`.
+A non-empty `List` of them is embedded in one batch.
+
+The body is kept. A single text sets the 
`CamelLangChain4jEmbeddingsEmbedding`, `CamelLangChain4jEmbeddingsVector`
+and `CamelLangChain4jEmbeddingsTextSegment` headers, and a `List` the 
`CamelLangChain4jEmbeddingsEmbeddings` and
+`CamelLangChain4jEmbeddingsTextSegments` headers. Each call removes the 
headers of an earlier call on the same
+exchange, so a single text after a batch is not stored as the old batch by a 
later embedding store `ADD`.
+
 === Integration with Vector Stores
 
 The LangChain4j Embeddings component works seamlessly with vector databases 
for RAG (Retrieval-Augmented Generation) workflows.
diff --git 
a/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddings.java
 
b/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddings.java
index a4efbb28fcc3..903cb2453803 100644
--- 
a/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddings.java
+++ 
b/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddings.java
@@ -50,7 +50,7 @@ public final class LangChain4jEmbeddings {
         public static final String VECTOR = 
CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR;
 
         @Metadata(description = "A TextSegment representation of the vector 
embedding input text",
-                  javaType = " dev.langchain4j.data.segment.TextSegment")
+                  javaType = "dev.langchain4j.data.segment.TextSegment")
         public static final String TEXT_SEGMENT = 
CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_TEXT_SEGMENT;
     }
 }
diff --git 
a/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsEndpoint.java
 
b/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsEndpoint.java
index 3671438bbd53..c4db2c4997c8 100644
--- 
a/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsEndpoint.java
+++ 
b/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsEndpoint.java
@@ -37,10 +37,12 @@ import org.apache.camel.support.DefaultEndpoint;
              syntax = "langchain4j-embeddings:embeddingId", producerOnly = 
true, category = {
                      Category.AI
              }, headersClass = LangChain4jEmbeddingsHeaders.class)
-@BodyTypes(producerIn = @BodyType(value = { Carrier.TEXT, Carrier.POJO, 
Carrier.LIST },
-                                  description = "The text to embed as a String 
or a TextSegment, or a non-empty List of"
-                                                + " them to embed in one 
batch. Bytes, a stream, a file, a Map and no body"
-                                                + " fail: convert the body to 
a String first"),
+@BodyTypes(producerIn = @BodyType(value = {
+        Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE, 
Carrier.POJO,
+        Carrier.LIST },
+                                  description = "The text to embed as a String 
or a TextSegment, or bytes, a stream or a"
+                                                + " file read as text; or a 
non-empty List of them to embed in one batch."
+                                                + " Another body is embedded 
as its toString(), and no body fails"),
            producerOut = @BodyType(value = Carrier.UNCHANGED,
                                    description = "The body is kept. The vector 
goes in the CamelLangChain4jEmbeddingsVector"
                                                  + " header as a float array, 
with the Embedding and the TextSegment in the"
diff --git 
a/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsHeaders.java
 
b/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsHeaders.java
index c18162c78aa5..a7de5a1907ac 100644
--- 
a/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsHeaders.java
+++ 
b/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsHeaders.java
@@ -49,7 +49,7 @@ public class LangChain4jEmbeddingsHeaders {
     public static final String VECTOR = 
CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR;
 
     @Metadata(description = "A TextSegment representation of the vector 
embedding input text",
-              javaType = " dev.langchain4j.data.segment.TextSegment")
+              javaType = "dev.langchain4j.data.segment.TextSegment")
     public static final String TEXT_SEGMENT = 
CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_TEXT_SEGMENT;
 
     @Metadata(description = "List of text segments from a batch embedAll 
operation",
diff --git 
a/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsProducer.java
 
b/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsProducer.java
index 308ad54740ed..1f76d6705915 100644
--- 
a/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsProducer.java
+++ 
b/components/camel-ai/camel-langchain4j-embeddings/src/main/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsProducer.java
@@ -25,6 +25,8 @@ import dev.langchain4j.model.embedding.EmbeddingModel;
 import dev.langchain4j.model.output.Response;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
+import org.apache.camel.NoTypeConversionAvailableException;
+import org.apache.camel.TypeConverter;
 import org.apache.camel.component.ai.observability.GenAiErrorSupport;
 import org.apache.camel.component.ai.observability.GenAiModelResolver;
 import org.apache.camel.component.ai.observability.GenAiObservability;
@@ -64,7 +66,7 @@ public class LangChain4jEmbeddingsProducer extends 
DefaultProducer {
     }
 
     private void processSingle(Exchange exchange, EmbeddingModel model, 
Message message) throws Exception {
-        final TextSegment in = 
exchange.getMessage().getMandatoryBody(TextSegment.class);
+        final TextSegment in = toTextSegment(exchange, 
message.getMandatoryBody());
         GenAiObservationContext observationContext = 
GenAiObservationContext.builder()
                 .operationName(GenAiOperationName.EMBEDDINGS)
                 
.system(GenAiModelResolver.resolveSystem(exchange.getContext().getClassResolver(),
 model))
@@ -91,11 +93,9 @@ public class LangChain4jEmbeddingsProducer extends 
DefaultProducer {
 
     private void processBatch(Exchange exchange, EmbeddingModel model, Message 
message, List<Object> bodyList)
             throws Exception {
-        // Convert each element to TextSegment using the type converter
         List<TextSegment> segments = new ArrayList<>(bodyList.size());
         for (Object item : bodyList) {
-            TextSegment segment = 
exchange.getContext().getTypeConverter().mandatoryConvertTo(TextSegment.class, 
item);
-            segments.add(segment);
+            segments.add(toTextSegment(exchange, item));
         }
 
         GenAiObservationContext observationContext = 
GenAiObservationContext.builder()
@@ -123,8 +123,43 @@ public class LangChain4jEmbeddingsProducer extends 
DefaultProducer {
         }
     }
 
+    /**
+     * Converts a body, or an element of a List body, to the TextSegment to 
embed. A TextSegment is used as is, a type
+     * converter to TextSegment is used when there is one, and other bodies, 
such as bytes, a stream or a file, are read
+     * as text.
+     */
+    private static TextSegment toTextSegment(Exchange exchange, Object value) 
throws NoTypeConversionAvailableException {
+        if (value instanceof TextSegment segment) {
+            return segment;
+        }
+        TypeConverter tc = exchange.getContext().getTypeConverter();
+        TextSegment segment = tc.tryConvertTo(TextSegment.class, exchange, 
value);
+        if (segment != null) {
+            return segment;
+        }
+        return TextSegment.from(tc.mandatoryConvertTo(String.class, exchange, 
value));
+    }
+
+    /**
+     * Removes the headers of an earlier call, so a header that this call does 
not set does not keep a stale value: an
+     * EMBEDDINGS header from an earlier batch would make a later embedding 
store ADD store the old batch.
+     */
+    private static void removeResultHeaders(Message message) {
+        message.removeHeader(LangChain4jEmbeddingsHeaders.REQUEST_MODEL);
+        message.removeHeader(LangChain4jEmbeddingsHeaders.FINISH_REASON);
+        message.removeHeader(LangChain4jEmbeddingsHeaders.INPUT_TOKEN_COUNT);
+        message.removeHeader(LangChain4jEmbeddingsHeaders.OUTPUT_TOKEN_COUNT);
+        message.removeHeader(LangChain4jEmbeddingsHeaders.TOTAL_TOKEN_COUNT);
+        message.removeHeader(LangChain4jEmbeddingsHeaders.VECTOR);
+        message.removeHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT);
+        message.removeHeader(LangChain4jEmbeddingsHeaders.EMBEDDING);
+        message.removeHeader(LangChain4jEmbeddingsHeaders.EMBEDDINGS);
+        message.removeHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENTS);
+    }
+
     private static void populateHeaders(
             Message message, Response<Embedding> result, TextSegment 
textSegment, String requestModel) {
+        removeResultHeaders(message);
         if (requestModel != null) {
             message.setHeader(LangChain4jEmbeddingsHeaders.REQUEST_MODEL, 
requestModel);
         }
@@ -146,6 +181,7 @@ public class LangChain4jEmbeddingsProducer extends 
DefaultProducer {
 
     private static void populateBatchHeaders(
             Message message, Response<List<Embedding>> result, 
List<TextSegment> segments, String requestModel) {
+        removeResultHeaders(message);
         if (requestModel != null) {
             message.setHeader(LangChain4jEmbeddingsHeaders.REQUEST_MODEL, 
requestModel);
         }
diff --git 
a/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsBodyTest.java
 
b/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsBodyTest.java
new file mode 100644
index 000000000000..fb02722232b0
--- /dev/null
+++ 
b/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsBodyTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ */
+package org.apache.camel.component.langchain4j.embeddings;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import dev.langchain4j.data.embedding.Embedding;
+import dev.langchain4j.data.segment.TextSegment;
+import 
dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.Message;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.apache.camel.util.IOHelper;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.camel.test.junit6.TestSupport.deleteDirectory;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class LangChain4jEmbeddingsBodyTest extends CamelTestSupport {
+
+    private static final String FILE_DIR = 
"target/data/langchain4j-embeddings-body";
+
+    @Override
+    protected void doPreSetup() throws Exception {
+        deleteDirectory(FILE_DIR);
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+
+        LangChain4jEmbeddingsComponent component
+                = context.getComponent(LangChain4jEmbeddings.SCHEME, 
LangChain4jEmbeddingsComponent.class);
+        component.getConfiguration().setEmbeddingModel(new 
AllMiniLmL6V2EmbeddingModel());
+
+        return context;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("file:" + FILE_DIR + "?initialDelay=0&delete=true")
+                        .to("langchain4j-embeddings:file")
+                        .to("mock:file");
+
+                from("direct:batchThenSingle")
+                        .to("langchain4j-embeddings:batch")
+                        .setBody(constant("hello"))
+                        .to("langchain4j-embeddings:single");
+
+                from("direct:singleThenBatch")
+                        .to("langchain4j-embeddings:single")
+                        .setBody(constant(List.of("hello", "world")))
+                        .to("langchain4j-embeddings:batch");
+            }
+        };
+    }
+
+    @Test
+    void bytesBodyIsEmbeddedAsText() {
+        Message result = embed("hello world".getBytes(StandardCharsets.UTF_8));
+
+        assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT, 
TextSegment.class).text())
+                .isEqualTo("hello world");
+        assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.VECTOR, 
float[].class))
+                .isEqualTo(embed("hello 
world").getHeader(LangChain4jEmbeddingsHeaders.VECTOR, float[].class));
+    }
+
+    @Test
+    void streamBodyIsEmbeddedAsText() {
+        Message result = embed(new ByteArrayInputStream("hello 
stream".getBytes(StandardCharsets.UTF_8)));
+
+        assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT, 
TextSegment.class).text())
+                .isEqualTo("hello stream");
+        assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.EMBEDDING, 
Embedding.class)).isNotNull();
+    }
+
+    @Test
+    void textSegmentBodyIsKept() {
+        TextSegment segment = TextSegment.from("hello segment");
+
+        Message result = embed(segment);
+
+        
assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT)).isSameAs(segment);
+    }
+
+    @Test
+    void fileConsumerBodyIsEmbeddedAsText() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:file");
+        mock.expectedMessageCount(1);
+
+        template.sendBodyAndHeader("file:" + FILE_DIR, "hello file", 
Exchange.FILE_NAME, "hello.txt");
+
+        mock.assertIsSatisfied();
+        Message result = mock.getReceivedExchanges().get(0).getMessage();
+        assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT, 
TextSegment.class).text())
+                .isEqualTo("hello file");
+        assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.EMBEDDING, 
Embedding.class)).isNotNull();
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    void batchOfBytesAndStreamsIsEmbeddedAsText() {
+        InputStream stream = new ByteArrayInputStream("from a 
stream".getBytes(StandardCharsets.UTF_8));
+
+        Message result = embed(List.of("from a string", "from 
bytes".getBytes(StandardCharsets.UTF_8), stream));
+
+        List<TextSegment> segments = 
result.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENTS, List.class);
+        assertThat(segments).extracting(TextSegment::text)
+                .containsExactly("from a string", "from bytes", "from a 
stream");
+        assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.EMBEDDINGS, 
List.class)).hasSize(3);
+    }
+
+    @Test
+    void noBodyFails() {
+        assertThatThrownBy(() -> 
template.requestBody("langchain4j-embeddings:test", (Object) null))
+                .isInstanceOf(CamelExecutionException.class)
+                .hasCauseInstanceOf(InvalidPayloadException.class);
+    }
+
+    @Test
+    void singleAfterBatchRemovesTheBatchHeaders() {
+        Message result = fluentTemplate.to("direct:batchThenSingle")
+                .withBody(List.of("one", "two"))
+                .request(Message.class);
+
+        
assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.EMBEDDINGS)).isNull();
+        
assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENTS)).isNull();
+        assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.EMBEDDING, 
Embedding.class)).isNotNull();
+        assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT, 
TextSegment.class).text())
+                .isEqualTo("hello");
+    }
+
+    @Test
+    void batchAfterSingleRemovesTheSingleHeaders() {
+        Message result = fluentTemplate.to("direct:singleThenBatch")
+                .withBody("one")
+                .request(Message.class);
+
+        
assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.EMBEDDING)).isNull();
+        
assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.VECTOR)).isNull();
+        
assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT)).isNull();
+        assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.EMBEDDINGS, 
List.class)).hasSize(2);
+        
assertThat(result.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENTS, 
List.class)).hasSize(2);
+    }
+
+    @Test
+    void headerJavaTypesHaveNoLeadingSpace() throws Exception {
+        String json;
+        try (InputStream is = getClass().getResourceAsStream(
+                
"/META-INF/org/apache/camel/component/langchain4j/embeddings/langchain4j-embeddings.json"))
 {
+            json = IOHelper.loadText(is);
+        }
+
+        assertThat(json).contains("\"javaType\": 
\"dev.langchain4j.data.segment.TextSegment\"")
+                .doesNotContain("\"javaType\": \" ");
+    }
+
+    private Message embed(Object body) {
+        return fluentTemplate.to("langchain4j-embeddings:test")
+                .withBody(body)
+                .request(Message.class);
+    }
+}
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jEmbeddingsEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jEmbeddingsEndpointBuilderFactory.java
index 9553d5c0f2f4..dcd39b288c6d 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jEmbeddingsEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jEmbeddingsEndpointBuilderFactory.java
@@ -512,7 +512,7 @@ public interface 
LangChain4jEmbeddingsEndpointBuilderFactory {
         /**
          * A TextSegment representation of the vector embedding input text.
          * 
-         * The option is a: {@code  dev.langchain4j.data.segment.TextSegment}
+         * The option is a: {@code dev.langchain4j.data.segment.TextSegment}
          * type.
          * 
          * Group: producer

Reply via email to