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 64850880b504 CAMEL-23949: Fail fast when ADD is missing embedding 
header
64850880b504 is described below

commit 64850880b50475d47f3438cb53b0bd9ae54fb878
Author: Omar Atie <[email protected]>
AuthorDate: Sun Jul 19 23:43:19 2026 -0700

    CAMEL-23949: Fail fast when ADD is missing embedding header
    
    Throw NoSuchHeaderException instead of calling EmbeddingStore.add(null)
    when the CamelLangChain4jEmbeddingsEmbedding header is absent on ADD
    operations in camel-langchain4j-embeddingstore.
    
    Co-Authored-By: Cursor <[email protected]>
---
 .../LangChain4jEmbeddingStoreProducer.java         |  14 +-
 ...4jEmbeddingStoreMissingEmbeddingHeaderTest.java | 202 +++++++++++++++++++++
 2 files changed, 210 insertions(+), 6 deletions(-)

diff --git 
a/components/camel-ai/camel-langchain4j-embeddingstore/src/main/java/org/apache/camel/component/langchain4j/embeddingstore/LangChain4jEmbeddingStoreProducer.java
 
b/components/camel-ai/camel-langchain4j-embeddingstore/src/main/java/org/apache/camel/component/langchain4j/embeddingstore/LangChain4jEmbeddingStoreProducer.java
index 7a5a61c183cb..257583509ed9 100644
--- 
a/components/camel-ai/camel-langchain4j-embeddingstore/src/main/java/org/apache/camel/component/langchain4j/embeddingstore/LangChain4jEmbeddingStoreProducer.java
+++ 
b/components/camel-ai/camel-langchain4j-embeddingstore/src/main/java/org/apache/camel/component/langchain4j/embeddingstore/LangChain4jEmbeddingStoreProducer.java
@@ -128,16 +128,18 @@ public class LangChain4jEmbeddingStoreProducer extends 
DefaultProducer {
      */
     private void add(Exchange exchange) throws Exception {
         final Message in = exchange.getMessage();
-        Embedding embedding = null;
-        TextSegment text = null;
-        String id = null;
 
-        if (in.getHeader(LangChain4jEmbeddingsHeaders.EMBEDDING) != null) {
-            embedding = in.getHeader(LangChain4jEmbeddingsHeaders.EMBEDDING, 
Embedding.class);
+        if (in.getHeader(LangChain4jEmbeddingsHeaders.EMBEDDING) == null) {
+            throw new NoSuchHeaderException(
+                    "The embedding is a required header for ADD operations", 
exchange,
+                    LangChain4jEmbeddingsHeaders.EMBEDDING);
         }
 
+        Embedding embedding = 
in.getHeader(LangChain4jEmbeddingsHeaders.EMBEDDING, Embedding.class);
+        String id;
+
         if (in.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT) != null) {
-            text = in.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT, 
TextSegment.class);
+            TextSegment text = 
in.getHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT, TextSegment.class);
             id = 
getEndpoint().getConfiguration().getEmbeddingStore().add(embedding, text);
         } else {
             id = 
getEndpoint().getConfiguration().getEmbeddingStore().add(embedding);
diff --git 
a/components/camel-ai/camel-langchain4j-embeddingstore/src/test/java/org/apache/camel/component/langchain4j/embeddingstore/LangChain4jEmbeddingStoreMissingEmbeddingHeaderTest.java
 
b/components/camel-ai/camel-langchain4j-embeddingstore/src/test/java/org/apache/camel/component/langchain4j/embeddingstore/LangChain4jEmbeddingStoreMissingEmbeddingHeaderTest.java
new file mode 100644
index 000000000000..8f4bc5de91e6
--- /dev/null
+++ 
b/components/camel-ai/camel-langchain4j-embeddingstore/src/test/java/org/apache/camel/component/langchain4j/embeddingstore/LangChain4jEmbeddingStoreMissingEmbeddingHeaderTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.embeddingstore;
+
+import dev.langchain4j.data.embedding.Embedding;
+import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.NoSuchHeaderException;
+import 
org.apache.camel.component.langchain4j.embeddings.LangChain4jEmbeddingsHeaders;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class LangChain4jEmbeddingStoreMissingEmbeddingHeaderTest extends 
CamelTestSupport {
+
+    private RecordingEmbeddingStore embeddingStore;
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        embeddingStore = new RecordingEmbeddingStore();
+
+        LangChain4jEmbeddingStoreComponent component = context.getComponent(
+                LangChain4jEmbeddingStore.SCHEME, 
LangChain4jEmbeddingStoreComponent.class);
+        component.getConfiguration().setEmbeddingStore(embeddingStore);
+
+        return context;
+    }
+
+    @Test
+    @DisplayName("ADD without embedding header fails fast with 
NoSuchHeaderException")
+    void addWithoutEmbeddingHeaderFailsFast() {
+        Exchange result = fluentTemplate.to("langchain4j-embeddingstore:test")
+                .withHeader(LangChain4jEmbeddingStoreHeaders.ACTION, 
LangChain4jEmbeddingStoreAction.ADD)
+                .request(Exchange.class);
+
+        assertMissingEmbeddingHeader(result);
+        assertThat(embeddingStore.getAddInvocations()).isZero();
+    }
+
+    @Test
+    @DisplayName("ADD with endpoint default action but no embedding header 
fails fast")
+    void addWithEndpointDefaultActionAndMissingEmbeddingHeaderFailsFast() {
+        Exchange result = 
fluentTemplate.to("langchain4j-embeddingstore:test?action=ADD")
+                .request(Exchange.class);
+
+        assertMissingEmbeddingHeader(result);
+        assertThat(embeddingStore.getAddInvocations()).isZero();
+    }
+
+    @Test
+    @DisplayName("ADD with text segment but no embedding header still fails 
fast")
+    void addWithTextSegmentButNoEmbeddingHeaderFailsFast() {
+        Exchange result = fluentTemplate.to("langchain4j-embeddingstore:test")
+                .withHeader(LangChain4jEmbeddingStoreHeaders.ACTION, 
LangChain4jEmbeddingStoreAction.ADD)
+                .withHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT, 
TextSegment.from("hello"))
+                .request(Exchange.class);
+
+        assertMissingEmbeddingHeader(result);
+        assertThat(embeddingStore.getAddInvocations()).isZero();
+    }
+
+    @Test
+    @DisplayName("ADD with embedding header stores the embedding")
+    void addWithEmbeddingHeaderSucceeds() {
+        Embedding embedding = Embedding.from(new float[] { 0.1f, 0.2f, 0.3f });
+
+        Exchange result = fluentTemplate.to("langchain4j-embeddingstore:test")
+                .withHeader(LangChain4jEmbeddingStoreHeaders.ACTION, 
LangChain4jEmbeddingStoreAction.ADD)
+                .withHeader(LangChain4jEmbeddingsHeaders.EMBEDDING, embedding)
+                .request(Exchange.class);
+
+        assertThat(result.getException()).isNull();
+        assertThat(result.getMessage().getBody(String.class)).isNotBlank();
+        assertThat(embeddingStore.getAddInvocations()).isEqualTo(1);
+        assertThat(embeddingStore.getLastEmbedding()).isSameAs(embedding);
+        assertThat(embeddingStore.getLastTextSegment()).isNull();
+    }
+
+    @Test
+    @DisplayName("ADD with embedding and text segment stores both values")
+    void addWithEmbeddingAndTextSegmentSucceeds() {
+        Embedding embedding = Embedding.from(new float[] { 0.4f, 0.5f });
+        TextSegment textSegment = TextSegment.from("segment");
+
+        Exchange result = fluentTemplate.to("langchain4j-embeddingstore:test")
+                .withHeader(LangChain4jEmbeddingStoreHeaders.ACTION, 
LangChain4jEmbeddingStoreAction.ADD)
+                .withHeader(LangChain4jEmbeddingsHeaders.EMBEDDING, embedding)
+                .withHeader(LangChain4jEmbeddingsHeaders.TEXT_SEGMENT, 
textSegment)
+                .request(Exchange.class);
+
+        assertThat(result.getException()).isNull();
+        assertThat(embeddingStore.getAddInvocations()).isEqualTo(1);
+        assertThat(embeddingStore.getLastEmbedding()).isSameAs(embedding);
+        assertThat(embeddingStore.getLastTextSegment()).isSameAs(textSegment);
+    }
+
+    @Test
+    @DisplayName("Missing action header is still rejected")
+    void missingActionHeaderFailsFast() {
+        Exchange result = fluentTemplate.to("langchain4j-embeddingstore:test")
+                .withHeader(LangChain4jEmbeddingsHeaders.EMBEDDING, 
Embedding.from(new float[] { 1.0f }))
+                .request(Exchange.class);
+
+        
assertThat(result.getException()).isInstanceOf(NoSuchHeaderException.class);
+        assertThat(((NoSuchHeaderException) 
result.getException()).getHeaderName())
+                .isEqualTo(LangChain4jEmbeddingStoreHeaders.ACTION);
+        assertThat(embeddingStore.getAddInvocations()).isZero();
+    }
+
+    @Test
+    @DisplayName("REMOVE does not require the embedding header")
+    void removeWithoutEmbeddingHeaderSucceeds() {
+        String id = embeddingStore.add(Embedding.from(new float[] { 0.9f }));
+
+        Exchange result = fluentTemplate.to("langchain4j-embeddingstore:test")
+                .withHeader(LangChain4jEmbeddingStoreHeaders.ACTION, 
LangChain4jEmbeddingStoreAction.REMOVE)
+                .withBody(id)
+                .request(Exchange.class);
+
+        assertThat(result.getException()).isNull();
+        assertThat(embeddingStore.getRemoveInvocations()).isEqualTo(1);
+        assertThat(embeddingStore.getLastRemovedId()).isEqualTo(id);
+    }
+
+    private void assertMissingEmbeddingHeader(Exchange result) {
+        
assertThat(result.getException()).isInstanceOf(NoSuchHeaderException.class);
+        NoSuchHeaderException exception = (NoSuchHeaderException) 
result.getException();
+        
assertThat(exception.getHeaderName()).isEqualTo(LangChain4jEmbeddingsHeaders.EMBEDDING);
+        assertThat(exception.getMessage()).contains("required header");
+    }
+
+    private static final class RecordingEmbeddingStore extends 
InMemoryEmbeddingStore<TextSegment> {
+
+        private int addInvocations;
+        private int removeInvocations;
+        private Embedding lastEmbedding;
+        private TextSegment lastTextSegment;
+        private String lastRemovedId;
+
+        @Override
+        public String add(Embedding embedding) {
+            addInvocations++;
+            lastEmbedding = embedding;
+            lastTextSegment = null;
+            return super.add(embedding);
+        }
+
+        @Override
+        public String add(Embedding embedding, TextSegment embedded) {
+            addInvocations++;
+            lastEmbedding = embedding;
+            lastTextSegment = embedded;
+            return super.add(embedding, embedded);
+        }
+
+        @Override
+        public void remove(String id) {
+            removeInvocations++;
+            lastRemovedId = id;
+            super.remove(id);
+        }
+
+        int getAddInvocations() {
+            return addInvocations;
+        }
+
+        int getRemoveInvocations() {
+            return removeInvocations;
+        }
+
+        Embedding getLastEmbedding() {
+            return lastEmbedding;
+        }
+
+        TextSegment getLastTextSegment() {
+            return lastTextSegment;
+        }
+
+        String getLastRemovedId() {
+            return lastRemovedId;
+        }
+    }
+}

Reply via email to