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

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


The following commit(s) were added to refs/heads/main by this push:
     new 787da26fa220 CAMEL-26205: camel-langchain4j-embeddings - embed bytes, 
stream and file bodies as text, and remove the headers of an earlier call 
(#29032)
787da26fa220 is described below

commit 787da26fa22055dd10d25ee36c7ed17297f54822
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 11:46:12 2026 +0100

    CAMEL-26205: camel-langchain4j-embeddings - embed bytes, stream and file 
bodies as text, and remove the headers of an earlier call (#29032)
    
    * CAMEL-26205: camel-langchain4j-embeddings - embed bytes, stream and f…
    
    …ile 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.
    
    * CAMEL-26205: camel-langchain4j-embeddings - fail a Map or POJO body, …
    
    …or List element, instead of embedding its toString()
    
    A body that is not text, bytes, a stream, a Reader, a file or a 
TextSegment, such as a Map or a POJO,
    was read as text through the ToString fallback converter and embedded as 
its toString(). It now fails
    with an InvalidPayloadException that says to convert the body to text 
first, and so does such an
    element of a List body (the message names its index and type). A type 
converter to TextSegment is
    still used when there is one. The body types no longer list a POJO body.
    
    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     |  15 ++
 .../embeddings/langchain4j-embeddings.json         |   4 +-
 .../docs/langchain4j-embeddings-component.adoc     |  15 ++
 .../embeddings/LangChain4jEmbeddings.java          |   2 +-
 .../embeddings/LangChain4jEmbeddingsEndpoint.java  |   9 +-
 .../embeddings/LangChain4jEmbeddingsHeaders.java   |   2 +-
 .../embeddings/LangChain4jEmbeddingsProducer.java  |  80 +++++++-
 .../embeddings/LangChain4jEmbeddingsBodyTest.java  | 228 +++++++++++++++++++++
 ...angChain4jEmbeddingsEndpointBuilderFactory.java |   2 +-
 10 files changed, 346 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..17e959c18b17 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", "list" ], 
"description": "The text to embed: a String, bytes, a stream or a file read as 
text, or a LangChain4j TextSegment; or a non-empty List of them to embed in one 
batch. Another body or List element, such as a Map or a POJO, fails, as does no 
body: convert it to text first" },
     "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..e77f65e70de6 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,21 @@ 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.
+
+Any other body, or element of a `List` body, such as a `Map` or a POJO, fails 
the exchange with an
+`InvalidPayloadException` instead of being embedded as its `toString()`: turn 
it into text first, with
+`convertBodyTo(String.class)` or a marshal to JSON, say. No body fails too.
+
+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..17e959c18b17 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", "list" ], 
"description": "The text to embed: a String, bytes, a stream or a file read as 
text, or a LangChain4j TextSegment; or a non-empty List of them to embed in one 
batch. Another body or List element, such as a Map or a POJO, fails, as does no 
body: convert it to text first" },
     "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..e77f65e70de6 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,21 @@ 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.
+
+Any other body, or element of a `List` body, such as a `Map` or a POJO, fails 
the exchange with an
+`InvalidPayloadException` instead of being embedded as its `toString()`: turn 
it into text first, with
+`convertBodyTo(String.class)` or a marshal to JSON, say. No body fails too.
+
+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..d6ed9610c61e 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,11 @@ 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.LIST },
+                                  description = "The text to embed: a String, 
bytes, a stream or a file read as text, or a"
+                                                + " LangChain4j TextSegment; 
or a non-empty List of them to embed in one batch."
+                                                + " Another body or List 
element, such as a Map or a POJO, fails, as does no"
+                                                + " body: convert it to text 
first"),
            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..41e669110533 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
@@ -16,6 +16,12 @@
  */
 package org.apache.camel.component.langchain4j.embeddings;
 
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -24,7 +30,12 @@ import dev.langchain4j.data.segment.TextSegment;
 import dev.langchain4j.model.embedding.EmbeddingModel;
 import dev.langchain4j.model.output.Response;
 import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.Message;
+import org.apache.camel.NoTypeConversionAvailableException;
+import org.apache.camel.StreamCache;
+import org.apache.camel.TypeConverter;
+import org.apache.camel.WrappedFile;
 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 +75,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(), -1);
         GenAiObservationContext observationContext = 
GenAiObservationContext.builder()
                 .operationName(GenAiOperationName.EMBEDDINGS)
                 
.system(GenAiModelResolver.resolveSystem(exchange.getContext().getClassResolver(),
 model))
@@ -91,11 +102,10 @@ 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());
+        int index = 0;
         for (Object item : bodyList) {
-            TextSegment segment = 
exchange.getContext().getTypeConverter().mandatoryConvertTo(TextSegment.class, 
item);
-            segments.add(segment);
+            segments.add(toTextSegment(exchange, item, index++));
         }
 
         GenAiObservationContext observationContext = 
GenAiObservationContext.builder()
@@ -123,8 +133,69 @@ 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 text, bytes, a 
stream, a Reader or a file is read as
+     * text. Any other value, such as a Map or a POJO, fails as an 
InvalidPayloadException instead of being embedded as
+     * its toString().
+     *
+     * @param index the index of the element in a List body, or -1 for the 
body itself
+     */
+    private static TextSegment toTextSegment(Exchange exchange, Object value, 
int index)
+            throws InvalidPayloadException, NoTypeConversionAvailableException 
{
+        if (value instanceof TextSegment segment) {
+            return segment;
+        }
+        TypeConverter tc = exchange.getContext().getTypeConverter();
+        TextSegment segment = value != null ? 
tc.tryConvertTo(TextSegment.class, exchange, value) : null;
+        if (segment != null) {
+            return segment;
+        }
+        if (!isReadableAsText(value)) {
+            String what = index < 0
+                    ? "" : "the element at index " + index + " of the List 
body is " + typeOf(value) + "; ";
+            throw new InvalidPayloadException(
+                    exchange, TextSegment.class, exchange.getMessage(),
+                    what + "langchain4j-embeddings embeds text, bytes, a 
stream, a Reader, a file or a TextSegment;"
+                                                                        + " 
convert the body to text before the endpoint,"
+                                                                        + " 
such as with convertBodyTo(String) or a marshal"
+                                                                        + " to 
JSON");
+        }
+        return TextSegment.from(tc.mandatoryConvertTo(String.class, exchange, 
value));
+    }
+
+    /** A value Camel converts to its text, rather than to the toString() of 
an object. */
+    private static boolean isReadableAsText(Object value) {
+        return value instanceof CharSequence || value instanceof byte[] || 
value instanceof ByteBuffer
+                || value instanceof InputStream || value instanceof Reader || 
value instanceof StreamCache
+                || value instanceof ByteArrayOutputStream
+                || value instanceof File || value instanceof Path || value 
instanceof WrappedFile;
+    }
+
+    private static String typeOf(Object value) {
+        return value == null ? "null" : value.getClass().getName();
+    }
+
+    /**
+     * 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 +217,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..0dc0f48d281d
--- /dev/null
+++ 
b/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsBodyTest.java
@@ -0,0 +1,228 @@
+/*
+ * 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 java.util.Map;
+
+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 mapBodyFails() {
+        Exchange exchange = send(Map.of("text", "hello map"));
+
+        
assertThat(exchange.getException()).isInstanceOf(InvalidPayloadException.class)
+                .hasMessageContaining("convert the body to text");
+        
assertThat(exchange.getMessage().getHeader(LangChain4jEmbeddingsHeaders.EMBEDDING)).isNull();
+        
assertThat(exchange.getMessage().getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT)).isNull();
+    }
+
+    @Test
+    void pojoBodyFails() {
+        Exchange exchange = send(new Note("hello pojo"));
+
+        
assertThat(exchange.getException()).isInstanceOf(InvalidPayloadException.class)
+                .hasMessageContaining("convert the body to text");
+        
assertThat(exchange.getMessage().getHeader(LangChain4jEmbeddingsHeaders.EMBEDDING)).isNull();
+        
assertThat(exchange.getMessage().getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT)).isNull();
+    }
+
+    @Test
+    void batchWithAMapElementFails() {
+        Exchange exchange = send(List.of("hello", Map.of("text", "hello 
map")));
+
+        
assertThat(exchange.getException()).isInstanceOf(InvalidPayloadException.class)
+                .hasMessageContaining("the element at index 1 of the List body 
is java.util")
+                .hasMessageContaining("convert the body to text");
+        
assertThat(exchange.getMessage().getHeader(LangChain4jEmbeddingsHeaders.EMBEDDINGS)).isNull();
+        
assertThat(exchange.getMessage().getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENTS)).isNull();
+    }
+
+    @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 Exchange send(Object body) {
+        return template.send("langchain4j-embeddings:test", e -> 
e.getMessage().setBody(body));
+    }
+
+    private record Note(String text) {
+    }
+
+    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