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`,