diff --git a/spring-ai-spring-boot-autoconfigure/pom.xml b/spring-ai-spring-boot-autoconfigure/pom.xml index 8cb693f9f..97d136c5d 100644 --- a/spring-ai-spring-boot-autoconfigure/pom.xml +++ b/spring-ai-spring-boot-autoconfigure/pom.xml @@ -170,6 +170,14 @@ true + + + org.springframework.ai + spring-ai-neo4j-store + ${project.parent.version} + true + + @@ -223,6 +231,13 @@ test + + org.testcontainers + neo4j + ${testcontainers.version} + test + + diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jDriverProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jDriverProperties.java new file mode 100644 index 000000000..ce6246fc0 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jDriverProperties.java @@ -0,0 +1,289 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.ai.autoconfigure.vectorstore.neo4j; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.io.File; +import java.net.URI; +import java.time.Duration; + +/** + * Properties for Neo4j driver + * + * @author Jingzhou Ou + */ +@ConfigurationProperties(Neo4jDriverProperties.CONFIG_PREFIX) +public class Neo4jDriverProperties { + + public static final String CONFIG_PREFIX = "spring.ai.vectorstore.neo4j.driver"; + + /** + * supports bolt or neo4j as schemes. + */ + private URI uri; + + /** + * optional + */ + private Authentication authentication = new Authentication(); + + /** + * connection pool configuration + */ + private PoolSettings pool = new PoolSettings(); + + /** + * Detailed driver configuration of the driver + */ + private DriverSettings config = new DriverSettings(); + + public URI getUri() { + return this.uri; + } + + public void setUri(URI uri) { + this.uri = uri; + } + + public Authentication getAuthentication() { + return this.authentication; + } + + public void setAuthentication(Authentication authentication) { + this.authentication = authentication; + } + + public PoolSettings getPool() { + return this.pool; + } + + public void setPool(PoolSettings pool) { + this.pool = pool; + } + + public DriverSettings getConfig() { + return this.config; + } + + public void setConfig(DriverSettings config) { + this.config = config; + } + + public static class Authentication { + + private String username; + + private String password; + + private String realm; + + /** + * kerberos authentication + */ + private String kerberosTicket; + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRealm() { + return this.realm; + } + + public void setRealm(String realm) { + this.realm = realm; + } + + public String getKerberosTicket() { + return this.kerberosTicket; + } + + public void setKerberosTicket(String kerberosTicket) { + this.kerberosTicket = kerberosTicket; + } + + } + + public static class PoolSettings { + + private boolean metricsEnabled = false; + + private boolean logLeakedSessions = false; + + private int maxConnectionPoolSize = org.neo4j.driver.internal.async.pool.PoolSettings.DEFAULT_MAX_CONNECTION_POOL_SIZE; + + private Duration idleTimeBeforeConnectionTest; + + private Duration maxConnectionLifetime = Duration + .ofMillis(org.neo4j.driver.internal.async.pool.PoolSettings.DEFAULT_MAX_CONNECTION_LIFETIME); + + private Duration connectionAcquisitionTimeout = Duration + .ofMillis(org.neo4j.driver.internal.async.pool.PoolSettings.DEFAULT_CONNECTION_ACQUISITION_TIMEOUT); + + public boolean isLogLeakedSessions() { + return this.logLeakedSessions; + } + + public void setLogLeakedSessions(boolean logLeakedSessions) { + this.logLeakedSessions = logLeakedSessions; + } + + public int getMaxConnectionPoolSize() { + return this.maxConnectionPoolSize; + } + + public void setMaxConnectionPoolSize(int maxConnectionPoolSize) { + this.maxConnectionPoolSize = maxConnectionPoolSize; + } + + public Duration getIdleTimeBeforeConnectionTest() { + return this.idleTimeBeforeConnectionTest; + } + + public void setIdleTimeBeforeConnectionTest(Duration idleTimeBeforeConnectionTest) { + this.idleTimeBeforeConnectionTest = idleTimeBeforeConnectionTest; + } + + public Duration getMaxConnectionLifetime() { + return this.maxConnectionLifetime; + } + + public void setMaxConnectionLifetime(Duration maxConnectionLifetime) { + this.maxConnectionLifetime = maxConnectionLifetime; + } + + public Duration getConnectionAcquisitionTimeout() { + return this.connectionAcquisitionTimeout; + } + + public void setConnectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) { + this.connectionAcquisitionTimeout = connectionAcquisitionTimeout; + } + + public boolean isMetricsEnabled() { + return this.metricsEnabled; + } + + public void setMetricsEnabled(boolean metricsEnabled) { + this.metricsEnabled = metricsEnabled; + } + + } + + public static class DriverSettings { + + private boolean encrypted = false; + + private TrustSettings trustSettings = new TrustSettings(); + + private Duration connectionTimeout = Duration.ofSeconds(30); + + private Duration maxTransactionRetryTime = Duration + .ofMillis(org.neo4j.driver.internal.retry.RetrySettings.DEFAULT.maxRetryTimeMs()); + + public boolean isEncrypted() { + return this.encrypted; + } + + public void setEncrypted(boolean encrypted) { + this.encrypted = encrypted; + } + + public TrustSettings getTrustSettings() { + return this.trustSettings; + } + + public void setTrustSettings(TrustSettings trustSettings) { + this.trustSettings = trustSettings; + } + + public Duration getConnectionTimeout() { + return this.connectionTimeout; + } + + public void setConnectionTimeout(Duration connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + public Duration getMaxTransactionRetryTime() { + return this.maxTransactionRetryTime; + } + + public void setMaxTransactionRetryTime(Duration maxTransactionRetryTime) { + this.maxTransactionRetryTime = maxTransactionRetryTime; + } + + } + + public static class TrustSettings { + + public enum Strategy { + + TRUST_ALL_CERTIFICATES, + + TRUST_CUSTOM_CA_SIGNED_CERTIFICATES, + + TRUST_SYSTEM_CA_SIGNED_CERTIFICATES + + } + + private TrustSettings.Strategy strategy = Strategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES; + + private File certFile; + + private boolean hostnameVerificationEnabled = false; + + public TrustSettings.Strategy getStrategy() { + return this.strategy; + } + + public void setStrategy(TrustSettings.Strategy strategy) { + this.strategy = strategy; + } + + public File getCertFile() { + return this.certFile; + } + + public void setCertFile(File certFile) { + this.certFile = certFile; + } + + public boolean isHostnameVerificationEnabled() { + return this.hostnameVerificationEnabled; + } + + public void setHostnameVerificationEnabled(boolean hostnameVerificationEnabled) { + this.hostnameVerificationEnabled = hostnameVerificationEnabled; + } + + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreAutoConfiguration.java new file mode 100644 index 000000000..60ababd29 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreAutoConfiguration.java @@ -0,0 +1,188 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.ai.autoconfigure.vectorstore.neo4j; + +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Config; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.internal.Scheme; +import org.springframework.ai.embedding.EmbeddingClient; +import org.springframework.ai.vectorstore.Neo4jVectorStore; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; +import org.springframework.context.annotation.Bean; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.net.URI; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +/** + * @author Jingzhou Ou + */ +@AutoConfiguration +@ConditionalOnClass({ Neo4jVectorStore.class, EmbeddingClient.class }) +@EnableConfigurationProperties({ Neo4jVectorStoreProperties.class, Neo4jDriverProperties.class }) +public class Neo4jVectorStoreAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public VectorStore vectorStore(Driver driver, EmbeddingClient embeddingClient, + Neo4jVectorStoreProperties properties) { + Neo4jVectorStore.Neo4jVectorStoreConfig config = Neo4jVectorStore.Neo4jVectorStoreConfig.builder() + .withDatabaseName(properties.getDatabaseName()) + .withEmbeddingDimension(properties.getEmbeddingDimension()) + .withDistanceType(properties.getDistanceType()) + .withLabel(properties.getLabel()) + .withEmbeddingProperty(properties.getEmbeddingProperty()) + .build(); + + return new Neo4jVectorStore(driver, embeddingClient, config); + } + + @Bean + @ConditionalOnMissingBean(Driver.class) + Driver neo4jDriver(Neo4jDriverProperties driverProperties) { + AuthToken authToken = getAuthToken(driverProperties); + Config config = getDriverConfig(driverProperties); + return GraphDatabase.driver(driverProperties.getUri(), authToken, config); + } + + private Config getDriverConfig(Neo4jDriverProperties driverProperties) { + Config.ConfigBuilder builder = Config.builder(); + buildWithPoolSettings(builder, driverProperties.getPool()); + URI uri = driverProperties.getUri(); + String scheme = uri == null ? "bolt" : uri.getScheme(); + buildWithDriverSettings(builder, driverProperties.getConfig(), isSimpleScheme(scheme)); + return builder.build(); + } + + private AuthToken getAuthToken(Neo4jDriverProperties driverProperties) { + String username = driverProperties.getAuthentication().getUsername(); + String password = driverProperties.getAuthentication().getPassword(); + String kerberosTicket = driverProperties.getAuthentication().getKerberosTicket(); + String realm = driverProperties.getAuthentication().getRealm(); + + boolean hasUsername = StringUtils.hasText(username); + boolean hasPassword = StringUtils.hasText(password); + boolean hasKerberosTicket = StringUtils.hasText(kerberosTicket); + + if (hasUsername && hasKerberosTicket) { + throw new InvalidConfigurationPropertyValueException("spring.ai.vectorstore.neo4j.driver.authentication", + "username=" + username + ",kerberos-ticket=" + kerberosTicket, + "Cannot specify both username and kerberos ticket."); + } + + if (hasUsername && hasPassword) { + return AuthTokens.basic(username, password, realm); + } + + if (hasKerberosTicket) { + return AuthTokens.kerberos(kerberosTicket); + } + + return AuthTokens.none(); + } + + private void buildWithPoolSettings(Config.ConfigBuilder builder, Neo4jDriverProperties.PoolSettings poolSettings) { + if (poolSettings.isLogLeakedSessions()) { + builder.withLeakedSessionsLogging(); + } + builder.withMaxConnectionPoolSize(poolSettings.getMaxConnectionPoolSize()); + if (poolSettings.getIdleTimeBeforeConnectionTest() != null) { + builder.withConnectionLivenessCheckTimeout(poolSettings.getIdleTimeBeforeConnectionTest().toMillis(), + TimeUnit.MILLISECONDS); + } + builder.withMaxConnectionLifetime(poolSettings.getMaxConnectionLifetime().toMillis(), TimeUnit.MILLISECONDS); + builder.withConnectionAcquisitionTimeout(poolSettings.getConnectionAcquisitionTimeout().toMillis(), + TimeUnit.MILLISECONDS); + + if (poolSettings.isMetricsEnabled()) { + builder.withDriverMetrics(); + } + else { + builder.withoutDriverMetrics(); + } + } + + private void buildWithDriverSettings(Config.ConfigBuilder builder, + Neo4jDriverProperties.DriverSettings driverSettings, boolean withEncryptionAndTrustSettings) { + if (withEncryptionAndTrustSettings) { + if (driverSettings.isEncrypted()) { + builder.withEncryption(); + } + else { + builder.withoutEncryption(); + } + builder.withTrustStrategy(getTrustStrategy(driverSettings.getTrustSettings())); + } + builder.withConnectionTimeout(driverSettings.getConnectionTimeout().toMillis(), TimeUnit.MILLISECONDS); + builder.withMaxTransactionRetryTime(driverSettings.getMaxTransactionRetryTime().toMillis(), + TimeUnit.MILLISECONDS); + } + + private boolean isSimpleScheme(String scheme) { + String lowerCaseScheme = scheme.toLowerCase(Locale.ENGLISH); + try { + Scheme.validateScheme(lowerCaseScheme); + } + catch (IllegalArgumentException ex) { + throw new IllegalArgumentException(String.format("'%s' is not a supported scheme.", scheme)); + } + return lowerCaseScheme.equals("bolt") || lowerCaseScheme.equals("neo4j"); + } + + private Config.TrustStrategy getTrustStrategy(Neo4jDriverProperties.TrustSettings trustSettings) { + String propertyName = "spring.ai.vectorstore.neo4j.driver.config.trust-settings"; + Config.TrustStrategy internalRepresentation; + File certFile = trustSettings.getCertFile(); + switch (trustSettings.getStrategy()) { + case TRUST_ALL_CERTIFICATES: + internalRepresentation = Config.TrustStrategy.trustAllCertificates(); + break; + case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES: + internalRepresentation = Config.TrustStrategy.trustSystemCertificates(); + break; + case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES: + if (certFile == null || !certFile.isFile()) { + throw new InvalidConfigurationPropertyValueException(propertyName, + trustSettings.getStrategy().name(), + "Configured trust strategy requires a certificate file."); + } + internalRepresentation = Config.TrustStrategy.trustCustomCertificateSignedBy(certFile); + break; + default: + throw new InvalidConfigurationPropertyValueException(propertyName, trustSettings.getStrategy().name(), + "Unknown strategy."); + } + if (trustSettings.isHostnameVerificationEnabled()) { + internalRepresentation.withHostnameVerification(); + } + else { + internalRepresentation.withoutHostnameVerification(); + } + return internalRepresentation; + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreProperties.java new file mode 100644 index 000000000..29c11ee02 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreProperties.java @@ -0,0 +1,80 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.ai.autoconfigure.vectorstore.neo4j; + +import org.springframework.ai.vectorstore.Neo4jVectorStore; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Jingzhou Ou + */ +@ConfigurationProperties(Neo4jVectorStoreProperties.CONFIG_PREFIX) +public class Neo4jVectorStoreProperties { + + public static final String CONFIG_PREFIX = "spring.ai.vectorstore.neo4j"; + + private String databaseName; + + private int embeddingDimension = Neo4jVectorStore.DEFAULT_EMBEDDING_DIMENSION; + + private Neo4jVectorStore.Neo4jDistanceType distanceType = Neo4jVectorStore.Neo4jDistanceType.COSINE; + + private String label = Neo4jVectorStore.DEFAULT_LABEL; + + private String embeddingProperty = Neo4jVectorStore.DEFAULT_EMBEDDING_PROPERTY; + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public int getEmbeddingDimension() { + return embeddingDimension; + } + + public void setEmbeddingDimension(int embeddingDimension) { + this.embeddingDimension = embeddingDimension; + } + + public Neo4jVectorStore.Neo4jDistanceType getDistanceType() { + return distanceType; + } + + public void setDistanceType(Neo4jVectorStore.Neo4jDistanceType distanceType) { + this.distanceType = distanceType; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getEmbeddingProperty() { + return embeddingProperty; + } + + public void setEmbeddingProperty(String embeddingProperty) { + this.embeddingProperty = embeddingProperty; + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreAutoConfigurationIT.java new file mode 100644 index 000000000..43004ab15 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreAutoConfigurationIT.java @@ -0,0 +1,104 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.ai.autoconfigure.vectorstore.neo4j; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import org.springframework.ai.ResourceUtils; +import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.EmbeddingClient; +import org.springframework.ai.embedding.TransformersEmbeddingClient; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Jingzhou Ou + */ +@Testcontainers +public class Neo4jVectorStoreAutoConfigurationIT { + + // Needs to be Neo4j 5.13+, because Neo4j 5.13 deprecated the used embedding storing + // function. + @Container + static Neo4jContainer neo4jContainer = new Neo4jContainer<>(DockerImageName.parse("neo4j:5.14")) + .withRandomPassword(); + + List documents = List.of( + new Document(ResourceUtils.getText("classpath:/test/data/spring.ai.txt"), Map.of("spring", "great")), + new Document(ResourceUtils.getText("classpath:/test/data/time.shelter.txt")), new Document( + ResourceUtils.getText("classpath:/test/data/great.depression.txt"), Map.of("depression", "bad"))); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(Neo4jVectorStoreAutoConfiguration.class)) + .withUserConfiguration(Config.class) + .withPropertyValues("spring.ai.vectorstore.neo4j.driver.uri=" + neo4jContainer.getBoltUrl(), + "spring.ai.vectorstore.neo4j.driver.authentication.username=" + "neo4j", + "spring.ai.vectorstore.neo4j.driver.authentication.password=" + neo4jContainer.getAdminPassword()); + + @Test + void addAndSearch() { + contextRunner + .withPropertyValues("spring.ai.vectorstore.neo4j.label=my_test_label", + "spring.ai.vectorstore.neo4j.embeddingDimension=384") + .run(context -> { + var properties = context.getBean(Neo4jVectorStoreProperties.class); + assertThat(properties.getLabel()).isEqualTo("my_test_label"); + assertThat(properties.getEmbeddingDimension()).isEqualTo(384); + + VectorStore vectorStore = context.getBean(VectorStore.class); + vectorStore.add(documents); + + List results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); + + assertThat(results).hasSize(1); + Document resultDoc = results.get(0); + assertThat(resultDoc.getId()).isEqualTo(documents.get(0).getId()); + assertThat(resultDoc.getContent()).contains( + "Spring AI provides abstractions that serve as the foundation for developing AI applications."); + + // Remove all documents from the store + vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); + + results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); + assertThat(results).isEmpty(); + }); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + public EmbeddingClient embeddingClient() { + return new TransformersEmbeddingClient(); + } + + } + +} diff --git a/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/Neo4jVectorStore.java b/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/Neo4jVectorStore.java index 71976f587..633b2b4c5 100644 --- a/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/Neo4jVectorStore.java +++ b/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/Neo4jVectorStore.java @@ -194,13 +194,13 @@ public class Neo4jVectorStore implements VectorStore, InitializingBean { } - private static final int DEFAULT_EMBEDDING_DIMENSION = 1536; + public static final int DEFAULT_EMBEDDING_DIMENSION = 1536; - private static final String DEFAULT_LABEL = "Document"; + public static final String DEFAULT_LABEL = "Document"; private static final String INDEX_NAME = "spring-ai-document-index"; - private static final String DEFAULT_EMBEDDING_PROPERTY = "embedding"; + public static final String DEFAULT_EMBEDDING_PROPERTY = "embedding"; private final Driver driver;