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 c764c51d10bc CAMEL-26514: camel-langchain4j-embeddings - set the 
CamelLangChain4jEmbeddingsResponseModel header (#29654)
c764c51d10bc is described below

commit c764c51d10bc059ea38f7ddd3b11382e6b095907
Author: Urmila Unni <[email protected]>
AuthorDate: Thu Nov 12 14:31:04 2026 +0530

    CAMEL-26514: camel-langchain4j-embeddings - set the 
CamelLangChain4jEmbeddingsResponseModel header (#29654)
    
    * CAMEL-26514: camel-langchain4j-embeddings - set the 
CamelLangChain4jEmbeddingsResponseModel header
    
    The producer now calls embed(EmbeddingRequest), whose EmbeddingResponse 
gives the model name. The test models implement embedAll, as the default 
methods of LangChain4j need.
    
    Co-authored-by: Claude <[email protected]>
    
    * CAMEL-26514: address review comments
    
    Pass the response model to the GenAI span, clear a stale 
CamelLangChain4jEmbeddingsResponseModel header, fail clearly when a model 
returns no embedding, and move the upgrade guide section before the Jackson 3 
sections.
    
    Co-authored-by: Claude <[email protected]>
    
    ---------
    
    Co-authored-by: Claude <[email protected]>
---
 .../embeddings/LangChain4jEmbeddingsProducer.java  | 43 ++++++++++++----------
 .../LangChain4jEmbeddingsErrorMetadataTest.java    |  3 +-
 .../LangChain4jEmbeddingsObservabilityTest.java    | 20 +++++++++-
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    | 11 ++++++
 4 files changed, 55 insertions(+), 22 deletions(-)

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 41e669110533..d224c83494dd 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
@@ -28,7 +28,8 @@ import java.util.List;
 import dev.langchain4j.data.embedding.Embedding;
 import dev.langchain4j.data.segment.TextSegment;
 import dev.langchain4j.model.embedding.EmbeddingModel;
-import dev.langchain4j.model.output.Response;
+import dev.langchain4j.model.embedding.request.EmbeddingRequest;
+import dev.langchain4j.model.embedding.response.EmbeddingResponse;
 import org.apache.camel.Exchange;
 import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.Message;
@@ -84,13 +85,14 @@ public class LangChain4jEmbeddingsProducer extends 
DefaultProducer {
                 .build();
         GenAiObservation observation = GenAiObservability.start(exchange, 
observationContext);
         try {
-            final Response<Embedding> result = model.embed(in);
+            final EmbeddingResponse result = 
model.embed(EmbeddingRequest.builder().textSegment(in).build());
             populateHeaders(message, result, in, 
observationContext.requestModel());
+            // an EmbeddingResponse has no finish reason
             observation.recordSuccess(GenAiUsage.of(
                     result.tokenUsage() != null ? 
result.tokenUsage().inputTokenCount() : null,
                     result.tokenUsage() != null ? 
result.tokenUsage().outputTokenCount() : null,
-                    result.finishReason(),
-                    null));
+                    null,
+                    result.modelName()));
         } catch (RuntimeException e) {
             GenAiErrorSupport.apply(exchange, e);
             observation.recordError(e);
@@ -116,14 +118,15 @@ public class LangChain4jEmbeddingsProducer extends 
DefaultProducer {
                 .build();
         GenAiObservation observation = GenAiObservability.start(exchange, 
observationContext);
         try {
-            final Response<List<Embedding>> result = model.embedAll(segments);
+            final EmbeddingResponse result = 
model.embed(EmbeddingRequest.builder().textSegments(segments).build());
 
             populateBatchHeaders(message, result, segments, 
observationContext.requestModel());
+            // an EmbeddingResponse has no finish reason
             observation.recordSuccess(GenAiUsage.of(
                     result.tokenUsage() != null ? 
result.tokenUsage().inputTokenCount() : null,
                     result.tokenUsage() != null ? 
result.tokenUsage().outputTokenCount() : null,
-                    result.finishReason(),
-                    null));
+                    null,
+                    result.modelName()));
         } catch (RuntimeException e) {
             GenAiErrorSupport.apply(exchange, e);
             observation.recordError(e);
@@ -183,6 +186,7 @@ public class LangChain4jEmbeddingsProducer extends 
DefaultProducer {
     private static void removeResultHeaders(Message message) {
         message.removeHeader(LangChain4jEmbeddingsHeaders.REQUEST_MODEL);
         message.removeHeader(LangChain4jEmbeddingsHeaders.FINISH_REASON);
+        message.removeHeader(LangChain4jEmbeddingsHeaders.RESPONSE_MODEL);
         message.removeHeader(LangChain4jEmbeddingsHeaders.INPUT_TOKEN_COUNT);
         message.removeHeader(LangChain4jEmbeddingsHeaders.OUTPUT_TOKEN_COUNT);
         message.removeHeader(LangChain4jEmbeddingsHeaders.TOTAL_TOKEN_COUNT);
@@ -194,14 +198,13 @@ public class LangChain4jEmbeddingsProducer extends 
DefaultProducer {
     }
 
     private static void populateHeaders(
-            Message message, Response<Embedding> result, TextSegment 
textSegment, String requestModel) {
+            Message message, EmbeddingResponse result, TextSegment 
textSegment, String requestModel) {
         removeResultHeaders(message);
         if (requestModel != null) {
             message.setHeader(LangChain4jEmbeddingsHeaders.REQUEST_MODEL, 
requestModel);
         }
-
-        if (result.finishReason() != null) {
-            message.setHeader(LangChain4jEmbeddingsHeaders.FINISH_REASON, 
result.finishReason());
+        if (result.modelName() != null) {
+            message.setHeader(LangChain4jEmbeddingsHeaders.RESPONSE_MODEL, 
result.modelName());
         }
 
         if (result.tokenUsage() != null) {
@@ -210,20 +213,23 @@ public class LangChain4jEmbeddingsProducer extends 
DefaultProducer {
             message.setHeader(LangChain4jEmbeddingsHeaders.TOTAL_TOKEN_COUNT, 
result.tokenUsage().totalTokenCount());
         }
 
-        message.setHeader(LangChain4jEmbeddingsHeaders.VECTOR, 
result.content().vector());
+        if (result.embeddings() == null || result.embeddings().isEmpty()) {
+            throw new IllegalStateException("The embedding model returned no 
embedding");
+        }
+        Embedding embedding = result.embeddings().get(0);
+        message.setHeader(LangChain4jEmbeddingsHeaders.VECTOR, 
embedding.vector());
         message.setHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT, 
textSegment);
-        message.setHeader(LangChain4jEmbeddingsHeaders.EMBEDDING, 
result.content());
+        message.setHeader(LangChain4jEmbeddingsHeaders.EMBEDDING, embedding);
     }
 
     private static void populateBatchHeaders(
-            Message message, Response<List<Embedding>> result, 
List<TextSegment> segments, String requestModel) {
+            Message message, EmbeddingResponse result, List<TextSegment> 
segments, String requestModel) {
         removeResultHeaders(message);
         if (requestModel != null) {
             message.setHeader(LangChain4jEmbeddingsHeaders.REQUEST_MODEL, 
requestModel);
         }
-
-        if (result.finishReason() != null) {
-            message.setHeader(LangChain4jEmbeddingsHeaders.FINISH_REASON, 
result.finishReason());
+        if (result.modelName() != null) {
+            message.setHeader(LangChain4jEmbeddingsHeaders.RESPONSE_MODEL, 
result.modelName());
         }
 
         if (result.tokenUsage() != null) {
@@ -232,8 +238,7 @@ public class LangChain4jEmbeddingsProducer extends 
DefaultProducer {
             message.setHeader(LangChain4jEmbeddingsHeaders.TOTAL_TOKEN_COUNT, 
result.tokenUsage().totalTokenCount());
         }
 
-        List<Embedding> embeddings = result.content();
-        message.setHeader(LangChain4jEmbeddingsHeaders.EMBEDDINGS, embeddings);
+        message.setHeader(LangChain4jEmbeddingsHeaders.EMBEDDINGS, 
result.embeddings());
         // Preserve text segments so downstream embedding store can associate 
them with embeddings
         message.setHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENTS, 
segments);
     }
diff --git 
a/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsErrorMetadataTest.java
 
b/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsErrorMetadataTest.java
index 5bee26554bd4..ccc882c3fb22 100644
--- 
a/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsErrorMetadataTest.java
+++ 
b/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsErrorMetadataTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.component.langchain4j.embeddings;
 
+import java.util.List;
 import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -96,7 +97,7 @@ class LangChain4jEmbeddingsErrorMetadataTest extends 
CamelTestSupport {
         }
 
         @Override
-        public Response<Embedding> embed(TextSegment textSegment) {
+        public Response<List<Embedding>> embedAll(List<TextSegment> 
textSegments) {
             throw new RateLimitException("quota exceeded");
         }
     }
diff --git 
a/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsObservabilityTest.java
 
b/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsObservabilityTest.java
index f4ed33d3574f..8c630926f654 100644
--- 
a/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsObservabilityTest.java
+++ 
b/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsObservabilityTest.java
@@ -89,6 +89,22 @@ class LangChain4jEmbeddingsObservabilityTest extends 
CamelTestSupport {
         
assertThat(tags.get(GenAiAttributes.OPERATION_NAME)).isEqualTo("embeddings");
         
assertThat(tags.get(GenAiAttributes.REQUEST_MODEL)).isEqualTo("text-embedding-3-small");
         
assertThat(tags.get(GenAiAttributes.CAMEL_COMPONENT)).isEqualTo("langchain4j-embeddings");
+        
assertThat(tags.get(GenAiAttributes.RESPONSE_MODEL)).isEqualTo("text-embedding-3-small");
+    }
+
+    @Test
+    void shouldSetResponseModelHeader() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(2);
+
+        template.sendBody("direct:start", TextSegment.from("Hello"));
+        template.sendBody("direct:start", List.of("Hello"));
+
+        mock.assertIsSatisfied(10, TimeUnit.SECONDS);
+        for (var exchange : mock.getReceivedExchanges()) {
+            
assertThat(exchange.getMessage().getHeader(LangChain4jEmbeddingsHeaders.RESPONSE_MODEL))
+                    .isEqualTo("text-embedding-3-small");
+        }
     }
 
     private static final class StubEmbeddingModel implements EmbeddingModel {
@@ -103,8 +119,8 @@ class LangChain4jEmbeddingsObservabilityTest extends 
CamelTestSupport {
         }
 
         @Override
-        public Response<Embedding> embed(TextSegment textSegment) {
-            return Response.from(Embedding.from(new float[] { 0.1f, 0.2f }),
+        public Response<List<Embedding>> embedAll(List<TextSegment> 
textSegments) {
+            return Response.from(List.of(Embedding.from(new float[] { 0.1f, 
0.2f })),
                     new TokenUsage(4, 0, 4));
         }
     }
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index d16e069d9cd8..d9bf677e1888 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -7765,6 +7765,17 @@ simple language) no longer has `"deprecationNote": ""`. 
The key is now present o
 already the case for options without `@Metadata`. Tools that read the catalog 
JSON get no key (null with
 `camel-tooling-model`) instead of an empty string.
 
+=== camel-langchain4j-embeddings - the producer uses the EmbeddingRequest API 
of LangChain4j
+
+The producer now calls `EmbeddingModel.embed(EmbeddingRequest)` instead of 
`embed(TextSegment)` and
+`embedAll(List<TextSegment>)`, so the 
`CamelLangChain4jEmbeddingsResponseModel` header is now set with the
+model name of the response. `EmbeddingResponse` has no finish reason, so the
+`CamelLangChain4jEmbeddingsFinishReason` header is no longer set.
+
+A custom `EmbeddingModel` must implement `embedAll(List<TextSegment>)` (or 
`doEmbed(EmbeddingRequest)`), as
+the LangChain4j models do. A model that only overrides `embed(TextSegment)` is 
no longer called, and the
+default methods of LangChain4j then fail with a `StackOverflowError`.
+
 === Jackson 3
 
 Camel 4.24 moves its own code to Jackson 3 (`tools.jackson`, version 3.2). The 
`camel-jackson`, `camel-jacksonxml`,

Reply via email to