diff --git a/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/transform/WeaviateEmbeddingsDataTypeTransformer.java b/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/transform/WeaviateEmbeddingsDataTypeTransformer.java index f9c33054ecc16..871acea76253d 100644 --- a/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/transform/WeaviateEmbeddingsDataTypeTransformer.java +++ b/components/camel-ai/camel-weaviate/src/main/java/org/apache/camel/component/weaviate/transform/WeaviateEmbeddingsDataTypeTransformer.java @@ -19,6 +19,7 @@ import java.util.HashMap; import java.util.List; +import java.util.Map; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; @@ -58,7 +59,8 @@ public void transform(Message message, DataType fromType, DataType toType) { case QUERY -> queryEmbeddingOperation(message, embedding, vectorFieldName, textFieldName, text, collectionName, keyValue, keyName); - default -> throw new IllegalStateException("The only operations supported are create and updatebyid"); + default -> + throw new IllegalStateException("The only operations supported are create, updatebyid and query"); } } @@ -66,22 +68,33 @@ private static void createEmbeddingOperation( Message message, Embedding embedding, String vectorFieldName, String textFieldName, TextSegment text, String collectionName, Object keyValue, String keyName) { message.setBody(embedding.vectorAsList(), List.class); - - if (ObjectHelper.isNotEmpty(keyValue) && ObjectHelper.isNotEmpty(keyName)) { - HashMap maps = new HashMap(); - maps.put(keyName, keyValue); - message.setHeader(WeaviateVectorDbHeaders.PROPERTIES, maps); - } + setProperties(message, textFieldName, text, keyValue, keyName); } private static void updateEmbeddingOperation( Message message, Embedding embedding, String vectorFieldName, String textFieldName, TextSegment text, String collectionName, Object keyValue, String keyName) { message.setBody(embedding.vectorAsList(), List.class); + setProperties(message, textFieldName, text, keyValue, keyName); + } + /** + * Writes the object properties for a CREATE / UPDATE_BY_ID operation. The embedded text is stored under + * textFieldName so that the source passage can be retrieved later; without it only the vector (and optional id) was + * persisted and the original text was lost. Mirrors the Milvus transformer. Any PROPERTIES header the caller + * already set is preserved: the text (and optional id) are merged into a copy of it rather than replacing it. + */ + private static void setProperties( + Message message, String textFieldName, TextSegment text, Object keyValue, String keyName) { + Map existing = message.getHeader(WeaviateVectorDbHeaders.PROPERTIES, Map.class); + HashMap maps = existing != null ? new HashMap<>(existing) : new HashMap<>(); + if (text != null && text.text() != null) { + maps.put(textFieldName, text.text()); + } if (ObjectHelper.isNotEmpty(keyValue) && ObjectHelper.isNotEmpty(keyName)) { - HashMap maps = new HashMap(); maps.put(keyName, keyValue); + } + if (!maps.isEmpty()) { message.setHeader(WeaviateVectorDbHeaders.PROPERTIES, maps); } } diff --git a/components/camel-ai/camel-weaviate/src/test/java/org/apache/camel/component/weaviate/transform/WeaviateEmbeddingsDataTypeTransformerTest.java b/components/camel-ai/camel-weaviate/src/test/java/org/apache/camel/component/weaviate/transform/WeaviateEmbeddingsDataTypeTransformerTest.java new file mode 100644 index 0000000000000..d454dcb6b73bd --- /dev/null +++ b/components/camel-ai/camel-weaviate/src/test/java/org/apache/camel/component/weaviate/transform/WeaviateEmbeddingsDataTypeTransformerTest.java @@ -0,0 +1,102 @@ +/* + * 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.weaviate.transform; + +import java.util.HashMap; +import java.util.Map; + +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.data.segment.TextSegment; +import org.apache.camel.Message; +import org.apache.camel.ai.CamelLangchain4jAttributes; +import org.apache.camel.component.weaviate.WeaviateVectorDbAction; +import org.apache.camel.component.weaviate.WeaviateVectorDbHeaders; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.spi.DataType; +import org.apache.camel.support.DefaultExchange; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class WeaviateEmbeddingsDataTypeTransformerTest { + + @SuppressWarnings("unchecked") + private Map transformProperties(WeaviateVectorDbAction action) throws Exception { + Embedding embedding = new Embedding(new float[] { 0.1f, 0.2f, 0.3f }); + TextSegment segment = TextSegment.from("the source passage"); + + try (DefaultCamelContext context = new DefaultCamelContext()) { + context.start(); + Message in = new DefaultExchange(context).getMessage(); + in.setHeader(CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR, embedding); + in.setHeader(WeaviateVectorDbHeaders.ACTION, action); + in.setHeader(WeaviateVectorDbHeaders.KEY_NAME, "id"); + in.setHeader(WeaviateVectorDbHeaders.KEY_VALUE, "doc-1"); + in.setBody(segment); + + new WeaviateEmbeddingsDataTypeTransformer().transform(in, DataType.ANY, DataType.ANY); + + return in.getHeader(WeaviateVectorDbHeaders.PROPERTIES, Map.class); + } + } + + @Test + void createStoresTheDocumentTextInProperties() throws Exception { + Map props = transformProperties(WeaviateVectorDbAction.CREATE); + assertThat(props) + .isNotNull() + .containsEntry("text", "the source passage") + .containsEntry("id", "doc-1"); + } + + @Test + void updateStoresTheDocumentTextInProperties() throws Exception { + Map props = transformProperties(WeaviateVectorDbAction.UPDATE_BY_ID); + assertThat(props) + .isNotNull() + .containsEntry("text", "the source passage") + .containsEntry("id", "doc-1"); + } + + @Test + @SuppressWarnings("unchecked") + void createMergesTextIntoAnExistingPropertiesHeader() throws Exception { + Embedding embedding = new Embedding(new float[] { 0.1f, 0.2f, 0.3f }); + TextSegment segment = TextSegment.from("the source passage"); + + try (DefaultCamelContext context = new DefaultCamelContext()) { + context.start(); + Message in = new DefaultExchange(context).getMessage(); + in.setHeader(CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR, embedding); + in.setHeader(WeaviateVectorDbHeaders.ACTION, WeaviateVectorDbAction.CREATE); + // Properties the caller populated before the transformer runs must survive. + Map callerProperties = new HashMap<>(); + callerProperties.put("sky", "blue"); + callerProperties.put("age", "34"); + in.setHeader(WeaviateVectorDbHeaders.PROPERTIES, callerProperties); + in.setBody(segment); + + new WeaviateEmbeddingsDataTypeTransformer().transform(in, DataType.ANY, DataType.ANY); + + Map props = in.getHeader(WeaviateVectorDbHeaders.PROPERTIES, Map.class); + assertThat(props) + .containsEntry("sky", "blue") + .containsEntry("age", "34") + .containsEntry("text", "the source passage"); + } + } +}