This is an automated email from the ASF dual-hosted git repository. zbendhiba 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 e9e869f9c25 CAMEL-21719: Neo4j Embedding Data transfomer for RAG results (#17072) e9e869f9c25 is described below commit e9e869f9c2587677b976e2fe2eff96f7cd48718b Author: Zineb BENDHIBA <bendhiba.zi...@gmail.com> AuthorDate: Wed Feb 5 20:09:12 2025 +0100 CAMEL-21719: Neo4j Embedding Data transfomer for RAG results (#17072) --- .../apache/camel/catalog/transformers.properties | 1 + .../camel/catalog/transformers/neo4j-rag.json | 14 ++++++ ...angChain4jEmbeddingsComponentNeo4jTargetIT.java | 29 +++++++++++- .../org/apache/camel/transformer.properties | 2 +- .../org/apache/camel/transformer/neo4j-rag | 2 + .../org/apache/camel/transformer/neo4j-rag.json | 14 ++++++ .../camel-neo4j/src/main/docs/neo4j-component.adoc | 49 +++++++++++++++----- ...ataTypeTransformer.java => Neo4jEmbedding.java} | 41 +++++++++++------ .../camel/component/neo4j/Neo4jProducer.java | 52 ++++++++++++++++------ .../Neo4jEmbeddingDataTypeTransformer.java | 17 ++++++- ...Neo4jReverseEmbeddingsDataTypeTransformer.java} | 27 ++++++++--- .../neo4j/it/Neo4jVectorEmbeddingsIT.java | 48 ++++++++++++++++---- 12 files changed, 238 insertions(+), 58 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers.properties b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers.properties index 4a3560e1fe5..b1ffb14a926 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers.properties +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers.properties @@ -29,6 +29,7 @@ google-storage-application-cloudevents http-application-cloudevents milvus-embeddings neo4j-embeddings +neo4j-rag pinecone-embeddings protobuf-binary protobuf-x-java-object diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/neo4j-rag.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/neo4j-rag.json new file mode 100644 index 00000000000..27e84a5f080 --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/neo4j-rag.json @@ -0,0 +1,14 @@ +{ + "transformer": { + "kind": "transformer", + "name": "neo4j:rag", + "title": "Neo4j (Rag)", + "description": "Prepares the similarity search LangChain4j embeddings to become a List of String for LangChain4j RAG", + "deprecated": false, + "javaType": "org.apache.camel.component.neo4j.transformer.Neo4jReverseEmbeddingsDataTypeTransformer", + "groupId": "org.apache.camel", + "artifactId": "camel-neo4j", + "version": "4.10.0-SNAPSHOT" + } +} + diff --git a/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsComponentNeo4jTargetIT.java b/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsComponentNeo4jTargetIT.java index 46fd8a65aa0..b6d29f463d5 100644 --- a/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsComponentNeo4jTargetIT.java +++ b/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsComponentNeo4jTargetIT.java @@ -16,6 +16,7 @@ */ package org.apache.camel.component.langchain4j.embeddings; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -28,6 +29,7 @@ import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.neo4j.Neo4Operation; import org.apache.camel.component.neo4j.Neo4jComponent; import org.apache.camel.component.neo4j.Neo4jConstants; +import org.apache.camel.spi.DataType; import org.apache.camel.test.infra.neo4j.services.Neo4jService; import org.apache.camel.test.infra.neo4j.services.Neo4jServiceFactory; import org.apache.camel.test.junit5.CamelTestSupport; @@ -47,7 +49,7 @@ import static org.junit.Assert.assertTrue; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class LangChain4jEmbeddingsComponentNeo4jTargetIT extends CamelTestSupport { - public static final String NEO4J_URI = "neo4j:neo4j"; + public static final String NEO4J_URI = "neo4j:neo4j?vectorIndexName=myIndex&label=Test"; @RegisterExtension static Neo4jService NEO4J = Neo4jServiceFactory.createSingletonService(); @@ -138,6 +140,20 @@ public class LangChain4jEmbeddingsComponentNeo4jTargetIT extends CamelTestSuppor } + @Test + @Order(3) + public void rag_similarity_search() { + Exchange result = fluentTemplate.to("direct:search") + .withBody("hi") + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + + assertThat(result.getIn().getBody()).isInstanceOfSatisfying(Collection.class, c -> assertThat(c).hasSize(1)); + assertTrue(result.getIn().getBody(List.class).contains("hi")); + } + @Override protected RoutesBuilder createRouteBuilder() { return new RouteBuilder() { @@ -149,6 +165,17 @@ public class LangChain4jEmbeddingsComponentNeo4jTargetIT extends CamelTestSuppor .setHeader(Neo4jConstants.Headers.LABEL).constant("Test") .transform(new org.apache.camel.spi.DataType("neo4j:embeddings")) .to(NEO4J_URI); + + from("direct:search") + .to("langchain4j-embeddings:test") + // transform prompt into embeddings for search + .transform( + new DataType("neo4j:embeddings")) + .setHeader(Neo4jConstants.Headers.OPERATION, constant(Neo4Operation.VECTOR_SIMILARITY_SEARCH)) + .to(NEO4J_URI) + // decode retrieved embeddings for RAG + .transform( + new DataType("neo4j:rag")); } }; } diff --git a/components/camel-ai/camel-neo4j/src/generated/resources/META-INF/services/org/apache/camel/transformer.properties b/components/camel-ai/camel-neo4j/src/generated/resources/META-INF/services/org/apache/camel/transformer.properties index 90fcac63c4f..10c4e1d06a4 100644 --- a/components/camel-ai/camel-neo4j/src/generated/resources/META-INF/services/org/apache/camel/transformer.properties +++ b/components/camel-ai/camel-neo4j/src/generated/resources/META-INF/services/org/apache/camel/transformer.properties @@ -1,5 +1,5 @@ # Generated by camel build tools - do NOT edit this file! -transformers=neo4j:embeddings +transformers=neo4j:embeddings neo4j:rag groupId=org.apache.camel artifactId=camel-neo4j version=4.10.0-SNAPSHOT diff --git a/components/camel-ai/camel-neo4j/src/generated/resources/META-INF/services/org/apache/camel/transformer/neo4j-rag b/components/camel-ai/camel-neo4j/src/generated/resources/META-INF/services/org/apache/camel/transformer/neo4j-rag new file mode 100644 index 00000000000..179c57958a6 --- /dev/null +++ b/components/camel-ai/camel-neo4j/src/generated/resources/META-INF/services/org/apache/camel/transformer/neo4j-rag @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.neo4j.transformer.Neo4jReverseEmbeddingsDataTypeTransformer diff --git a/components/camel-ai/camel-neo4j/src/generated/resources/META-INF/services/org/apache/camel/transformer/neo4j-rag.json b/components/camel-ai/camel-neo4j/src/generated/resources/META-INF/services/org/apache/camel/transformer/neo4j-rag.json new file mode 100644 index 00000000000..27e84a5f080 --- /dev/null +++ b/components/camel-ai/camel-neo4j/src/generated/resources/META-INF/services/org/apache/camel/transformer/neo4j-rag.json @@ -0,0 +1,14 @@ +{ + "transformer": { + "kind": "transformer", + "name": "neo4j:rag", + "title": "Neo4j (Rag)", + "description": "Prepares the similarity search LangChain4j embeddings to become a List of String for LangChain4j RAG", + "deprecated": false, + "javaType": "org.apache.camel.component.neo4j.transformer.Neo4jReverseEmbeddingsDataTypeTransformer", + "groupId": "org.apache.camel", + "artifactId": "camel-neo4j", + "version": "4.10.0-SNAPSHOT" + } +} + diff --git a/components/camel-ai/camel-neo4j/src/main/docs/neo4j-component.adoc b/components/camel-ai/camel-neo4j/src/main/docs/neo4j-component.adoc index 5fc4b0a8960..ea552b2faa2 100644 --- a/components/camel-ai/camel-neo4j/src/main/docs/neo4j-component.adoc +++ b/components/camel-ai/camel-neo4j/src/main/docs/neo4j-component.adoc @@ -223,16 +223,20 @@ The URI endpoint should contain also specify the vector index name. === Create a vector To create a vector in a database named `test`, use the operation `CREATE_VECTOR`. The URI endpoint should also specify the label, the alias and the vector index name. +Put the vector array in the `CamelLangChain4jEmbeddingsVector` header, and the corresponding text in the body. +The `id` can be generated by Camel Neo4j. + +Camel Neo4j will create the node and store the vector as an `embedding` property, the text as `text` property and the `id`as `id` property. -Camel Neo4j will create the node and store the vector as an `embedding` property. .Example: [source,java] ---- - Exchange result = fluentTemplate.to("neo4j:test?vectorIndexName=movieIdx&label=Movie&alias=m") - .withHeader(Neo4j.Headers.OPERATION, Neo4Operation.CREATE_VECTOR) - .withHeader(Neo4j.Headers.VECTOR_ID, testData.getId()) - .withBody(List.of(0.8f, 0.6f)) + Exchange result = fluentTemplate.to("neo4j:test?vectorIndexName=myIndex&label=Test&alias=t") + .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.CREATE_VECTOR) + .withHeader(Neo4jConstants.Headers.VECTOR_ID, "1") + .withHeader("CamelLangChain4jEmbeddingsVector", new float[] { 10.8f, 10.6f }) + .withBody("Hello World!") .request(Exchange.class); ---- @@ -244,9 +248,8 @@ The URI endpoint should also specify the label, the alias and the vector index n .Example: [source,java] ---- - Exchange result = fluentTemplate.to("neo4j:test?vectorIndexName=movieIdx&label=Movie&alias=m") - .withHeader(Neo4j.Headers.OPERATION, Neo4Operation.CREATE_VECTOR) - .withHeader(Neo4j.Headers.VECTOR_ID, testData.getId()) + Exchange result = fluentTemplate.to("neo4j:test?vectorIndexName=myIndex&label=Test&alias=t") + .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.VECTOR_SIMILARITY_SEARCH) .withBody(List.of(0.75f, 0.65f)) .request(Exchange.class); ---- @@ -254,7 +257,7 @@ The URI endpoint should also specify the label, the alias and the vector index n == Generate Embeddings with Langchain4j-embeddings You can generate embeddings with an Embedding Models using the camel Lancghain4j Embeddings components. Camel Neo4j introduces a DataType `neo4j:embeddings` that automates the transformations of the Lancghain4j embeddings to Neo4j vectors. -.Example of a camel Route that create embeddings with Camel Langchain4j Embeddings +.Example of a camel Route that create embeddings with Camel Langchain4j Embeddings, and ingest them into Neo4j database. [source,java] ---- from("direct:in") @@ -262,5 +265,31 @@ You can generate embeddings with an Embedding Models using the camel Lancghain4j .setHeader(Neo4j.Headers.OPERATION).constant(Neo4Operation.CREATE_VECTOR) .setHeader(Neo4j.Headers.LABEL).constant("Test") .transform(new DataType("neo4j:embeddings")) - .to("neo4j:test"); + .to("neo4j:neo4j?vectorIndexName=myIndex&label=Test"); ---- + +== Similarity Search for LangChain4j RAG +You can enhance the Camel LangChain4j chat RAG experience by integrating Neo4j similarity search with Camel Neo4j DataTypes. + +To achieve this, use the `neo4j:embeddings` DataType to generate embeddings from the prompt. These embeddings will then be utilized for the similarity search operation. + +Next, use the `neo4j:rag` DataType to convert the retrieved embeddings into a List<String> for RAG. This list can be directly used with the `LangChain4jRagAggregatorStrategy` from the LangChain4j chat component. + +NOTE: The retrieved embeddings must be ingested in Neo4j as LangChain4j embeddings. + +.Example of a camel Route that performs a similarity search in the Vector index, using a string and returning a list of strings +[source,java] +---- + from("direct:search") + .to("langchain4j-embeddings:test") + // transform prompt into embeddings for search + .transform( + new DataType("neo4j:embeddings")) + .setHeader(Neo4jConstants.Headers.OPERATION, constant(Neo4Operation.VECTOR_SIMILARITY_SEARCH)) + .to("neo4j:neo4j?vectorIndexName=myIndex&label=Test") + // decode retrieved embeddings for RAG + .transform( + new DataType("neo4j:rag")); +---- + + diff --git a/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jEmbeddingDataTypeTransformer.java b/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jEmbedding.java similarity index 50% copy from components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jEmbeddingDataTypeTransformer.java copy to components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jEmbedding.java index 78544256206..02d06718ecc 100644 --- a/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jEmbeddingDataTypeTransformer.java +++ b/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jEmbedding.java @@ -14,21 +14,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.component.neo4j.transformer; +package org.apache.camel.component.neo4j; -import dev.langchain4j.data.embedding.Embedding; -import org.apache.camel.Message; -import org.apache.camel.ai.CamelLangchain4jAttributes; -import org.apache.camel.spi.DataType; -import org.apache.camel.spi.DataTypeTransformer; -import org.apache.camel.spi.Transformer; +/** + * Class that represents the embedding to persist when using LangChain4j - The names of the properties correspond to the + * ones in LangChain4j project for compatibility. + */ +public class Neo4jEmbedding { + private String id; + + private String text; + + private float[] vectors; + + public Neo4jEmbedding(String id, String text, float[] vectors) { + this.id = id; + this.text = text; + this.vectors = vectors; + } + + public String getId() { + return id; + } + + public String getText() { + return text; + } -@DataTypeTransformer(name = "neo4j:embeddings", - description = "Prepares the message to become an object writable by Neo4j component") -public class Neo4jEmbeddingDataTypeTransformer extends Transformer { - @Override - public void transform(Message message, DataType fromType, DataType toType) { - Embedding embedding = message.getHeader(CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR, Embedding.class); - message.setBody(embedding.vector()); + public float[] getVectors() { + return vectors; } } diff --git a/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jProducer.java b/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jProducer.java index 6dfa6bc8838..c1ac6cb0a87 100644 --- a/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jProducer.java +++ b/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jProducer.java @@ -25,6 +25,7 @@ import org.apache.camel.Exchange; import org.apache.camel.InvalidPayloadException; import org.apache.camel.Message; import org.apache.camel.NoSuchHeaderException; +import org.apache.camel.ai.CamelLangchain4jAttributes; import org.apache.camel.support.DefaultProducer; import org.apache.camel.util.ObjectHelper; import org.neo4j.driver.Driver; @@ -242,37 +243,60 @@ public class Neo4jProducer extends DefaultProducer { private void createVector(Exchange exchange) { final String alias - = getEndpoint().getConfiguration().getAlias() != null ? getEndpoint().getConfiguration().getAlias() : "x"; + = getEndpoint().getConfiguration().getAlias() != null ? getEndpoint().getConfiguration().getAlias() : "e"; - final String label = exchange.getMessage().getHeader(Neo4jConstants.Headers.LABEL, - () -> getEndpoint().getConfiguration().getLabel(), String.class); - ObjectHelper.notNull(label, "label"); - - final String id - = exchange.getMessage().getHeader(Neo4jConstants.Headers.VECTOR_ID, () -> UUID.randomUUID(), String.class); + final String label + = getEndpoint().getConfiguration().getLabel() != null + ? getEndpoint().getConfiguration().getLabel() : "Embedding"; - final float[] body = exchange.getMessage().getBody(float[].class); + String id; + String text; + float[] vectors; final String databaseName = getEndpoint().getName(); + Object body = exchange.getMessage().getBody(); + + if (body instanceof Neo4jEmbedding) { + id = ((Neo4jEmbedding) body).getId(); + text = ((Neo4jEmbedding) body).getText(); + vectors = ((Neo4jEmbedding) body).getVectors(); + } else { + id = exchange.getMessage().getHeader(Neo4jConstants.Headers.VECTOR_ID, () -> UUID.randomUUID(), String.class); + vectors = exchange.getMessage().getHeader(CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR, + float[].class); + text = exchange.getMessage().getBody(String.class); + } + + ObjectHelper.notNull(text, "text"); + ObjectHelper.notNull(vectors, "vectors"); + String query = String.format(""" - MERGE (%s:%s {id: $id}) + MERGE (%s:%s {id: $id, text: $text}) WITH %s CALL db.create.setNodeVectorProperty(%s, 'embedding', $embedding); """, alias, label, alias, alias); Map<String, Object> params = Map.of( - "embedding", Values.value(body), - "id", id); + "embedding", Values.value(vectors), + "id", id, + "text", text); executeWriteQuery(exchange, query, params, databaseName, Neo4Operation.CREATE_VECTOR); } - public void similaritySearch(Exchange exchange) { + public void similaritySearch(Exchange exchange) throws InvalidPayloadException { final String vectorIndexName = getEndpoint().getConfiguration().getVectorIndexName(); ObjectHelper.notNull(vectorIndexName, "vectorIndexName"); - final float[] body = exchange.getMessage().getBody(float[].class); + float[] vectors; + + Object body = exchange.getMessage().getMandatoryBody(); + if (body instanceof Neo4jEmbedding) { + vectors = ((Neo4jEmbedding) body).getVectors(); + } else { + vectors = exchange.getMessage().getBody(float[].class); + } final double minScore = getEndpoint().getConfiguration().getMinScore(); @@ -288,7 +312,7 @@ public class Neo4jProducer extends DefaultProducer { """; Map<String, Object> params = Map.of("indexName", vectorIndexName, - "embeddingValue", body, + "embeddingValue", vectors, "minScore", minScore, "maxResults", maxResults); diff --git a/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jEmbeddingDataTypeTransformer.java b/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jEmbeddingDataTypeTransformer.java index 78544256206..2908a8ab057 100644 --- a/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jEmbeddingDataTypeTransformer.java +++ b/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jEmbeddingDataTypeTransformer.java @@ -16,9 +16,14 @@ */ package org.apache.camel.component.neo4j.transformer; +import java.util.UUID; + 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.neo4j.Neo4jConstants; +import org.apache.camel.component.neo4j.Neo4jEmbedding; import org.apache.camel.spi.DataType; import org.apache.camel.spi.DataTypeTransformer; import org.apache.camel.spi.Transformer; @@ -28,7 +33,15 @@ import org.apache.camel.spi.Transformer; public class Neo4jEmbeddingDataTypeTransformer extends Transformer { @Override public void transform(Message message, DataType fromType, DataType toType) { - Embedding embedding = message.getHeader(CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR, Embedding.class); - message.setBody(embedding.vector()); + final Embedding embedding + = message.getHeader(CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR, Embedding.class); + + final TextSegment text = message.getBody(TextSegment.class); + + final String id = message.getHeader(Neo4jConstants.Headers.VECTOR_ID, () -> UUID.randomUUID(), String.class); + + Neo4jEmbedding neo4jEmbedding = new Neo4jEmbedding(id, text.text(), embedding.vector()); + + message.setBody(neo4jEmbedding); } } diff --git a/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jEmbeddingDataTypeTransformer.java b/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jReverseEmbeddingsDataTypeTransformer.java similarity index 59% copy from components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jEmbeddingDataTypeTransformer.java copy to components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jReverseEmbeddingsDataTypeTransformer.java index 78544256206..cc711176585 100644 --- a/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jEmbeddingDataTypeTransformer.java +++ b/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/transformer/Neo4jReverseEmbeddingsDataTypeTransformer.java @@ -14,21 +14,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.camel.component.neo4j.transformer; -import dev.langchain4j.data.embedding.Embedding; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import org.apache.camel.Message; -import org.apache.camel.ai.CamelLangchain4jAttributes; import org.apache.camel.spi.DataType; import org.apache.camel.spi.DataTypeTransformer; import org.apache.camel.spi.Transformer; -@DataTypeTransformer(name = "neo4j:embeddings", - description = "Prepares the message to become an object writable by Neo4j component") -public class Neo4jEmbeddingDataTypeTransformer extends Transformer { +/** + * Maps a List of retrieved LangChain4j Embeddings with similarity search to a List of String for LangChain4j RAG + **/ +@DataTypeTransformer(name = "neo4j:rag", + description = "Prepares the similarity search LangChain4j embeddings to become a List of String for LangChain4j RAG") +public class Neo4jReverseEmbeddingsDataTypeTransformer extends Transformer { + @Override public void transform(Message message, DataType fromType, DataType toType) { - Embedding embedding = message.getHeader(CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR, Embedding.class); - message.setBody(embedding.vector()); + final List<Map<String, Object>> embeddings = message.getBody(List.class); + + List<String> result = embeddings.stream() + .map(embedding -> (String) embedding.getOrDefault("text", "")) + .collect(Collectors.toList()); + + message.setBody(result); + } } diff --git a/components/camel-ai/camel-neo4j/src/test/java/org/apache/camel/component/neo4j/it/Neo4jVectorEmbeddingsIT.java b/components/camel-ai/camel-neo4j/src/test/java/org/apache/camel/component/neo4j/it/Neo4jVectorEmbeddingsIT.java index b2e04c3a2c9..ab5fce9bd7a 100644 --- a/components/camel-ai/camel-neo4j/src/test/java/org/apache/camel/component/neo4j/it/Neo4jVectorEmbeddingsIT.java +++ b/components/camel-ai/camel-neo4j/src/test/java/org/apache/camel/component/neo4j/it/Neo4jVectorEmbeddingsIT.java @@ -20,8 +20,10 @@ import java.util.List; import org.apache.camel.Exchange; import org.apache.camel.Message; +import org.apache.camel.ai.CamelLangchain4jAttributes; import org.apache.camel.component.neo4j.Neo4Operation; import org.apache.camel.component.neo4j.Neo4jConstants; +import org.apache.camel.component.neo4j.Neo4jEmbedding; import org.apache.camel.component.neo4j.Neo4jTestSupport; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; @@ -62,7 +64,8 @@ public class Neo4jVectorEmbeddingsIT extends Neo4jTestSupport { Exchange result = fluentTemplate.to("neo4j:neo4j?vectorIndexName=movieIdx&label=Movie&alias=m") .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.CREATE_VECTOR) .withHeader(Neo4jConstants.Headers.VECTOR_ID, testData.getId()) - .withBody(testData.getVectors()) + .withHeader(CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR, testData.getVectors()) + .withBody(testData.getText()) .request(Exchange.class); assertNotNull(result); @@ -77,8 +80,29 @@ public class Neo4jVectorEmbeddingsIT extends Neo4jTestSupport { assertEquals("A node creation is expected ", 1, in.getHeader(Neo4jConstants.Headers.QUERY_RESULT_NODES_CREATED)); } - @Test @Order(2) + @Test + void addGeneratedNeo4jEmbedding() { + Neo4jEmbedding neo4jEmbedding = new Neo4jEmbedding("15", "Hello World", new float[] { 10.8f, 10.6f }); + Exchange result = fluentTemplate.to("neo4j:neo4j?vectorIndexName=movieIdx&label=Movie&alias=m") + .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.CREATE_VECTOR) + .withBody(neo4jEmbedding) + .request(Exchange.class); + + assertNotNull(result); + + Message in = result.getMessage(); + assertNotNull(in); + + assertEquals(Neo4Operation.CREATE_VECTOR, in.getHeader(Neo4jConstants.Headers.OPERATION)); + assertTrue("The executed request should contain the procedure of setting vector embedding", + in.getHeader(Neo4jConstants.Headers.QUERY_RESULT, String.class) + .contains("CALL db.create.setNodeVectorProperty")); + assertEquals("A node creation is expected ", 1, in.getHeader(Neo4jConstants.Headers.QUERY_RESULT_NODES_CREATED)); + } + + @Test + @Order(3) public void similaritySeach() { Exchange result = fluentTemplate.to("neo4j:neo4j?vectorIndexName=movieIdx&label=Movie&alias=m") .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.VECTOR_SIMILARITY_SEARCH) @@ -98,7 +122,7 @@ public class Neo4jVectorEmbeddingsIT extends Neo4jTestSupport { } @Test - @Order(3) + @Order(4) void dropVectorIndex() { Exchange result = fluentTemplate.to("neo4j:neo4j?vectorIndexName=movieIdx") .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.DROP_VECTOR_INDEX) @@ -116,17 +140,19 @@ public class Neo4jVectorEmbeddingsIT extends Neo4jTestSupport { // Enum to provide test data public enum TestData { - VECTOR_1(9, List.of(0.8f, 0.6f)), - VECTOR_2(10, List.of(0.1f, 0.9f)), - VECTOR_3(11, List.of(0.7f, 0.7f)), - VECTOR_4(12, List.of(-0.3f, -0.9f)), - VECTOR_5(13, List.of(1.2f, 0.8f)); + VECTOR_1(9, "VECTOR_1", List.of(0.8f, 0.6f)), + VECTOR_2(10, "VECTOR_2", List.of(0.1f, 0.9f)), + VECTOR_3(11, "VECTOR_3", List.of(0.7f, 0.7f)), + VECTOR_4(12, "VECTOR_4", List.of(-0.3f, -0.9f)), + VECTOR_5(13, "VECTOR_5", List.of(1.2f, 0.8f)); private final int id; + private final String text; private final List<Float> vectors; - TestData(int id, List<Float> vectors) { + TestData(int id, String text, List<Float> vectors) { this.id = id; + this.text = text; this.vectors = vectors; } @@ -137,5 +163,9 @@ public class Neo4jVectorEmbeddingsIT extends Neo4jTestSupport { public List<Float> getVectors() { return vectors; } + + public String getText() { + return text; + } } }