diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml index 4b93de1f546a1..e8668d2059c56 100644 --- a/bom/camel-bom/pom.xml +++ b/bom/camel-bom/pom.xml @@ -1902,6 +1902,11 @@ camel-pgevent 4.19.0-SNAPSHOT + + org.apache.camel + camel-pgvector + 4.19.0-SNAPSHOT + org.apache.camel camel-pinecone diff --git a/catalog/camel-allcomponents/pom.xml b/catalog/camel-allcomponents/pom.xml index 593117e20b1c2..ea92025722c8e 100644 --- a/catalog/camel-allcomponents/pom.xml +++ b/catalog/camel-allcomponents/pom.xml @@ -1692,6 +1692,11 @@ camel-pgevent ${project.version} + + org.apache.camel + camel-pgvector + ${project.version} + org.apache.camel camel-pinecone diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components.properties b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components.properties index e9742912148af..6d730df36b5ac 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components.properties +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components.properties @@ -295,6 +295,7 @@ paho-mqtt5 pdf pg-replication-slot pgevent +pgvector pinecone platform-http plc4x diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pgvector.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pgvector.json new file mode 100644 index 0000000000000..163451d430669 --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pgvector.json @@ -0,0 +1,50 @@ +{ + "component": { + "kind": "component", + "name": "pgvector", + "title": "PGVector", + "description": "Perform operations on the PostgreSQL pgvector Vector Database.", + "deprecated": false, + "firstVersion": "4.19.0", + "label": "database,ai", + "javaType": "org.apache.camel.component.pgvector.PgVectorComponent", + "supportLevel": "Preview", + "groupId": "org.apache.camel", + "artifactId": "camel-pgvector", + "version": "4.19.0-SNAPSHOT", + "scheme": "pgvector", + "extendsScheme": "", + "syntax": "pgvector:collection", + "async": false, + "api": false, + "consumerOnly": false, + "producerOnly": true, + "lenientProperties": false, + "browsable": false, + "remote": true + }, + "componentProperties": { + "configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.pgvector.PgVectorConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The configuration;" }, + "dataSource": { "index": 1, "kind": "property", "displayName": "Data Source", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "javax.sql.DataSource", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The DataSource to use for connecting to the PostgreSQL database with pgvector extension." }, + "dimension": { "index": 2, "kind": "property", "displayName": "Dimension", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 384, "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The dimension of the vectors to store." }, + "distanceType": { "index": 3, "kind": "property", "displayName": "Distance Type", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.pgvector.PgVectorDistanceType", "enum": [ "COSINE", "EUCLIDEAN", "INNER_PRODUCT" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "COSINE", "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The distance type to use for similarity search." }, + "lazyStartProducer": { "index": 4, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, + "autowiredEnabled": { "index": 5, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc." } + }, + "headers": { + "CamelPgVectorAction": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "enum": [ "CREATE_TABLE", "CREATE_INDEX", "DROP_TABLE", "UPSERT", "DELETE", "SIMILARITY_SEARCH" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The action to be performed.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#ACTION" }, + "CamelPgVectorRecordId": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The id of the vector record.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#RECORD_ID" }, + "CamelPgVectorQueryTopK": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "3", "description": "The maximum number of results to return for similarity search.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#QUERY_TOP_K" }, + "CamelPgVectorTextContent": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The text content to store alongside the vector embedding.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#TEXT_CONTENT" }, + "CamelPgVectorMetadata": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The metadata associated with the vector record, stored as JSON.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#METADATA" }, + "CamelPgVectorFilter": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Filter condition for similarity search. Applied as a SQL WHERE clause on the text_content and metadata columns. Supports parameterized queries using placeholders with values provided via the CamelPgVectorFilterParams header. WARNING: When not using parameterized queries, the filter value is appended directly as SQL. Never use untrusted input as the filter value without parameterization, as this could lead to SQL injection.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#FILTER" }, + "CamelPgVectorFilterParams": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Parameter values for parameterized filter queries. Use with placeholders in the CamelPgVectorFilter header. Example: filter = 'text_content LIKE AND metadata::jsonb-'category' = ' with filterParams = List.of(%hello%, science).", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#FILTER_PARAMS" } + }, + "properties": { + "collection": { "index": 0, "kind": "path", "displayName": "Collection", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The collection (table) name" }, + "dataSource": { "index": 1, "kind": "parameter", "displayName": "Data Source", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "javax.sql.DataSource", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The DataSource to use for connecting to the PostgreSQL database with pgvector extension." }, + "dimension": { "index": 2, "kind": "parameter", "displayName": "Dimension", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 384, "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The dimension of the vectors to store." }, + "distanceType": { "index": 3, "kind": "parameter", "displayName": "Distance Type", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.pgvector.PgVectorDistanceType", "enum": [ "COSINE", "EUCLIDEAN", "INNER_PRODUCT" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "COSINE", "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The distance type to use for similarity search." }, + "lazyStartProducer": { "index": 4, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." } + } +} 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 c261164480cc8..51e76f50317c1 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 @@ -32,6 +32,8 @@ infinispan-embeddings milvus-embeddings neo4j-embeddings neo4j-rag +pgvector-embeddings +pgvector-rag pinecone-embeddings pinecone-rag protobuf-binary diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/pgvector-embeddings.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/pgvector-embeddings.json new file mode 100644 index 0000000000000..2a5efd26fd33d --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/pgvector-embeddings.json @@ -0,0 +1,14 @@ +{ + "transformer": { + "kind": "transformer", + "name": "pgvector:embeddings", + "title": "Pgvector (Embeddings)", + "description": "Prepares the message to become an object writable by PgVector component", + "deprecated": false, + "javaType": "org.apache.camel.component.pgvector.transform.PgVectorEmbeddingsDataTypeTransformer", + "groupId": "org.apache.camel", + "artifactId": "camel-pgvector", + "version": "4.19.0-SNAPSHOT" + } +} + diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/pgvector-rag.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/pgvector-rag.json new file mode 100644 index 0000000000000..bde0480f57203 --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/pgvector-rag.json @@ -0,0 +1,14 @@ +{ + "transformer": { + "kind": "transformer", + "name": "pgvector:rag", + "title": "Pgvector (Rag)", + "description": "Prepares the PgVector similarity search results to become a List of String for LangChain4j RAG", + "deprecated": false, + "javaType": "org.apache.camel.component.pgvector.transform.PgVectorReverseEmbeddingsDataTypeTransformer", + "groupId": "org.apache.camel", + "artifactId": "camel-pgvector", + "version": "4.19.0-SNAPSHOT" + } +} + diff --git a/components/camel-ai/camel-langchain4j-embeddings/pom.xml b/components/camel-ai/camel-langchain4j-embeddings/pom.xml index 9ea004fe94ca0..9993ff4d88259 100644 --- a/components/camel-ai/camel-langchain4j-embeddings/pom.xml +++ b/components/camel-ai/camel-langchain4j-embeddings/pom.xml @@ -87,6 +87,11 @@ camel-milvus test + + org.apache.camel + camel-pgvector + test + org.apache.camel camel-pinecone @@ -156,6 +161,12 @@ ${project.version} test + + org.apache.camel + camel-test-infra-postgres + ${project.version} + test + org.apache.camel camel-test-infra-weaviate diff --git a/components/camel-ai/camel-langchain4j-embeddings/src/main/docs/langchain4j-embeddings-component.adoc b/components/camel-ai/camel-langchain4j-embeddings/src/main/docs/langchain4j-embeddings-component.adoc index b9328f28701d4..884940a9dc067 100644 --- a/components/camel-ai/camel-langchain4j-embeddings/src/main/docs/langchain4j-embeddings-component.adoc +++ b/components/camel-ai/camel-langchain4j-embeddings/src/main/docs/langchain4j-embeddings-component.adoc @@ -268,6 +268,77 @@ YAML:: ---- ==== +==== Using with PGVector + +This example shows how to embed text and store it in PostgreSQL with pgvector: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:store") + .to("langchain4j-embeddings:embed") + .setHeader(PgVectorHeaders.ACTION).constant(PgVectorAction.UPSERT) + .transformDataType(new DataType("pgvector:embeddings")) + .to("pgvector:myCollection"); +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: "direct:store" + steps: + - to: "langchain4j-embeddings:embed" + - setHeader: + name: CamelPgVectorAction + constant: UPSERT + - transform: + dataType: "pgvector:embeddings" + - to: "pgvector:myCollection" +---- +==== + +Similarity search with PGVector and RAG: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:search") + .to("langchain4j-embeddings:embed") + .transformDataType(new DataType("pgvector:embeddings")) + .setHeader(PgVectorHeaders.ACTION, constant(PgVectorAction.SIMILARITY_SEARCH)) + .to("pgvector:myCollection") + .transformDataType(new DataType("pgvector:rag")); +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: "direct:search" + steps: + - to: "langchain4j-embeddings:embed" + - transform: + dataType: "pgvector:embeddings" + - setHeader: + name: CamelPgVectorAction + constant: SIMILARITY_SEARCH + - to: "pgvector:myCollection" + - transform: + dataType: "pgvector:rag" +---- +==== + ==== Using with LangChain4j Embedding Store Component For a simpler integration, use the `langchain4j-embeddingstore` component which provides a unified interface: diff --git a/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsComponentPgVectorTargetIT.java b/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsComponentPgVectorTargetIT.java new file mode 100644 index 0000000000000..cb1f75b825ef8 --- /dev/null +++ b/components/camel-ai/camel-langchain4j-embeddings/src/test/java/org/apache/camel/component/langchain4j/embeddings/LangChain4jEmbeddingsComponentPgVectorTargetIT.java @@ -0,0 +1,141 @@ +/* + * 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.embeddings; + +import java.util.List; + +import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.pgvector.PgVector; +import org.apache.camel.component.pgvector.PgVectorAction; +import org.apache.camel.component.pgvector.PgVectorComponent; +import org.apache.camel.component.pgvector.PgVectorHeaders; +import org.apache.camel.spi.DataType; +import org.apache.camel.test.infra.postgres.services.PostgresService; +import org.apache.camel.test.infra.postgres.services.PostgresVectorServiceFactory; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.postgresql.ds.PGSimpleDataSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class LangChain4jEmbeddingsComponentPgVectorTargetIT extends CamelTestSupport { + + public static final String PGVECTOR_URI = "pgvector:embeddings"; + + @RegisterExtension + static PostgresService POSTGRES = PostgresVectorServiceFactory.createService(); + + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + + PGSimpleDataSource dataSource = new PGSimpleDataSource(); + dataSource.setUrl(POSTGRES.jdbcUrl()); + dataSource.setUser(POSTGRES.userName()); + dataSource.setPassword(POSTGRES.password()); + + PgVectorComponent component = context.getComponent(PgVector.SCHEME, PgVectorComponent.class); + component.getConfiguration().setDataSource(dataSource); + context.getRegistry().bind("embedding-model", new AllMiniLmL6V2EmbeddingModel()); + + return context; + } + + @Test + @Order(1) + public void createTable() { + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.CREATE_TABLE) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + } + + @Test + @Order(2) + public void upsertEmbedding() { + Exchange result = fluentTemplate.to("direct:in") + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.UPSERT) + .withHeader(PgVectorHeaders.RECORD_ID, "embed-1") + .withBody("The sky is blue") + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(String.class)).isEqualTo("embed-1"); + } + + @Test + @Order(3) + public void upsertSecondEmbedding() { + Exchange result = fluentTemplate.to("direct:in") + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.UPSERT) + .withHeader(PgVectorHeaders.RECORD_ID, "embed-2") + .withBody("The ocean is deep") + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + } + + @Test + @Order(4) + @SuppressWarnings("unchecked") + public void queryEmbeddings() { + Exchange result = fluentTemplate.to("direct:query") + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.SIMILARITY_SEARCH) + .withBody("blue sky") + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + + List results = result.getMessage().getBody(List.class); + assertThat(results).isNotEmpty(); + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + from("direct:in") + .to("langchain4j-embeddings:test") + .transformDataType(new DataType("pgvector:embeddings")) + .to(PGVECTOR_URI); + + from("direct:query") + .to("langchain4j-embeddings:test") + .transformDataType(new DataType("pgvector:embeddings")) + .setHeader(PgVectorHeaders.ACTION, constant(PgVectorAction.SIMILARITY_SEARCH)) + .to(PGVECTOR_URI) + .transformDataType(new DataType("pgvector:rag")); + } + }; + } +} diff --git a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc index 4e09e1d24eccb..484534bd3f432 100644 --- a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc +++ b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc @@ -420,6 +420,8 @@ For single-input requests, the component returns a raw `List` embedding v ==== PostgreSQL + pgvector (Recommended) +Using the xref:pgvector-component.adoc[PGVector] component: + [source,yaml] ---- # Index documents in PostgreSQL with pgvector @@ -434,11 +436,42 @@ For single-input requests, the component returns a raw `List` embedding v uri: openai:embeddings parameters: embeddingModel: nomic-embed-text - - setVariable: - name: embedding - simple: "${body.toString()}" + - setHeader: + name: CamelPgVectorAction + constant: UPSERT + - setHeader: + name: CamelPgVectorTextContent + simple: "${variable.text}" + - to: + uri: pgvector:documents + +# Similarity search +- route: + from: + uri: direct:search + steps: + - to: + uri: openai:embeddings + parameters: + embeddingModel: nomic-embed-text + - setHeader: + name: CamelPgVectorAction + constant: SIMILARITY_SEARCH + - setHeader: + name: CamelPgVectorQueryTopK + constant: 5 - to: - uri: sql:INSERT INTO documents (content, embedding) VALUES (:#text, :#embedding::vector) + uri: pgvector:documents +---- + +The pgvector component handles table creation, HNSW indexing, upsert with conflict resolution, and similarity search with configurable distance types (cosine, euclidean, inner product). See the xref:pgvector-component.adoc[PGVector component documentation] for details. + +For custom table schemas, complex queries (joins, CTEs), or integration with existing PostgreSQL tables, you can use `camel-sql` directly with the pgvector extension: + +[source,yaml] +---- +- to: + uri: sql:INSERT INTO documents (content, embedding) VALUES (:#text, :#embedding::vector) ---- ==== Alternative: Dedicated Vector Databases diff --git a/components/camel-ai/camel-pgvector/pom.xml b/components/camel-ai/camel-pgvector/pom.xml new file mode 100644 index 0000000000000..b02e9208d361a --- /dev/null +++ b/components/camel-ai/camel-pgvector/pom.xml @@ -0,0 +1,90 @@ + + + + 4.0.0 + + + camel-ai-parent + org.apache.camel + 4.19.0-SNAPSHOT + + + camel-pgvector + jar + Camel :: AI :: PGVector + Camel PGVector support + + + true + 4 + + + + + org.apache.camel + camel-support + + + + dev.langchain4j + langchain4j-core + ${langchain4j-version} + + + + org.postgresql + postgresql + ${pgjdbc-driver-version} + + + + com.pgvector + pgvector + ${pgvector-version} + + + + + org.apache.camel + camel-test-infra-postgres + ${project.version} + test + + + + + org.apache.camel + camel-test-junit6 + test + + + org.junit.jupiter + junit-jupiter + test + + + + org.assertj + assertj-core + test + + + + diff --git a/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorComponentConfigurer.java b/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorComponentConfigurer.java new file mode 100644 index 0000000000000..49501a70110a5 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorComponentConfigurer.java @@ -0,0 +1,87 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.pgvector; + +import javax.annotation.processing.Generated; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.ExtendedPropertyConfigurerGetter; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.spi.ConfigurerStrategy; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.support.component.PropertyConfigurerSupport; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.EndpointSchemaGeneratorMojo") +@SuppressWarnings("unchecked") +public class PgVectorComponentConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + private org.apache.camel.component.pgvector.PgVectorConfiguration getOrCreateConfiguration(PgVectorComponent target) { + if (target.getConfiguration() == null) { + target.setConfiguration(new org.apache.camel.component.pgvector.PgVectorConfiguration()); + } + return target.getConfiguration(); + } + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + PgVectorComponent target = (PgVectorComponent) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "autowiredenabled": + case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; + case "configuration": target.setConfiguration(property(camelContext, org.apache.camel.component.pgvector.PgVectorConfiguration.class, value)); return true; + case "datasource": + case "dataSource": getOrCreateConfiguration(target).setDataSource(property(camelContext, javax.sql.DataSource.class, value)); return true; + case "dimension": getOrCreateConfiguration(target).setDimension(property(camelContext, int.class, value)); return true; + case "distancetype": + case "distanceType": getOrCreateConfiguration(target).setDistanceType(property(camelContext, org.apache.camel.component.pgvector.PgVectorDistanceType.class, value)); return true; + case "lazystartproducer": + case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; + default: return false; + } + } + + @Override + public String[] getAutowiredNames() { + return new String[]{"dataSource"}; + } + + @Override + public Class getOptionType(String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "autowiredenabled": + case "autowiredEnabled": return boolean.class; + case "configuration": return org.apache.camel.component.pgvector.PgVectorConfiguration.class; + case "datasource": + case "dataSource": return javax.sql.DataSource.class; + case "dimension": return int.class; + case "distancetype": + case "distanceType": return org.apache.camel.component.pgvector.PgVectorDistanceType.class; + case "lazystartproducer": + case "lazyStartProducer": return boolean.class; + default: return null; + } + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + PgVectorComponent target = (PgVectorComponent) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "autowiredenabled": + case "autowiredEnabled": return target.isAutowiredEnabled(); + case "configuration": return target.getConfiguration(); + case "datasource": + case "dataSource": return getOrCreateConfiguration(target).getDataSource(); + case "dimension": return getOrCreateConfiguration(target).getDimension(); + case "distancetype": + case "distanceType": return getOrCreateConfiguration(target).getDistanceType(); + case "lazystartproducer": + case "lazyStartProducer": return target.isLazyStartProducer(); + default: return null; + } + } +} + diff --git a/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorConfigurationConfigurer.java b/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorConfigurationConfigurer.java new file mode 100644 index 0000000000000..48387e1f34519 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorConfigurationConfigurer.java @@ -0,0 +1,60 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.pgvector; + +import javax.annotation.processing.Generated; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.ExtendedPropertyConfigurerGetter; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.spi.ConfigurerStrategy; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.component.pgvector.PgVectorConfiguration; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo") +@SuppressWarnings("unchecked") +public class PgVectorConfigurationConfigurer extends org.apache.camel.support.component.PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + org.apache.camel.component.pgvector.PgVectorConfiguration target = (org.apache.camel.component.pgvector.PgVectorConfiguration) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "datasource": + case "dataSource": target.setDataSource(property(camelContext, javax.sql.DataSource.class, value)); return true; + case "dimension": target.setDimension(property(camelContext, int.class, value)); return true; + case "distancetype": + case "distanceType": target.setDistanceType(property(camelContext, org.apache.camel.component.pgvector.PgVectorDistanceType.class, value)); return true; + default: return false; + } + } + + @Override + public Class getOptionType(String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "datasource": + case "dataSource": return javax.sql.DataSource.class; + case "dimension": return int.class; + case "distancetype": + case "distanceType": return org.apache.camel.component.pgvector.PgVectorDistanceType.class; + default: return null; + } + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + org.apache.camel.component.pgvector.PgVectorConfiguration target = (org.apache.camel.component.pgvector.PgVectorConfiguration) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "datasource": + case "dataSource": return target.getDataSource(); + case "dimension": return target.getDimension(); + case "distancetype": + case "distanceType": return target.getDistanceType(); + default: return null; + } + } +} + diff --git a/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorEndpointConfigurer.java b/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorEndpointConfigurer.java new file mode 100644 index 0000000000000..1e3f50746b82b --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorEndpointConfigurer.java @@ -0,0 +1,71 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.pgvector; + +import javax.annotation.processing.Generated; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.ExtendedPropertyConfigurerGetter; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.spi.ConfigurerStrategy; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.support.component.PropertyConfigurerSupport; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.EndpointSchemaGeneratorMojo") +@SuppressWarnings("unchecked") +public class PgVectorEndpointConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + PgVectorEndpoint target = (PgVectorEndpoint) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "datasource": + case "dataSource": target.getConfiguration().setDataSource(property(camelContext, javax.sql.DataSource.class, value)); return true; + case "dimension": target.getConfiguration().setDimension(property(camelContext, int.class, value)); return true; + case "distancetype": + case "distanceType": target.getConfiguration().setDistanceType(property(camelContext, org.apache.camel.component.pgvector.PgVectorDistanceType.class, value)); return true; + case "lazystartproducer": + case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; + default: return false; + } + } + + @Override + public String[] getAutowiredNames() { + return new String[]{"dataSource"}; + } + + @Override + public Class getOptionType(String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "datasource": + case "dataSource": return javax.sql.DataSource.class; + case "dimension": return int.class; + case "distancetype": + case "distanceType": return org.apache.camel.component.pgvector.PgVectorDistanceType.class; + case "lazystartproducer": + case "lazyStartProducer": return boolean.class; + default: return null; + } + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + PgVectorEndpoint target = (PgVectorEndpoint) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "datasource": + case "dataSource": return target.getConfiguration().getDataSource(); + case "dimension": return target.getConfiguration().getDimension(); + case "distancetype": + case "distanceType": return target.getConfiguration().getDistanceType(); + case "lazystartproducer": + case "lazyStartProducer": return target.isLazyStartProducer(); + default: return null; + } + } +} + diff --git a/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorEndpointUriFactory.java b/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorEndpointUriFactory.java new file mode 100644 index 0000000000000..c41db64d0fcbe --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/java/org/apache/camel/component/pgvector/PgVectorEndpointUriFactory.java @@ -0,0 +1,74 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.pgvector; + +import javax.annotation.processing.Generated; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.camel.spi.EndpointUriFactory; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.GenerateEndpointUriFactoryMojo") +public class PgVectorEndpointUriFactory extends org.apache.camel.support.component.EndpointUriFactorySupport implements EndpointUriFactory { + + private static final String BASE = ":collection"; + + private static final Set PROPERTY_NAMES; + private static final Set SECRET_PROPERTY_NAMES; + private static final Map MULTI_VALUE_PREFIXES; + static { + Set props = new HashSet<>(5); + props.add("collection"); + props.add("dataSource"); + props.add("dimension"); + props.add("distanceType"); + props.add("lazyStartProducer"); + PROPERTY_NAMES = Collections.unmodifiableSet(props); + SECRET_PROPERTY_NAMES = Collections.emptySet(); + MULTI_VALUE_PREFIXES = Collections.emptyMap(); + } + + @Override + public boolean isEnabled(String scheme) { + return "pgvector".equals(scheme); + } + + @Override + public String buildUri(String scheme, Map properties, boolean encode) throws URISyntaxException { + String syntax = scheme + BASE; + String uri = syntax; + + Map copy = new HashMap<>(properties); + + uri = buildPathParameter(syntax, uri, "collection", null, true, copy); + uri = buildQueryParameters(uri, copy, encode); + return uri; + } + + @Override + public Set propertyNames() { + return PROPERTY_NAMES; + } + + @Override + public Set secretPropertyNames() { + return SECRET_PROPERTY_NAMES; + } + + @Override + public Map multiValuePrefixes() { + return MULTI_VALUE_PREFIXES; + } + + @Override + public boolean isLenientProperties() { + return false; + } +} + diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/org/apache/camel/component/pgvector/pgvector.json b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/org/apache/camel/component/pgvector/pgvector.json new file mode 100644 index 0000000000000..163451d430669 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/org/apache/camel/component/pgvector/pgvector.json @@ -0,0 +1,50 @@ +{ + "component": { + "kind": "component", + "name": "pgvector", + "title": "PGVector", + "description": "Perform operations on the PostgreSQL pgvector Vector Database.", + "deprecated": false, + "firstVersion": "4.19.0", + "label": "database,ai", + "javaType": "org.apache.camel.component.pgvector.PgVectorComponent", + "supportLevel": "Preview", + "groupId": "org.apache.camel", + "artifactId": "camel-pgvector", + "version": "4.19.0-SNAPSHOT", + "scheme": "pgvector", + "extendsScheme": "", + "syntax": "pgvector:collection", + "async": false, + "api": false, + "consumerOnly": false, + "producerOnly": true, + "lenientProperties": false, + "browsable": false, + "remote": true + }, + "componentProperties": { + "configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.pgvector.PgVectorConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The configuration;" }, + "dataSource": { "index": 1, "kind": "property", "displayName": "Data Source", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "javax.sql.DataSource", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The DataSource to use for connecting to the PostgreSQL database with pgvector extension." }, + "dimension": { "index": 2, "kind": "property", "displayName": "Dimension", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 384, "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The dimension of the vectors to store." }, + "distanceType": { "index": 3, "kind": "property", "displayName": "Distance Type", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.pgvector.PgVectorDistanceType", "enum": [ "COSINE", "EUCLIDEAN", "INNER_PRODUCT" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "COSINE", "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The distance type to use for similarity search." }, + "lazyStartProducer": { "index": 4, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, + "autowiredEnabled": { "index": 5, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc." } + }, + "headers": { + "CamelPgVectorAction": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "enum": [ "CREATE_TABLE", "CREATE_INDEX", "DROP_TABLE", "UPSERT", "DELETE", "SIMILARITY_SEARCH" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The action to be performed.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#ACTION" }, + "CamelPgVectorRecordId": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The id of the vector record.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#RECORD_ID" }, + "CamelPgVectorQueryTopK": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "3", "description": "The maximum number of results to return for similarity search.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#QUERY_TOP_K" }, + "CamelPgVectorTextContent": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The text content to store alongside the vector embedding.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#TEXT_CONTENT" }, + "CamelPgVectorMetadata": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The metadata associated with the vector record, stored as JSON.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#METADATA" }, + "CamelPgVectorFilter": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Filter condition for similarity search. Applied as a SQL WHERE clause on the text_content and metadata columns. Supports parameterized queries using placeholders with values provided via the CamelPgVectorFilterParams header. WARNING: When not using parameterized queries, the filter value is appended directly as SQL. Never use untrusted input as the filter value without parameterization, as this could lead to SQL injection.", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#FILTER" }, + "CamelPgVectorFilterParams": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Parameter values for parameterized filter queries. Use with placeholders in the CamelPgVectorFilter header. Example: filter = 'text_content LIKE AND metadata::jsonb-'category' = ' with filterParams = List.of(%hello%, science).", "constantName": "org.apache.camel.component.pgvector.PgVectorHeaders#FILTER_PARAMS" } + }, + "properties": { + "collection": { "index": 0, "kind": "path", "displayName": "Collection", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The collection (table) name" }, + "dataSource": { "index": 1, "kind": "parameter", "displayName": "Data Source", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "javax.sql.DataSource", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The DataSource to use for connecting to the PostgreSQL database with pgvector extension." }, + "dimension": { "index": 2, "kind": "parameter", "displayName": "Dimension", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 384, "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The dimension of the vectors to store." }, + "distanceType": { "index": 3, "kind": "parameter", "displayName": "Distance Type", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.pgvector.PgVectorDistanceType", "enum": [ "COSINE", "EUCLIDEAN", "INNER_PRODUCT" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "COSINE", "configurationClass": "org.apache.camel.component.pgvector.PgVectorConfiguration", "configurationField": "configuration", "description": "The distance type to use for similarity search." }, + "lazyStartProducer": { "index": 4, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." } + } +} diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/component.properties b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/component.properties new file mode 100644 index 0000000000000..cad7e0ed66910 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/component.properties @@ -0,0 +1,7 @@ +# Generated by camel build tools - do NOT edit this file! +components=pgvector +groupId=org.apache.camel +artifactId=camel-pgvector +version=4.19.0-SNAPSHOT +projectName=Camel :: AI :: PGVector +projectDescription=Camel PGVector support diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/component/pgvector b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/component/pgvector new file mode 100644 index 0000000000000..3945951638c5b --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/component/pgvector @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.pgvector.PgVectorComponent diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.pgvector.PgVectorConfiguration b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.pgvector.PgVectorConfiguration new file mode 100644 index 0000000000000..cf12de7c025d5 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.pgvector.PgVectorConfiguration @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.pgvector.PgVectorConfigurationConfigurer diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/configurer/pgvector-component b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/configurer/pgvector-component new file mode 100644 index 0000000000000..fd431f002970f --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/configurer/pgvector-component @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.pgvector.PgVectorComponentConfigurer diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/configurer/pgvector-endpoint b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/configurer/pgvector-endpoint new file mode 100644 index 0000000000000..483f077e2040f --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/configurer/pgvector-endpoint @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.pgvector.PgVectorEndpointConfigurer diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer.properties b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer.properties new file mode 100644 index 0000000000000..27a4f45b1c785 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer.properties @@ -0,0 +1,7 @@ +# Generated by camel build tools - do NOT edit this file! +transformers=pgvector:embeddings pgvector:rag +groupId=org.apache.camel +artifactId=camel-pgvector +version=4.19.0-SNAPSHOT +projectName=Camel :: AI :: PGVector +projectDescription=Camel PGVector support diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-embeddings b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-embeddings new file mode 100644 index 0000000000000..4b6963457fb08 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-embeddings @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.pgvector.transform.PgVectorEmbeddingsDataTypeTransformer diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-embeddings.json b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-embeddings.json new file mode 100644 index 0000000000000..2a5efd26fd33d --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-embeddings.json @@ -0,0 +1,14 @@ +{ + "transformer": { + "kind": "transformer", + "name": "pgvector:embeddings", + "title": "Pgvector (Embeddings)", + "description": "Prepares the message to become an object writable by PgVector component", + "deprecated": false, + "javaType": "org.apache.camel.component.pgvector.transform.PgVectorEmbeddingsDataTypeTransformer", + "groupId": "org.apache.camel", + "artifactId": "camel-pgvector", + "version": "4.19.0-SNAPSHOT" + } +} + diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-rag b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-rag new file mode 100644 index 0000000000000..8ddcd235af2a5 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-rag @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.pgvector.transform.PgVectorReverseEmbeddingsDataTypeTransformer diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-rag.json b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-rag.json new file mode 100644 index 0000000000000..bde0480f57203 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/transformer/pgvector-rag.json @@ -0,0 +1,14 @@ +{ + "transformer": { + "kind": "transformer", + "name": "pgvector:rag", + "title": "Pgvector (Rag)", + "description": "Prepares the PgVector similarity search results to become a List of String for LangChain4j RAG", + "deprecated": false, + "javaType": "org.apache.camel.component.pgvector.transform.PgVectorReverseEmbeddingsDataTypeTransformer", + "groupId": "org.apache.camel", + "artifactId": "camel-pgvector", + "version": "4.19.0-SNAPSHOT" + } +} + diff --git a/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/urifactory/pgvector-endpoint b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/urifactory/pgvector-endpoint new file mode 100644 index 0000000000000..a9f8e7a6c555d --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/generated/resources/META-INF/services/org/apache/camel/urifactory/pgvector-endpoint @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.pgvector.PgVectorEndpointUriFactory diff --git a/components/camel-ai/camel-pgvector/src/main/docs/pgvector-component.adoc b/components/camel-ai/camel-pgvector/src/main/docs/pgvector-component.adoc new file mode 100644 index 0000000000000..6048cd9f37fc5 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/main/docs/pgvector-component.adoc @@ -0,0 +1,206 @@ += PGVector Component +:doctitle: PGVector +:shortname: pgvector +:artifactid: camel-pgvector +:description: Perform operations on the PostgreSQL pgvector Vector Database. +:since: 4.19 +:supportlevel: Preview +:tabs-sync-option: +:component-header: Only producer is supported +//Manually maintained attributes +:group: AI + +*Since Camel {since}* + +*{component-header}* + +The PGVector Component provides support for interacting with https://github.com/pgvector/pgvector[pgvector], +the open-source vector similarity search extension for PostgreSQL. + +== URI format + +---- +pgvector:collection[?options] +---- + +Where *collection* represents the table name used to store vectors in the PostgreSQL database. + +== Configuring the DataSource + +A `javax.sql.DataSource` must be provided. It is recommended to use a connection pooling DataSource +(such as HikariCP) for production deployments. The DataSource can be set on the component or endpoint +configuration, or autowired from the registry. + +== Actions + +The following actions are supported via the `CamelPgVectorAction` header: + +- `CREATE_TABLE` - Creates the pgvector extension and a table with columns: id, text_content, metadata, embedding +- `CREATE_INDEX` - Creates an HNSW index on the embedding column for faster approximate nearest neighbor search. The index uses the distance type configured on the endpoint (default: COSINE) +- `DROP_TABLE` - Drops the table +- `UPSERT` - Inserts or updates a vector record. The body must be a `List`. Set `CamelPgVectorRecordId` for the ID (auto-generated UUID if not set), `CamelPgVectorTextContent` for text, and `CamelPgVectorMetadata` for metadata +- `DELETE` - Deletes a record by `CamelPgVectorRecordId` +- `SIMILARITY_SEARCH` - Searches for similar vectors. The body must be a `List` query vector. Set `CamelPgVectorQueryTopK` for max results (default 3). Optionally set `CamelPgVectorFilter` with a SQL WHERE clause to filter results (e.g., `text_content LIKE '%hello%'`). Returns a `List>` with keys: id, text_content, metadata, distance + +== Defaults + +- *dimension*: `384` (matches common embedding models like `all-MiniLM-L6-v2`) +- *distanceType*: `COSINE` (other options: `EUCLIDEAN`, `INNER_PRODUCT`) + +== Parameterized Filters + +When using the `SIMILARITY_SEARCH` action, you can filter results using a SQL WHERE clause via the +`CamelPgVectorFilter` header. For safe handling of dynamic values, use parameterized queries with `?` +placeholders and provide values via the `CamelPgVectorFilterParams` header: + +[source,java] +---- +from("direct:search") + .setHeader(PgVectorHeaders.ACTION).constant(PgVectorAction.SIMILARITY_SEARCH) + .setHeader(PgVectorHeaders.FILTER).constant("text_content LIKE ? AND metadata::jsonb->>'category' = ?") + .setHeader(PgVectorHeaders.FILTER_PARAMS).constant(List.of("%hello%", "science")) + .to("pgvector:documents"); +---- + +== Security + +The `CamelPgVectorFilter` header value is appended directly as a SQL WHERE clause. When using +static, developer-controlled filter expressions this is safe. However, *never pass untrusted user +input directly as the filter value* without using parameterized queries (`?` placeholders with +`CamelPgVectorFilterParams`), as this could lead to SQL injection. + +== OpenAI Integration + +The component works directly with the xref:openai-component.adoc[OpenAI] component for embedding generation. +The OpenAI embeddings endpoint returns a `List`, which is exactly the body format expected by the pgvector UPSERT and SIMILARITY_SEARCH actions. + +[tabs] +==== +Java:: ++ +[source,java] +---- +// Index a document +from("direct:index") + .setVariable("text", body()) + .to("openai:embeddings?embeddingModel=nomic-embed-text") + .setHeader(PgVectorHeaders.ACTION).constant(PgVectorAction.UPSERT) + .setHeader(PgVectorHeaders.TEXT_CONTENT).variable("text") + .to("pgvector:documents"); + +// Similarity search +from("direct:search") + .to("openai:embeddings?embeddingModel=nomic-embed-text") + .setHeader(PgVectorHeaders.ACTION).constant(PgVectorAction.SIMILARITY_SEARCH) + .setHeader(PgVectorHeaders.QUERY_TOP_K).constant(5) + .to("pgvector:documents"); +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:index + steps: + - setVariable: + name: text + simple: "${body}" + - to: + uri: openai:embeddings + parameters: + embeddingModel: nomic-embed-text + - setHeader: + name: CamelPgVectorAction + constant: UPSERT + - setHeader: + name: CamelPgVectorTextContent + simple: "${variable.text}" + - to: pgvector:documents + +- route: + from: + uri: direct:search + steps: + - to: + uri: openai:embeddings + parameters: + embeddingModel: nomic-embed-text + - setHeader: + name: CamelPgVectorAction + constant: SIMILARITY_SEARCH + - setHeader: + name: CamelPgVectorQueryTopK + constant: 5 + - to: pgvector:documents +---- +==== + +== LangChain4j Integration + +This component provides data type transformers for xref:langchain4j-embeddings-component.adoc[LangChain4j Embeddings] integration: + +- `pgvector:embeddings` - Transforms LangChain4j embedding output into a format suitable for the PGVector UPSERT action +- `pgvector:rag` - Transforms similarity search results into a `List` for RAG pipelines + +[tabs] +==== +Java:: ++ +[source,java] +---- +// Store embeddings +from("direct:store") + .to("langchain4j-embeddings:embed") + .setHeader(PgVectorHeaders.ACTION).constant(PgVectorAction.UPSERT) + .transformDataType(new DataType("pgvector:embeddings")) + .to("pgvector:myCollection"); + +// Similarity search for RAG +from("direct:search") + .to("langchain4j-embeddings:embed") + .transformDataType(new DataType("pgvector:embeddings")) + .setHeader(PgVectorHeaders.ACTION, constant(PgVectorAction.SIMILARITY_SEARCH)) + .to("pgvector:myCollection") + .transformDataType(new DataType("pgvector:rag")); +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: "direct:store" + steps: + - to: "langchain4j-embeddings:embed" + - setHeader: + name: CamelPgVectorAction + constant: UPSERT + - transform: + dataType: "pgvector:embeddings" + - to: "pgvector:myCollection" + +- route: + from: + uri: "direct:search" + steps: + - to: "langchain4j-embeddings:embed" + - transform: + dataType: "pgvector:embeddings" + - setHeader: + name: CamelPgVectorAction + constant: SIMILARITY_SEARCH + - to: "pgvector:myCollection" + - transform: + dataType: "pgvector:rag" +---- +==== + + +// component options: START +include::partial$component-configure-options.adoc[] +include::partial$component-endpoint-options.adoc[] +include::partial$component-endpoint-headers.adoc[] +// component options: END diff --git a/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVector.java b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVector.java new file mode 100644 index 0000000000000..0024a9375b6af --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVector.java @@ -0,0 +1,24 @@ +/* + * 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.pgvector; + +public class PgVector { + public static final String SCHEME = "pgvector"; + + private PgVector() { + } +} diff --git a/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorAction.java b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorAction.java new file mode 100644 index 0000000000000..bd2703b6d09e3 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorAction.java @@ -0,0 +1,26 @@ +/* + * 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.pgvector; + +public enum PgVectorAction { + CREATE_TABLE, + CREATE_INDEX, + DROP_TABLE, + UPSERT, + DELETE, + SIMILARITY_SEARCH +} diff --git a/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorComponent.java b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorComponent.java new file mode 100644 index 0000000000000..363050f9d4ca1 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorComponent.java @@ -0,0 +1,68 @@ +/* + * 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.pgvector; + +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.Endpoint; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.annotations.Component; +import org.apache.camel.support.DefaultComponent; + +@Component(PgVector.SCHEME) +public class PgVectorComponent extends DefaultComponent { + + @Metadata + private PgVectorConfiguration configuration; + + public PgVectorComponent() { + this(null); + } + + public PgVectorComponent(CamelContext context) { + super(context); + + this.configuration = new PgVectorConfiguration(); + } + + public PgVectorConfiguration getConfiguration() { + return configuration; + } + + /** + * The configuration; + */ + public void setConfiguration(PgVectorConfiguration configuration) { + this.configuration = configuration; + } + + @Override + protected Endpoint createEndpoint( + String uri, + String remaining, + Map parameters) + throws Exception { + + PgVectorConfiguration configuration = this.configuration.copy(); + + PgVectorEndpoint endpoint = new PgVectorEndpoint(uri, this, remaining, configuration); + setProperties(endpoint, parameters); + + return endpoint; + } +} diff --git a/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorConfiguration.java b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorConfiguration.java new file mode 100644 index 0000000000000..aefac8f64e2e3 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorConfiguration.java @@ -0,0 +1,91 @@ +/* + * 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.pgvector; + +import javax.sql.DataSource; + +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.spi.Configurer; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriParams; + +@Configurer +@UriParams +public class PgVectorConfiguration implements Cloneable { + + @Metadata(autowired = true, + description = "The DataSource to use for connecting to the PostgreSQL database with pgvector extension.") + @UriParam + private DataSource dataSource; + + @Metadata(label = "producer", + description = "The dimension of the vectors to store.") + @UriParam(defaultValue = "384") + private int dimension = 384; + + @Metadata(label = "producer", + description = "The distance type to use for similarity search.") + @UriParam(defaultValue = "COSINE") + private PgVectorDistanceType distanceType = PgVectorDistanceType.COSINE; + + public DataSource getDataSource() { + return dataSource; + } + + /** + * The DataSource to use for connecting to the PostgreSQL database with pgvector extension. + */ + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + + public int getDimension() { + return dimension; + } + + /** + * The dimension of the vectors to store. + */ + public void setDimension(int dimension) { + this.dimension = dimension; + } + + public PgVectorDistanceType getDistanceType() { + return distanceType; + } + + /** + * The distance type to use for similarity search. Supported values: COSINE, EUCLIDEAN, INNER_PRODUCT. + */ + public void setDistanceType(PgVectorDistanceType distanceType) { + this.distanceType = distanceType; + } + + // ************************ + // + // Clone + // + // ************************ + public PgVectorConfiguration copy() { + try { + return (PgVectorConfiguration) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeCamelException(e); + } + } +} diff --git a/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorDistanceType.java b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorDistanceType.java new file mode 100644 index 0000000000000..471220b0a5330 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorDistanceType.java @@ -0,0 +1,40 @@ +/* + * 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.pgvector; + +public enum PgVectorDistanceType { + + COSINE("<=>", "vector_cosine_ops"), + EUCLIDEAN("<->", "vector_l2_ops"), + INNER_PRODUCT("<#>", "vector_ip_ops"); + + private final String operator; + private final String indexOpsClass; + + PgVectorDistanceType(String operator, String indexOpsClass) { + this.operator = operator; + this.indexOpsClass = indexOpsClass; + } + + public String getOperator() { + return operator; + } + + public String getIndexOpsClass() { + return indexOpsClass; + } +} diff --git a/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorEndpoint.java b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorEndpoint.java new file mode 100644 index 0000000000000..a22f811cd0cb2 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorEndpoint.java @@ -0,0 +1,97 @@ +/* + * 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.pgvector; + +import javax.sql.DataSource; + +import org.apache.camel.Category; +import org.apache.camel.Component; +import org.apache.camel.Consumer; +import org.apache.camel.Processor; +import org.apache.camel.Producer; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriPath; +import org.apache.camel.support.DefaultEndpoint; + +/** + * Perform operations on the PostgreSQL pgvector Vector Database. + */ +@UriEndpoint( + firstVersion = "4.19.0", + scheme = PgVector.SCHEME, + title = "PGVector", + syntax = "pgvector:collection", + producerOnly = true, + category = { + Category.DATABASE, + Category.AI + }, + headersClass = PgVectorHeaders.class) +public class PgVectorEndpoint extends DefaultEndpoint { + + @Metadata(required = true) + @UriPath(description = "The collection (table) name") + private final String collection; + + @UriParam + private PgVectorConfiguration configuration; + + public PgVectorEndpoint( + String endpointUri, + Component component, + String collection, + PgVectorConfiguration configuration) { + + super(endpointUri, component); + + this.collection = collection; + this.configuration = configuration; + } + + public PgVectorConfiguration getConfiguration() { + return configuration; + } + + public String getCollection() { + return collection; + } + + public DataSource getDataSource() { + return this.configuration.getDataSource(); + } + + @Override + public Producer createProducer() throws Exception { + return new PgVectorProducer(this); + } + + @Override + public Consumer createConsumer(Processor processor) throws Exception { + throw new UnsupportedOperationException("Consumer is not implemented for this component"); + } + + @Override + public void doStart() throws Exception { + super.doStart(); + + if (configuration.getDataSource() == null) { + throw new IllegalArgumentException("DataSource must be configured on the pgvector component or endpoint"); + } + } +} diff --git a/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorHeaders.java b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorHeaders.java new file mode 100644 index 0000000000000..627020c3f9220 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorHeaders.java @@ -0,0 +1,64 @@ +/* + * 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.pgvector; + +import org.apache.camel.spi.Metadata; + +public final class PgVectorHeaders { + + private PgVectorHeaders() { + } + + @Metadata(description = "The action to be performed.", + javaType = "String", + enums = "CREATE_TABLE,CREATE_INDEX,DROP_TABLE,UPSERT,DELETE,SIMILARITY_SEARCH") + public static final String ACTION = "CamelPgVectorAction"; + + @Metadata(description = "The id of the vector record.", + javaType = "String") + public static final String RECORD_ID = "CamelPgVectorRecordId"; + + @Metadata(description = "The maximum number of results to return for similarity search.", + javaType = "Integer", defaultValue = "3") + public static final String QUERY_TOP_K = "CamelPgVectorQueryTopK"; + + @Metadata(description = "The text content to store alongside the vector embedding.", + javaType = "String") + public static final String TEXT_CONTENT = "CamelPgVectorTextContent"; + + @Metadata(description = "The metadata associated with the vector record, stored as JSON.", + javaType = "String") + public static final String METADATA = "CamelPgVectorMetadata"; + + @Metadata(description = "Filter condition for similarity search." + + " Applied as a SQL WHERE clause on the text_content and metadata columns." + + " Supports parameterized queries using ? placeholders with values provided" + + " via the CamelPgVectorFilterParams header." + + " WARNING: When not using parameterized queries, the filter value is appended" + + " directly as SQL. Never use untrusted input as the filter value without" + + " parameterization, as this could lead to SQL injection.", + javaType = "String") + public static final String FILTER = "CamelPgVectorFilter"; + + @Metadata(description = "Parameter values for parameterized filter queries." + + " Use with ? placeholders in the CamelPgVectorFilter header." + + " Example: filter = 'text_content LIKE ? AND metadata::jsonb->>'category' = ?'" + + " with filterParams = List.of(\"%hello%\", \"science\").", + javaType = "java.util.List") + public static final String FILTER_PARAMS = "CamelPgVectorFilterParams"; + +} diff --git a/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorProducer.java b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorProducer.java new file mode 100644 index 0000000000000..e8cbb80933774 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/PgVectorProducer.java @@ -0,0 +1,273 @@ +/* + * 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.pgvector; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import com.pgvector.PGvector; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.NoSuchHeaderException; +import org.apache.camel.support.DefaultProducer; +import org.apache.camel.support.ExchangeHelper; + +public class PgVectorProducer extends DefaultProducer { + + public PgVectorProducer(PgVectorEndpoint endpoint) { + super(endpoint); + } + + @Override + public PgVectorEndpoint getEndpoint() { + return (PgVectorEndpoint) super.getEndpoint(); + } + + @Override + public void process(Exchange exchange) { + final Message in = exchange.getMessage(); + final PgVectorAction action = in.getHeader(PgVectorHeaders.ACTION, PgVectorAction.class); + + try { + if (action == null) { + throw new NoSuchHeaderException("The action is a required header", exchange, PgVectorHeaders.ACTION); + } + + switch (action) { + case CREATE_TABLE: + createTable(exchange); + break; + case CREATE_INDEX: + createIndex(exchange); + break; + case DROP_TABLE: + dropTable(exchange); + break; + case UPSERT: + upsert(exchange); + break; + case DELETE: + delete(exchange); + break; + case SIMILARITY_SEARCH: + similaritySearch(exchange); + break; + default: + throw new UnsupportedOperationException("Unsupported action: " + action.name()); + } + } catch (Exception e) { + exchange.setException(e); + } + } + + // *************************************** + // + // Actions + // + // *************************************** + + private void createTable(Exchange exchange) throws SQLException { + String tableName = getEndpoint().getCollection(); + int dimension = getEndpoint().getConfiguration().getDimension(); + + try (Connection conn = getEndpoint().getDataSource().getConnection()) { + try (Statement stmt = conn.createStatement()) { + stmt.executeUpdate("CREATE EXTENSION IF NOT EXISTS vector"); + } + + String sql = String.format( + "CREATE TABLE IF NOT EXISTS %s (" + + "id VARCHAR(36) PRIMARY KEY, " + + "text_content TEXT, " + + "metadata TEXT, " + + "embedding vector(%d))", + sanitizeIdentifier(tableName), dimension); + try (Statement stmt = conn.createStatement()) { + stmt.executeUpdate(sql); + } + + exchange.getMessage().setBody(true); + } + } + + private void createIndex(Exchange exchange) throws SQLException { + String tableName = getEndpoint().getCollection(); + PgVectorDistanceType distanceType = getEndpoint().getConfiguration().getDistanceType(); + + String sql = String.format( + "CREATE INDEX IF NOT EXISTS %s_embedding_idx ON %s USING hnsw (embedding %s)", + sanitizeIdentifier(tableName), sanitizeIdentifier(tableName), distanceType.getIndexOpsClass()); + + try (Connection conn = getEndpoint().getDataSource().getConnection()) { + try (Statement stmt = conn.createStatement()) { + stmt.executeUpdate(sql); + } + + exchange.getMessage().setBody(true); + } + } + + private void dropTable(Exchange exchange) throws SQLException { + String tableName = getEndpoint().getCollection(); + + try (Connection conn = getEndpoint().getDataSource().getConnection()) { + String sql = String.format("DROP TABLE IF EXISTS %s", sanitizeIdentifier(tableName)); + try (Statement stmt = conn.createStatement()) { + stmt.executeUpdate(sql); + } + + exchange.getMessage().setBody(true); + } + } + + @SuppressWarnings("unchecked") + private void upsert(Exchange exchange) throws Exception { + final Message in = exchange.getMessage(); + String tableName = getEndpoint().getCollection(); + String id = in.getHeader(PgVectorHeaders.RECORD_ID, () -> UUID.randomUUID().toString(), String.class); + + List vector = in.getMandatoryBody(List.class); + float[] vectorArray = toFloatArray(vector); + + String textContent = in.getHeader(PgVectorHeaders.TEXT_CONTENT, String.class); + String metadata = in.getHeader(PgVectorHeaders.METADATA, String.class); + + String sql = String.format( + "INSERT INTO %s (id, text_content, metadata, embedding) VALUES (?, ?, ?, ?) " + + "ON CONFLICT (id) DO UPDATE SET text_content = EXCLUDED.text_content, " + + "metadata = EXCLUDED.metadata, embedding = EXCLUDED.embedding", + sanitizeIdentifier(tableName)); + + try (Connection conn = getEndpoint().getDataSource().getConnection()) { + PGvector.addVectorType(conn); + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setString(1, id); + pstmt.setString(2, textContent); + pstmt.setString(3, metadata); + pstmt.setObject(4, new PGvector(vectorArray)); + pstmt.executeUpdate(); + } + } + + exchange.getMessage().setBody(id); + } + + private void delete(Exchange exchange) throws Exception { + String tableName = getEndpoint().getCollection(); + String id = ExchangeHelper.getMandatoryHeader(exchange, PgVectorHeaders.RECORD_ID, String.class); + + String sql = String.format("DELETE FROM %s WHERE id = ?", sanitizeIdentifier(tableName)); + + try (Connection conn = getEndpoint().getDataSource().getConnection()) { + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setString(1, id); + int deleted = pstmt.executeUpdate(); + exchange.getMessage().setBody(deleted > 0); + } + } + } + + @SuppressWarnings("unchecked") + private void similaritySearch(Exchange exchange) throws Exception { + final Message in = exchange.getMessage(); + String tableName = getEndpoint().getCollection(); + int topK = in.getHeader(PgVectorHeaders.QUERY_TOP_K, 3, Integer.class); + PgVectorDistanceType distanceType = getEndpoint().getConfiguration().getDistanceType(); + String filter = in.getHeader(PgVectorHeaders.FILTER, String.class); + List filterParams = in.getHeader(PgVectorHeaders.FILTER_PARAMS, List.class); + + List queryVector = in.getMandatoryBody(List.class); + float[] vectorArray = toFloatArray(queryVector); + + String distanceOp = distanceType.getOperator(); + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append(String.format( + "SELECT id, text_content, metadata, embedding %s ? AS distance FROM %s", + distanceOp, sanitizeIdentifier(tableName))); + + if (filter != null && !filter.isBlank()) { + sqlBuilder.append(" WHERE ").append(filter); + } + + sqlBuilder.append(String.format(" ORDER BY embedding %s ? LIMIT ?", distanceOp)); + + try (Connection conn = getEndpoint().getDataSource().getConnection()) { + PGvector.addVectorType(conn); + try (PreparedStatement pstmt = conn.prepareStatement(sqlBuilder.toString())) { + int paramIdx = 1; + PGvector pgVector = new PGvector(vectorArray); + pstmt.setObject(paramIdx++, pgVector); + + // Bind filter parameters if provided + if (filter != null && !filter.isBlank() && filterParams != null) { + for (Object param : filterParams) { + pstmt.setObject(paramIdx++, param); + } + } + + pstmt.setObject(paramIdx++, pgVector); + pstmt.setInt(paramIdx, topK); + + try (ResultSet rs = pstmt.executeQuery()) { + List> results = new ArrayList<>(); + while (rs.next()) { + Map row = new HashMap<>(); + row.put("id", rs.getString("id")); + row.put("text_content", rs.getString("text_content")); + row.put("metadata", rs.getString("metadata")); + row.put("distance", rs.getDouble("distance")); + results.add(row); + } + exchange.getMessage().setBody(results); + } + } + } + } + + // *************************************** + // + // Helpers + // + // *************************************** + + private static float[] toFloatArray(List vector) { + float[] result = new float[vector.size()]; + for (int i = 0; i < vector.size(); i++) { + Float value = vector.get(i); + if (value == null) { + throw new IllegalArgumentException("Vector contains null value at index " + i); + } + result[i] = value; + } + return result; + } + + private static String sanitizeIdentifier(String identifier) { + if (!identifier.matches("[a-zA-Z_][a-zA-Z0-9_]*")) { + throw new IllegalArgumentException("Invalid SQL identifier: " + identifier); + } + return identifier; + } +} diff --git a/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/transform/PgVectorEmbeddingsDataTypeTransformer.java b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/transform/PgVectorEmbeddingsDataTypeTransformer.java new file mode 100644 index 0000000000000..33a815f943d7c --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/transform/PgVectorEmbeddingsDataTypeTransformer.java @@ -0,0 +1,90 @@ +/* + * 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.pgvector.transform; + +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import dev.langchain4j.data.document.Metadata; +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.pgvector.PgVectorHeaders; +import org.apache.camel.spi.DataType; +import org.apache.camel.spi.DataTypeTransformer; +import org.apache.camel.spi.Transformer; + +/** + * Maps a LangChain4j Embeddings to a PgVector upsert message to write an embeddings vector on a PostgreSQL pgvector + * database. + */ +@DataTypeTransformer(name = "pgvector:embeddings", + description = "Prepares the message to become an object writable by PgVector component") +public class PgVectorEmbeddingsDataTypeTransformer extends Transformer { + + @Override + public void transform(Message message, DataType fromType, DataType toType) { + Embedding embedding + = message.getHeader(CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR, Embedding.class); + if (embedding == null) { + throw new IllegalArgumentException( + "Missing embedding vector header '" + CamelLangchain4jAttributes.CAMEL_LANGCHAIN4J_EMBEDDING_VECTOR + + "'. Ensure the langchain4j-embeddings component is called before this transformer."); + } + TextSegment text = message.getBody(TextSegment.class); + + String id = message.getHeader(PgVectorHeaders.RECORD_ID, () -> UUID.randomUUID().toString(), String.class); + message.setHeader(PgVectorHeaders.RECORD_ID, id); + + message.setBody(embedding.vectorAsList()); + + if (text != null) { + message.setHeader(PgVectorHeaders.TEXT_CONTENT, text.text()); + Metadata metadata = text.metadata(); + if (metadata != null && !metadata.toMap().isEmpty()) { + message.setHeader(PgVectorHeaders.METADATA, toJson(metadata.toMap())); + } + } + } + + private static String toJson(Map map) { + return "{" + map.entrySet().stream() + .map(e -> "\"" + escapeJson(String.valueOf(e.getKey())) + "\":" + formatJsonValue(e.getValue())) + .collect(Collectors.joining(",")) + + "}"; + } + + private static String formatJsonValue(Object value) { + if (value == null) { + return "null"; + } + if (value instanceof Number || value instanceof Boolean) { + return String.valueOf(value); + } + return "\"" + escapeJson(String.valueOf(value)) + "\""; + } + + private static String escapeJson(String value) { + return value.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t"); + } +} diff --git a/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/transform/PgVectorReverseEmbeddingsDataTypeTransformer.java b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/transform/PgVectorReverseEmbeddingsDataTypeTransformer.java new file mode 100644 index 0000000000000..76900cdf1993d --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/main/java/org/apache/camel/component/pgvector/transform/PgVectorReverseEmbeddingsDataTypeTransformer.java @@ -0,0 +1,50 @@ +/* + * 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.pgvector.transform; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.camel.Message; +import org.apache.camel.spi.DataType; +import org.apache.camel.spi.DataTypeTransformer; +import org.apache.camel.spi.Transformer; + +/** + * Maps a List of PgVector similarity search results to a List of String for LangChain4j RAG. + */ +@DataTypeTransformer(name = "pgvector:rag", + description = "Prepares the PgVector similarity search results to become a List of String for LangChain4j RAG") +public class PgVectorReverseEmbeddingsDataTypeTransformer extends Transformer { + + @Override + @SuppressWarnings("unchecked") + public void transform(Message message, DataType from, DataType to) throws Exception { + List> results = message.getBody(List.class); + if (results == null) { + throw new IllegalArgumentException( + "Message body must contain similarity search results (List>)"); + } + + List textContents = results.stream() + .map(row -> (String) row.getOrDefault("text_content", "")) + .collect(Collectors.toList()); + + message.setBody(textContents); + } +} diff --git a/components/camel-ai/camel-pgvector/src/test/java/org/apache/camel/component/pgvector/PgVectorComponentIT.java b/components/camel-ai/camel-pgvector/src/test/java/org/apache/camel/component/pgvector/PgVectorComponentIT.java new file mode 100644 index 0000000000000..bc1070638eb00 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/test/java/org/apache/camel/component/pgvector/PgVectorComponentIT.java @@ -0,0 +1,315 @@ +/* + * 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.pgvector; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.NoSuchHeaderException; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.infra.postgres.services.PostgresService; +import org.apache.camel.test.infra.postgres.services.PostgresVectorServiceFactory; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.postgresql.ds.PGSimpleDataSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class PgVectorComponentIT extends CamelTestSupport { + + public static final String PGVECTOR_URI = "pgvector:test_embeddings"; + private static final int DIMENSION = 3; + + @RegisterExtension + static PostgresService POSTGRES = PostgresVectorServiceFactory.createService(); + + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + + PGSimpleDataSource dataSource = new PGSimpleDataSource(); + dataSource.setUrl(POSTGRES.jdbcUrl()); + dataSource.setUser(POSTGRES.userName()); + dataSource.setPassword(POSTGRES.password()); + + PgVectorComponent component = context.getComponent(PgVector.SCHEME, PgVectorComponent.class); + component.getConfiguration().setDataSource(dataSource); + component.getConfiguration().setDimension(DIMENSION); + + return context; + } + + @Test + @Order(1) + public void createTable() { + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.CREATE_TABLE) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(Boolean.class)).isTrue(); + } + + @Test + @Order(2) + public void createIndex() { + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.CREATE_INDEX) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(Boolean.class)).isTrue(); + } + + @Test + @Order(3) + public void upsert() { + List vector = Arrays.asList(0.1f, 0.2f, 0.3f); + + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.UPSERT) + .withHeader(PgVectorHeaders.RECORD_ID, "test-1") + .withHeader(PgVectorHeaders.TEXT_CONTENT, "Hello World") + .withHeader(PgVectorHeaders.METADATA, "{\"category\":\"greeting\"}") + .withBody(vector) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(String.class)).isEqualTo("test-1"); + } + + @Test + @Order(4) + public void upsertUpdate() { + // Update test-1 with new text and vector to verify ON CONFLICT DO UPDATE + List vector = Arrays.asList(0.15f, 0.25f, 0.35f); + + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.UPSERT) + .withHeader(PgVectorHeaders.RECORD_ID, "test-1") + .withHeader(PgVectorHeaders.TEXT_CONTENT, "Hello Updated World") + .withBody(vector) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(String.class)).isEqualTo("test-1"); + } + + @Test + @Order(5) + public void upsertSecond() { + List vector = Arrays.asList(0.4f, 0.5f, 0.6f); + + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.UPSERT) + .withHeader(PgVectorHeaders.RECORD_ID, "test-2") + .withHeader(PgVectorHeaders.TEXT_CONTENT, "Goodbye World") + .withHeader(PgVectorHeaders.METADATA, "{\"category\":\"farewell\"}") + .withBody(vector) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(String.class)).isEqualTo("test-2"); + } + + @Test + @Order(6) + public void upsertAutoId() { + // Verify auto-generated UUID when no RECORD_ID is provided + List vector = Arrays.asList(0.7f, 0.8f, 0.9f); + + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.UPSERT) + .withHeader(PgVectorHeaders.TEXT_CONTENT, "Auto ID record") + .withBody(vector) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + String autoId = result.getMessage().getBody(String.class); + assertThat(autoId).isNotNull(); + // Verify it's a valid UUID format + assertThat(autoId).matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); + } + + @Test + @Order(7) + @SuppressWarnings("unchecked") + public void similaritySearch() { + List queryVector = Arrays.asList(0.1f, 0.2f, 0.3f); + + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.SIMILARITY_SEARCH) + .withHeader(PgVectorHeaders.QUERY_TOP_K, 2) + .withBody(queryVector) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + + List> results = result.getMessage().getBody(List.class); + assertThat(results).isNotEmpty(); + assertThat(results).hasSizeLessThanOrEqualTo(2); + + // The closest vector should be test-1 (updated by upsertUpdate test) + assertThat(results.get(0).get("id")).isEqualTo("test-1"); + assertThat(results.get(0).get("text_content")).isEqualTo("Hello Updated World"); + } + + @Test + @Order(8) + @SuppressWarnings("unchecked") + public void similaritySearchWithFilter() { + List queryVector = Arrays.asList(0.1f, 0.2f, 0.3f); + + // Filter to only match the "farewell" category — should return test-2 even though test-1 is closer + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.SIMILARITY_SEARCH) + .withHeader(PgVectorHeaders.QUERY_TOP_K, 2) + .withHeader(PgVectorHeaders.FILTER, "metadata LIKE '%farewell%'") + .withBody(queryVector) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + + List> results = result.getMessage().getBody(List.class); + assertThat(results).hasSize(1); + assertThat(results.get(0).get("id")).isEqualTo("test-2"); + assertThat(results.get(0).get("text_content")).isEqualTo("Goodbye World"); + } + + @Test + @Order(9) + @SuppressWarnings("unchecked") + public void similaritySearchWithParameterizedFilter() { + List queryVector = Arrays.asList(0.1f, 0.2f, 0.3f); + + // Same filter as above but using parameterized query for safety + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.SIMILARITY_SEARCH) + .withHeader(PgVectorHeaders.QUERY_TOP_K, 2) + .withHeader(PgVectorHeaders.FILTER, "metadata LIKE ?") + .withHeader(PgVectorHeaders.FILTER_PARAMS, List.of("%farewell%")) + .withBody(queryVector) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + + List> results = result.getMessage().getBody(List.class); + assertThat(results).hasSize(1); + assertThat(results.get(0).get("id")).isEqualTo("test-2"); + assertThat(results.get(0).get("text_content")).isEqualTo("Goodbye World"); + } + + @Test + @Order(10) + public void delete() { + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.DELETE) + .withHeader(PgVectorHeaders.RECORD_ID, "test-1") + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(Boolean.class)).isTrue(); + } + + @Test + @Order(11) + @SuppressWarnings("unchecked") + public void searchAfterDelete() { + List queryVector = Arrays.asList(0.1f, 0.2f, 0.3f); + + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.SIMILARITY_SEARCH) + .withHeader(PgVectorHeaders.QUERY_TOP_K, 2) + .withBody(queryVector) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + + List> results = result.getMessage().getBody(List.class); + assertThat(results).hasSize(2); + // test-1 was deleted, so results should be test-2 and auto-ID record + assertThat(results).extracting(r -> r.get("id")).doesNotContain("test-1"); + } + + @Test + @Order(12) + public void missingActionHeader() { + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withBody(Arrays.asList(0.1f, 0.2f, 0.3f)) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isInstanceOf(NoSuchHeaderException.class); + } + + @Test + @Order(13) + public void deleteNonExistentRecord() { + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.DELETE) + .withHeader(PgVectorHeaders.RECORD_ID, "non-existent-id") + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(Boolean.class)).isFalse(); + } + + @Test + @Order(14) + public void dropTable() { + Exchange result = fluentTemplate.to(PGVECTOR_URI) + .withHeader(PgVectorHeaders.ACTION, PgVectorAction.DROP_TABLE) + .request(Exchange.class); + + assertThat(result).isNotNull(); + assertThat(result.getException()).isNull(); + assertThat(result.getMessage().getBody(Boolean.class)).isTrue(); + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + from("direct:in") + .to(PGVECTOR_URI); + } + }; + } +} diff --git a/components/camel-ai/camel-pgvector/src/test/resources/log4j2.properties b/components/camel-ai/camel-pgvector/src/test/resources/log4j2.properties new file mode 100644 index 0000000000000..f325a55302c34 --- /dev/null +++ b/components/camel-ai/camel-pgvector/src/test/resources/log4j2.properties @@ -0,0 +1,30 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +appender.file.type = File +appender.file.name = file +appender.file.fileName = target/camel-pgvector-test.log +appender.file.layout.type = PatternLayout +appender.file.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n + +appender.out.type = Console +appender.out.name = out +appender.out.layout.type = PatternLayout +appender.out.layout.pattern = [%30.30t] %-30.30c{1} %-5p %m%n + +rootLogger.level = INFO +rootLogger.appenderRef.file.ref = file diff --git a/components/camel-ai/pom.xml b/components/camel-ai/pom.xml index b3cc35116113e..d0d2ed952d817 100644 --- a/components/camel-ai/pom.xml +++ b/components/camel-ai/pom.xml @@ -52,6 +52,7 @@ camel-milvus camel-neo4j camel-openai + camel-pgvector camel-pinecone camel-qdrant camel-tensorflow-serving diff --git a/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties b/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties index e9742912148af..6d730df36b5ac 100644 --- a/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties +++ b/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties @@ -295,6 +295,7 @@ paho-mqtt5 pdf pg-replication-slot pgevent +pgvector pinecone platform-http plc4x diff --git a/docs/components/modules/ROOT/examples/json/pgvector.json b/docs/components/modules/ROOT/examples/json/pgvector.json new file mode 120000 index 0000000000000..44847d9815ea6 --- /dev/null +++ b/docs/components/modules/ROOT/examples/json/pgvector.json @@ -0,0 +1 @@ +../../../../../../components/camel-ai/camel-pgvector/src/generated/resources/META-INF/org/apache/camel/component/pgvector/pgvector.json \ No newline at end of file diff --git a/docs/components/modules/ROOT/nav.adoc b/docs/components/modules/ROOT/nav.adoc index cfc430edd163d..887761cd772f6 100644 --- a/docs/components/modules/ROOT/nav.adoc +++ b/docs/components/modules/ROOT/nav.adoc @@ -19,6 +19,7 @@ *** xref:milvus-component.adoc[Milvus] *** xref:neo4j-component.adoc[Neo4j] *** xref:openai-component.adoc[OpenAI] +*** xref:pgvector-component.adoc[PGVector] *** xref:pinecone-component.adoc[Pinecone] *** xref:qdrant-component.adoc[Qdrant] *** xref:spring-ai-chat-component.adoc[Spring AI Chat] diff --git a/docs/components/modules/ROOT/pages/pgvector-component.adoc b/docs/components/modules/ROOT/pages/pgvector-component.adoc new file mode 120000 index 0000000000000..62dcbff37d4ed --- /dev/null +++ b/docs/components/modules/ROOT/pages/pgvector-component.adoc @@ -0,0 +1 @@ +../../../../../components/camel-ai/camel-pgvector/src/main/docs/pgvector-component.adoc \ No newline at end of file diff --git a/docs/gulpfile.js b/docs/gulpfile.js index 39d7dc056961a..4df446b08efca 100644 --- a/docs/gulpfile.js +++ b/docs/gulpfile.js @@ -206,7 +206,9 @@ const sources = { '../core/camel-base/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc', '../core/camel-main/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc', '../components/{*,*/*}/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc', - '../dsl/**/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc', + '../dsl/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc', + '../dsl/*/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc', + '../dsl/*/*/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc', ], destination: 'components/modules/others/pages', keep: [ @@ -326,7 +328,7 @@ const tasks = Array.from(sourcesMap).flatMap(([type, definition]) => { } }) - return gulp.src(source, { ignore: ['**/target/**'] }) + return gulp.src(source, { ignore: ['**/target/**', '**/target'] }) .pipe(filterFn) .pipe( map((file, done) => { @@ -409,7 +411,7 @@ const tasks = Array.from(sourcesMap).flatMap(([type, definition]) => { return done() } - return gulp.src(source, { ignore: ['**/target/**'] }) // asciidoc files + return gulp.src(source, { ignore: ['**/target/**', '**/target'] }) // asciidoc files .pipe(through2.obj(extractExamples)) // extracted example files // symlink links from a fixed directory, i.e. we could link to // the example files from `destination`, that would not work for diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java index e21c19160c095..918c6b6862212 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java @@ -3994,6 +3994,19 @@ static PgReplicationSlotComponentBuilderFactory.PgReplicationSlotComponentBuilde static PgeventComponentBuilderFactory.PgeventComponentBuilder pgevent() { return PgeventComponentBuilderFactory.pgevent(); } + /** + * PGVector (camel-pgvector) + * Perform operations on the PostgreSQL pgvector Vector Database. + * + * Category: database,ai + * Since: 4.19 + * Maven coordinates: org.apache.camel:camel-pgvector + * + * @return the dsl builder + */ + static PgvectorComponentBuilderFactory.PgvectorComponentBuilder pgvector() { + return PgvectorComponentBuilderFactory.pgvector(); + } /** * Pinecone (camel-pinecone) * Perform operations on the Pinecone Vector Database. diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PgvectorComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PgvectorComponentBuilderFactory.java new file mode 100644 index 0000000000000..0ce0c76d964cf --- /dev/null +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PgvectorComponentBuilderFactory.java @@ -0,0 +1,197 @@ +/* Generated by camel build tools - do NOT edit this file! */ +/* + * 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.builder.component.dsl; + +import javax.annotation.processing.Generated; +import org.apache.camel.Component; +import org.apache.camel.builder.component.AbstractComponentBuilder; +import org.apache.camel.builder.component.ComponentBuilder; +import org.apache.camel.component.pgvector.PgVectorComponent; + +/** + * Perform operations on the PostgreSQL pgvector Vector Database. + * + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.ComponentDslMojo") +public interface PgvectorComponentBuilderFactory { + + /** + * PGVector (camel-pgvector) + * Perform operations on the PostgreSQL pgvector Vector Database. + * + * Category: database,ai + * Since: 4.19 + * Maven coordinates: org.apache.camel:camel-pgvector + * + * @return the dsl builder + */ + static PgvectorComponentBuilder pgvector() { + return new PgvectorComponentBuilderImpl(); + } + + /** + * Builder for the PGVector component. + */ + interface PgvectorComponentBuilder extends ComponentBuilder { + + /** + * The configuration;. + * + * The option is a: + * <code>org.apache.camel.component.pgvector.PgVectorConfiguration</code> type. + * + * Group: producer + * + * @param configuration the value to set + * @return the dsl builder + */ + default PgvectorComponentBuilder configuration(org.apache.camel.component.pgvector.PgVectorConfiguration configuration) { + doSetProperty("configuration", configuration); + return this; + } + + /** + * The DataSource to use for connecting to the PostgreSQL database with + * pgvector extension. + * + * The option is a: <code>javax.sql.DataSource</code> type. + * + * Group: producer + * + * @param dataSource the value to set + * @return the dsl builder + */ + default PgvectorComponentBuilder dataSource(javax.sql.DataSource dataSource) { + doSetProperty("dataSource", dataSource); + return this; + } + + + /** + * The dimension of the vectors to store. + * + * The option is a: <code>int</code> type. + * + * Default: 384 + * Group: producer + * + * @param dimension the value to set + * @return the dsl builder + */ + default PgvectorComponentBuilder dimension(int dimension) { + doSetProperty("dimension", dimension); + return this; + } + + + /** + * The distance type to use for similarity search. + * + * The option is a: + * <code>org.apache.camel.component.pgvector.PgVectorDistanceType</code> type. + * + * Default: COSINE + * Group: producer + * + * @param distanceType the value to set + * @return the dsl builder + */ + default PgvectorComponentBuilder distanceType(org.apache.camel.component.pgvector.PgVectorDistanceType distanceType) { + doSetProperty("distanceType", distanceType); + return this; + } + + + /** + * Whether the producer should be started lazy (on the first message). + * By starting lazy you can use this to allow CamelContext and routes to + * startup in situations where a producer may otherwise fail during + * starting and cause the route to fail being started. By deferring this + * startup to be lazy then the startup failure can be handled during + * routing messages via Camel's routing error handlers. Beware that when + * the first message is processed then creating and starting the + * producer may take a little time and prolong the total processing time + * of the processing. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: producer + * + * @param lazyStartProducer the value to set + * @return the dsl builder + */ + default PgvectorComponentBuilder lazyStartProducer(boolean lazyStartProducer) { + doSetProperty("lazyStartProducer", lazyStartProducer); + return this; + } + + + /** + * Whether autowiring is enabled. This is used for automatic autowiring + * options (the option must be marked as autowired) by looking up in the + * registry to find if there is a single instance of matching type, + * which then gets configured on the component. This can be used for + * automatic configuring JDBC data sources, JMS connection factories, + * AWS Clients, etc. + * + * The option is a: <code>boolean</code> type. + * + * Default: true + * Group: advanced + * + * @param autowiredEnabled the value to set + * @return the dsl builder + */ + default PgvectorComponentBuilder autowiredEnabled(boolean autowiredEnabled) { + doSetProperty("autowiredEnabled", autowiredEnabled); + return this; + } + } + + class PgvectorComponentBuilderImpl + extends AbstractComponentBuilder + implements PgvectorComponentBuilder { + @Override + protected PgVectorComponent buildConcreteComponent() { + return new PgVectorComponent(); + } + private org.apache.camel.component.pgvector.PgVectorConfiguration getOrCreateConfiguration(PgVectorComponent component) { + if (component.getConfiguration() == null) { + component.setConfiguration(new org.apache.camel.component.pgvector.PgVectorConfiguration()); + } + return component.getConfiguration(); + } + @Override + protected boolean setPropertyOnComponent( + Component component, + String name, + Object value) { + switch (name) { + case "configuration": ((PgVectorComponent) component).setConfiguration((org.apache.camel.component.pgvector.PgVectorConfiguration) value); return true; + case "dataSource": getOrCreateConfiguration((PgVectorComponent) component).setDataSource((javax.sql.DataSource) value); return true; + case "dimension": getOrCreateConfiguration((PgVectorComponent) component).setDimension((int) value); return true; + case "distanceType": getOrCreateConfiguration((PgVectorComponent) component).setDistanceType((org.apache.camel.component.pgvector.PgVectorDistanceType) value); return true; + case "lazyStartProducer": ((PgVectorComponent) component).setLazyStartProducer((boolean) value); return true; + case "autowiredEnabled": ((PgVectorComponent) component).setAutowiredEnabled((boolean) value); return true; + default: return false; + } + } + } +} \ No newline at end of file diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilderFactory.java index 09654b6c8cea9..0b50eec3a2874 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilderFactory.java @@ -296,6 +296,7 @@ public interface EndpointBuilderFactory org.apache.camel.builder.endpoint.dsl.PdfEndpointBuilderFactory.PdfBuilders, org.apache.camel.builder.endpoint.dsl.PgEventEndpointBuilderFactory.PgEventBuilders, org.apache.camel.builder.endpoint.dsl.PgReplicationSlotEndpointBuilderFactory.PgReplicationSlotBuilders, + org.apache.camel.builder.endpoint.dsl.PgVectorEndpointBuilderFactory.PgVectorBuilders, org.apache.camel.builder.endpoint.dsl.PineconeVectorDbEndpointBuilderFactory.PineconeVectorDbBuilders, org.apache.camel.builder.endpoint.dsl.PlatformHttpEndpointBuilderFactory.PlatformHttpBuilders, org.apache.camel.builder.endpoint.dsl.Plc4XEndpointBuilderFactory.Plc4XBuilders, diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilders.java index 6f423c2f75da4..a30690261df07 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilders.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilders.java @@ -293,6 +293,7 @@ public interface EndpointBuilders org.apache.camel.builder.endpoint.dsl.PdfEndpointBuilderFactory, org.apache.camel.builder.endpoint.dsl.PgEventEndpointBuilderFactory, org.apache.camel.builder.endpoint.dsl.PgReplicationSlotEndpointBuilderFactory, + org.apache.camel.builder.endpoint.dsl.PgVectorEndpointBuilderFactory, org.apache.camel.builder.endpoint.dsl.PineconeVectorDbEndpointBuilderFactory, org.apache.camel.builder.endpoint.dsl.PlatformHttpEndpointBuilderFactory, org.apache.camel.builder.endpoint.dsl.Plc4XEndpointBuilderFactory, diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java index 408f13ccb8c84..71c450e30d21b 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java @@ -13322,6 +13322,46 @@ public static PgEventEndpointBuilderFactory.PgEventEndpointBuilder pgevent(Strin public static PgEventEndpointBuilderFactory.PgEventEndpointBuilder pgevent(String componentName, String path) { return PgEventEndpointBuilderFactory.endpointBuilder(componentName, path); } + /** + * PGVector (camel-pgvector) + * Perform operations on the PostgreSQL pgvector Vector Database. + * + * Category: database,ai + * Since: 4.19 + * Maven coordinates: org.apache.camel:camel-pgvector + * + * Syntax: pgvector:collection + * + * Path parameter: collection (required) + * The collection (table) name + * + * @param path collection + * @return the dsl builder + */ + public static PgVectorEndpointBuilderFactory.PgVectorEndpointBuilder pgvector(String path) { + return pgvector("pgvector", path); + } + /** + * PGVector (camel-pgvector) + * Perform operations on the PostgreSQL pgvector Vector Database. + * + * Category: database,ai + * Since: 4.19 + * Maven coordinates: org.apache.camel:camel-pgvector + * + * Syntax: pgvector:collection + * + * Path parameter: collection (required) + * The collection (table) name + * + * @param componentName to use a custom component name for the endpoint + * instead of the default name + * @param path collection + * @return the dsl builder + */ + public static PgVectorEndpointBuilderFactory.PgVectorEndpointBuilder pgvector(String componentName, String path) { + return PgVectorEndpointBuilderFactory.endpointBuilder(componentName, path); + } /** * Pinecone (camel-pinecone) * Perform operations on the Pinecone Vector Database. diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/PgVectorEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/PgVectorEndpointBuilderFactory.java new file mode 100644 index 0000000000000..33c8f592f8696 --- /dev/null +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/PgVectorEndpointBuilderFactory.java @@ -0,0 +1,371 @@ +/* Generated by camel build tools - do NOT edit this file! */ +/* + * 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.builder.endpoint.dsl; + +import java.util.*; +import java.util.concurrent.*; +import java.util.function.*; +import java.util.stream.*; +import javax.annotation.processing.Generated; +import org.apache.camel.builder.EndpointConsumerBuilder; +import org.apache.camel.builder.EndpointProducerBuilder; +import org.apache.camel.builder.endpoint.AbstractEndpointBuilder; + +/** + * Perform operations on the PostgreSQL pgvector Vector Database. + * + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.EndpointDslMojo") +public interface PgVectorEndpointBuilderFactory { + + /** + * Builder for endpoint for the PGVector component. + */ + public interface PgVectorEndpointBuilder + extends + EndpointProducerBuilder { + default AdvancedPgVectorEndpointBuilder advanced() { + return (AdvancedPgVectorEndpointBuilder) this; + } + + /** + * The DataSource to use for connecting to the PostgreSQL database with + * pgvector extension. + * + * The option is a: javax.sql.DataSource type. + * + * Group: producer + * + * @param dataSource the value to set + * @return the dsl builder + */ + default PgVectorEndpointBuilder dataSource(javax.sql.DataSource dataSource) { + doSetProperty("dataSource", dataSource); + return this; + } + /** + * The DataSource to use for connecting to the PostgreSQL database with + * pgvector extension. + * + * The option will be converted to a javax.sql.DataSource + * type. + * + * Group: producer + * + * @param dataSource the value to set + * @return the dsl builder + */ + default PgVectorEndpointBuilder dataSource(String dataSource) { + doSetProperty("dataSource", dataSource); + return this; + } + /** + * The dimension of the vectors to store. + * + * The option is a: int type. + * + * Default: 384 + * Group: producer + * + * @param dimension the value to set + * @return the dsl builder + */ + default PgVectorEndpointBuilder dimension(int dimension) { + doSetProperty("dimension", dimension); + return this; + } + /** + * The dimension of the vectors to store. + * + * The option will be converted to a int type. + * + * Default: 384 + * Group: producer + * + * @param dimension the value to set + * @return the dsl builder + */ + default PgVectorEndpointBuilder dimension(String dimension) { + doSetProperty("dimension", dimension); + return this; + } + /** + * The distance type to use for similarity search. + * + * The option is a: + * org.apache.camel.component.pgvector.PgVectorDistanceType + * type. + * + * Default: COSINE + * Group: producer + * + * @param distanceType the value to set + * @return the dsl builder + */ + default PgVectorEndpointBuilder distanceType(org.apache.camel.component.pgvector.PgVectorDistanceType distanceType) { + doSetProperty("distanceType", distanceType); + return this; + } + /** + * The distance type to use for similarity search. + * + * The option will be converted to a + * org.apache.camel.component.pgvector.PgVectorDistanceType + * type. + * + * Default: COSINE + * Group: producer + * + * @param distanceType the value to set + * @return the dsl builder + */ + default PgVectorEndpointBuilder distanceType(String distanceType) { + doSetProperty("distanceType", distanceType); + return this; + } + } + + /** + * Advanced builder for endpoint for the PGVector component. + */ + public interface AdvancedPgVectorEndpointBuilder + extends + EndpointProducerBuilder { + default PgVectorEndpointBuilder basic() { + return (PgVectorEndpointBuilder) this; + } + + /** + * Whether the producer should be started lazy (on the first message). + * By starting lazy you can use this to allow CamelContext and routes to + * startup in situations where a producer may otherwise fail during + * starting and cause the route to fail being started. By deferring this + * startup to be lazy then the startup failure can be handled during + * routing messages via Camel's routing error handlers. Beware that when + * the first message is processed then creating and starting the + * producer may take a little time and prolong the total processing time + * of the processing. + * + * The option is a: boolean type. + * + * Default: false + * Group: producer (advanced) + * + * @param lazyStartProducer the value to set + * @return the dsl builder + */ + default AdvancedPgVectorEndpointBuilder lazyStartProducer(boolean lazyStartProducer) { + doSetProperty("lazyStartProducer", lazyStartProducer); + return this; + } + /** + * Whether the producer should be started lazy (on the first message). + * By starting lazy you can use this to allow CamelContext and routes to + * startup in situations where a producer may otherwise fail during + * starting and cause the route to fail being started. By deferring this + * startup to be lazy then the startup failure can be handled during + * routing messages via Camel's routing error handlers. Beware that when + * the first message is processed then creating and starting the + * producer may take a little time and prolong the total processing time + * of the processing. + * + * The option will be converted to a boolean type. + * + * Default: false + * Group: producer (advanced) + * + * @param lazyStartProducer the value to set + * @return the dsl builder + */ + default AdvancedPgVectorEndpointBuilder lazyStartProducer(String lazyStartProducer) { + doSetProperty("lazyStartProducer", lazyStartProducer); + return this; + } + } + + public interface PgVectorBuilders { + /** + * PGVector (camel-pgvector) + * Perform operations on the PostgreSQL pgvector Vector Database. + * + * Category: database,ai + * Since: 4.19 + * Maven coordinates: org.apache.camel:camel-pgvector + * + * @return the dsl builder for the headers' name. + */ + default PgVectorHeaderNameBuilder pgvector() { + return PgVectorHeaderNameBuilder.INSTANCE; + } + /** + * PGVector (camel-pgvector) + * Perform operations on the PostgreSQL pgvector Vector Database. + * + * Category: database,ai + * Since: 4.19 + * Maven coordinates: org.apache.camel:camel-pgvector + * + * Syntax: pgvector:collection + * + * Path parameter: collection (required) + * The collection (table) name + * + * @param path collection + * @return the dsl builder + */ + default PgVectorEndpointBuilder pgvector(String path) { + return PgVectorEndpointBuilderFactory.endpointBuilder("pgvector", path); + } + /** + * PGVector (camel-pgvector) + * Perform operations on the PostgreSQL pgvector Vector Database. + * + * Category: database,ai + * Since: 4.19 + * Maven coordinates: org.apache.camel:camel-pgvector + * + * Syntax: pgvector:collection + * + * Path parameter: collection (required) + * The collection (table) name + * + * @param componentName to use a custom component name for the endpoint + * instead of the default name + * @param path collection + * @return the dsl builder + */ + default PgVectorEndpointBuilder pgvector(String componentName, String path) { + return PgVectorEndpointBuilderFactory.endpointBuilder(componentName, path); + } + + } + /** + * The builder of headers' name for the PGVector component. + */ + public static class PgVectorHeaderNameBuilder { + /** + * The internal instance of the builder used to access to all the + * methods representing the name of headers. + */ + private static final PgVectorHeaderNameBuilder INSTANCE = new PgVectorHeaderNameBuilder(); + + /** + * The action to be performed. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code PgVectorAction}. + */ + public String pgVectorAction() { + return "CamelPgVectorAction"; + } + /** + * The id of the vector record. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code PgVectorRecordId}. + */ + public String pgVectorRecordId() { + return "CamelPgVectorRecordId"; + } + /** + * The maximum number of results to return for similarity search. + * + * The option is a: {@code Integer} type. + * + * Default: 3 + * Group: producer + * + * @return the name of the header {@code PgVectorQueryTopK}. + */ + public String pgVectorQueryTopK() { + return "CamelPgVectorQueryTopK"; + } + /** + * The text content to store alongside the vector embedding. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code PgVectorTextContent}. + */ + public String pgVectorTextContent() { + return "CamelPgVectorTextContent"; + } + /** + * The metadata associated with the vector record, stored as JSON. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code PgVectorMetadata}. + */ + public String pgVectorMetadata() { + return "CamelPgVectorMetadata"; + } + /** + * Filter condition for similarity search. Applied as a SQL WHERE clause + * on the text_content and metadata columns. Supports parameterized + * queries using placeholders with values provided via the + * CamelPgVectorFilterParams header. WARNING: When not using + * parameterized queries, the filter value is appended directly as SQL. + * Never use untrusted input as the filter value without + * parameterization, as this could lead to SQL injection. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code PgVectorFilter}. + */ + public String pgVectorFilter() { + return "CamelPgVectorFilter"; + } + /** + * Parameter values for parameterized filter queries. Use with + * placeholders in the CamelPgVectorFilter header. Example: filter = + * 'text_content LIKE AND metadata::jsonb-'category' = ' with + * filterParams = List.of(%hello%, science). + * + * The option is a: {@code java.util.List} type. + * + * Group: producer + * + * @return the name of the header {@code PgVectorFilterParams}. + */ + public String pgVectorFilterParams() { + return "CamelPgVectorFilterParams"; + } + } + static PgVectorEndpointBuilder endpointBuilder(String componentName, String path) { + class PgVectorEndpointBuilderImpl extends AbstractEndpointBuilder implements PgVectorEndpointBuilder, AdvancedPgVectorEndpointBuilder { + public PgVectorEndpointBuilderImpl(String path) { + super(componentName, path); + } + } + return new PgVectorEndpointBuilderImpl(path); + } +} \ No newline at end of file diff --git a/dsl/camel-kamelet-main/src/generated/resources/camel-component-known-dependencies.properties b/dsl/camel-kamelet-main/src/generated/resources/camel-component-known-dependencies.properties index e20ed4f61f3ad..aaf1a111c3470 100644 --- a/dsl/camel-kamelet-main/src/generated/resources/camel-component-known-dependencies.properties +++ b/dsl/camel-kamelet-main/src/generated/resources/camel-component-known-dependencies.properties @@ -305,6 +305,7 @@ org.apache.camel.component.paho.mqtt5.PahoMqtt5Component=camel:paho-mqtt5 org.apache.camel.component.pdf.PdfComponent=camel:pdf org.apache.camel.component.pg.replication.slot.PgReplicationSlotComponent=camel:pg-replication-slot org.apache.camel.component.pgevent.PgEventComponent=camel:pgevent +org.apache.camel.component.pgvector.PgVectorComponent=camel:pgvector org.apache.camel.component.pinecone.PineconeVectorDbComponent=camel:pinecone org.apache.camel.component.platform.http.PlatformHttpComponent=camel:platform-http org.apache.camel.component.plc4x.Plc4XComponent=camel:plc4x diff --git a/parent/pom.xml b/parent/pom.xml index b13caf1205240..6cb752702b1ca 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -460,6 +460,7 @@ 3.0.7 42.7.10 0.8.9 + 0.1.6 4.7.7 3.1.0 0.13.1 @@ -2386,6 +2387,11 @@ camel-pgevent ${project.version} + + org.apache.camel + camel-pgvector + ${project.version} + org.apache.camel camel-pinecone diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java index 4b0a6d66e0d01..46f7670f3996f 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java @@ -46,7 +46,7 @@ public static List getComponentPath(Path dir) { dir.resolve("camel-langchain4j-web-search"), dir.resolve("camel-qdrant"), dir.resolve("camel-milvus"), dir.resolve("camel-neo4j"), dir.resolve("camel-openai"), - dir.resolve("camel-pinecone"), dir.resolve("camel-kserve"), + dir.resolve("camel-pgvector"), dir.resolve("camel-pinecone"), dir.resolve("camel-kserve"), dir.resolve("camel-tensorflow-serving"), dir.resolve("camel-weaviate"), dir.resolve("camel-docling")); case "camel-as2":