diff --git a/spring-ai-modules/pom.xml b/spring-ai-modules/pom.xml
index 876abd83095f..44c133498d10 100644
--- a/spring-ai-modules/pom.xml
+++ b/spring-ai-modules/pom.xml
@@ -18,6 +18,7 @@
spring-ai-mcp
spring-ai-text-to-sql
+ spring-ai-vector-stores
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/pom.xml b/spring-ai-modules/spring-ai-vector-stores/pom.xml
new file mode 100644
index 000000000000..f88fd70ced48
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+
+ com.baeldung
+ spring-ai-modules
+ 0.0.1
+ ../pom.xml
+
+
+ spring-ai-vector-stores
+ 0.0.1
+ pom
+ spring-ai-vector-stores
+
+
+ spring-ai-oracle
+
+
+
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/pom.xml b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/pom.xml
new file mode 100644
index 000000000000..3852f07ff3c9
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/pom.xml
@@ -0,0 +1,79 @@
+
+
+ 4.0.0
+
+
+ com.baeldung
+ spring-ai-vector-stores
+ 0.0.1
+ ../pom.xml
+
+
+ com.baeldung
+ spring-ai-oracle
+ 0.0.1
+ spring-ai-oracle
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.ai
+ spring-ai-starter-vector-store-oracle
+
+
+ org.springframework.ai
+ spring-ai-advisors-vector-store
+
+
+ org.springframework.ai
+ spring-ai-starter-model-openai
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.ai
+ spring-ai-spring-boot-testcontainers
+ test
+
+
+ org.testcontainers
+ oracle-free
+ test
+
+
+
+
+ 21
+ 1.0.0
+ 3.5.4
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+
+ org.springframework.ai
+ spring-ai-bom
+ ${spring-ai.version}
+ pom
+ import
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/Application.java b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/Application.java
new file mode 100644
index 000000000000..ae7f203899bd
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/Application.java
@@ -0,0 +1,13 @@
+package com.baeldung.springai.vectorstore.oracle;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/Quote.java b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/Quote.java
new file mode 100644
index 000000000000..3106010dbd27
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/Quote.java
@@ -0,0 +1,4 @@
+package com.baeldung.springai.vectorstore.oracle;
+
+record Quote(String quote, String author) {
+}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/QuoteFetcher.java b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/QuoteFetcher.java
new file mode 100644
index 000000000000..f822a8d9c2fa
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/QuoteFetcher.java
@@ -0,0 +1,27 @@
+package com.baeldung.springai.vectorstore.oracle;
+
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.web.client.RestClient;
+
+import java.net.URI;
+import java.util.List;
+
+class QuoteFetcher {
+
+ private static final String BASE_URL = "https://api.breakingbadquotes.xyz/v1/quotes/";
+ private static final int DEFAULT_COUNT = 150;
+
+ static List fetch() {
+ return fetch(DEFAULT_COUNT);
+ }
+
+ static List fetch(int count) {
+ return RestClient
+ .create()
+ .get()
+ .uri(URI.create(BASE_URL + count))
+ .retrieve()
+ .body(new ParameterizedTypeReference<>() {});
+ }
+
+}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/RAGChatbotConfiguration.java b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/RAGChatbotConfiguration.java
new file mode 100644
index 000000000000..036a7904990c
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/RAGChatbotConfiguration.java
@@ -0,0 +1,60 @@
+package com.baeldung.springai.vectorstore.oracle;
+
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.prompt.PromptTemplate;
+import org.springframework.ai.template.st.StTemplateRenderer;
+import org.springframework.ai.vectorstore.SearchRequest;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+@Configuration
+class RAGChatbotConfiguration {
+
+ private static final int MAX_RESULTS = 10;
+
+ @Bean
+ PromptTemplate promptTemplate(
+ @Value("classpath:prompt-template.st") Resource promptTemplate
+ ) throws IOException {
+ String template = promptTemplate.getContentAsString(StandardCharsets.UTF_8);
+ return PromptTemplate
+ .builder()
+ .renderer(StTemplateRenderer
+ .builder()
+ .startDelimiterToken('<')
+ .endDelimiterToken('>')
+ .build())
+ .template(template)
+ .build();
+ }
+
+ @Bean
+ ChatClient chatClient(
+ ChatModel chatModel,
+ VectorStore vectorStore,
+ PromptTemplate promptTemplate
+ ) {
+ return ChatClient
+ .builder(chatModel)
+ .defaultAdvisors(
+ QuestionAnswerAdvisor
+ .builder(vectorStore)
+ .promptTemplate(promptTemplate)
+ .searchRequest(SearchRequest
+ .builder()
+ .topK(MAX_RESULTS)
+ .build())
+ .build()
+ )
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/VectorStoreInitializer.java b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/VectorStoreInitializer.java
new file mode 100644
index 000000000000..d18d2c6d7329
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/java/com/baeldung/springai/vectorstore/oracle/VectorStoreInitializer.java
@@ -0,0 +1,34 @@
+package com.baeldung.springai.vectorstore.oracle;
+
+import org.springframework.ai.document.Document;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+@Component
+class VectorStoreInitializer implements ApplicationRunner {
+
+ private final VectorStore vectorStore;
+
+ VectorStoreInitializer(VectorStore vectorStore) {
+ this.vectorStore = vectorStore;
+ }
+
+ @Override
+ public void run(ApplicationArguments args) {
+ List documents = QuoteFetcher
+ .fetch()
+ .stream()
+ .map(quote -> {
+ Map metadata = Map.of("author", quote.author());
+ return new Document(quote.quote(), metadata);
+ })
+ .toList();
+ vectorStore.add(documents);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/resources/application.yaml b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/resources/application.yaml
new file mode 100644
index 000000000000..a40f4dbc7863
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/resources/application.yaml
@@ -0,0 +1,13 @@
+spring:
+ ai:
+ vectorstore:
+ oracle:
+ initialize-schema: true
+ openai:
+ api-key: ${OPENAI_API_KEY}
+ embedding:
+ options:
+ model: text-embedding-3-large
+ chat:
+ options:
+ model: gpt-4o
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/resources/logback-spring.xml b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/resources/logback-spring.xml
new file mode 100644
index 000000000000..449efbdaebb0
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/resources/logback-spring.xml
@@ -0,0 +1,15 @@
+
+
+
+ [%d{yyyy-MM-dd HH:mm:ss}] [%p] [%c{1}] - %m%n
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/resources/prompt-template.st b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/resources/prompt-template.st
new file mode 100644
index 000000000000..f0d83e59c83c
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/main/resources/prompt-template.st
@@ -0,0 +1,16 @@
+You are a chatbot built for analyzing quotes from the 'Breaking Bad' television series.
+Given the quotes in the CONTEXT section, answer the query in the USER_QUESTION section.
+The response should follow the guidelines listed in the GUIDELINES section.
+
+CONTEXT:
+
+
+USER_QUESTION:
+
+
+GUIDELINES:
+- Base your answer solely on the information found in the provided quotes.
+- Provide concise, direct answers without mentioning "based on the context" or similar phrases.
+- When referencing specific quotes, mention the character who said them.
+- If the question cannot be answered using the context, respond with "The provided quotes do not contain information to answer this question."
+- If the question is unrelated to the Breaking Bad show or the quotes provided, respond with "This question is outside the scope of the available Breaking Bad quotes."
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/OracleDatabaseContainerConnectionDetailsFactory.java b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/OracleDatabaseContainerConnectionDetailsFactory.java
new file mode 100644
index 000000000000..9c5ce2f1f012
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/OracleDatabaseContainerConnectionDetailsFactory.java
@@ -0,0 +1,40 @@
+package com.baeldung.springai.vectorstore.oracle;
+
+import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
+import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
+import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
+import org.testcontainers.oracle.OracleContainer;
+
+class OracleDatabaseContainerConnectionDetailsFactory
+ extends ContainerConnectionDetailsFactory {
+
+ @Override
+ protected JdbcConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) {
+ return new OracleDatabaseContainerConnectionDetails(source);
+ }
+
+ private static final class OracleDatabaseContainerConnectionDetails
+ extends ContainerConnectionDetails implements JdbcConnectionDetails {
+
+ OracleDatabaseContainerConnectionDetails(ContainerConnectionSource source) {
+ super(source);
+ }
+
+ @Override
+ public String getUsername() {
+ return getContainer().getUsername();
+ }
+
+ @Override
+ public String getPassword() {
+ return getContainer().getPassword();
+ }
+
+ @Override
+ public String getJdbcUrl() {
+ return getContainer().getJdbcUrl();
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/RAGChatbotLiveTest.java b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/RAGChatbotLiveTest.java
new file mode 100644
index 000000000000..f70c85cd2833
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/RAGChatbotLiveTest.java
@@ -0,0 +1,64 @@
+package com.baeldung.springai.vectorstore.oracle;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+@SpringBootTest
+@Import(TestcontainersConfiguration.class)
+@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*")
+class RAGChatbotLiveTest {
+
+ private static final String OUT_OF_SCOPE_MESSAGE = "This question is outside the scope of the available Breaking Bad quotes.";
+ private static final String NO_INFORMATION_MESSAGE = "The provided quotes do not contain information to answer this question.";
+
+ @Autowired
+ private ChatClient chatClient;
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "How does the show portray the mentor-student dynamic?",
+ "Which characters in the show portray insecurity through their quotes?",
+ "Does the show contain quotes with mature themes inappropriate for young viewers?"
+ })
+ void whenQuestionsRelatedToBreakingBadAsked_thenRelevantAnswerReturned(String userQuery) {
+ String response = chatClient
+ .prompt(userQuery)
+ .call()
+ .content();
+
+ assertThat(response)
+ .isNotBlank()
+ .doesNotContain(OUT_OF_SCOPE_MESSAGE, NO_INFORMATION_MESSAGE);
+ }
+
+ @Test
+ void whenUnrelatedQuestionAsked_thenOutOfScopeMessageReturned() {
+ String response = chatClient
+ .prompt("Did Jon Jones duck Tom Aspinall?")
+ .call()
+ .content();
+
+ assertThat(response)
+ .isEqualTo(OUT_OF_SCOPE_MESSAGE);
+ }
+
+ @Test
+ void whenQuestionWithNoRelevantQuotesAsked_thenNoInformationMessageReturned() {
+ String response = chatClient
+ .prompt("What does Walter White think about Albuquerque's weather?")
+ .call()
+ .content();
+
+ assertThat(response)
+ .isEqualTo(NO_INFORMATION_MESSAGE);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/SimilaritySearchLiveTest.java b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/SimilaritySearchLiveTest.java
new file mode 100644
index 000000000000..2be8a8add34f
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/SimilaritySearchLiveTest.java
@@ -0,0 +1,79 @@
+package com.baeldung.springai.vectorstore.oracle;
+
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.vectorstore.SearchRequest;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+@Import(TestcontainersConfiguration.class)
+@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*")
+class SimilaritySearchLiveTest {
+
+ private static final int MAX_RESULTS = 5;
+
+ @Autowired
+ private VectorStore vectorStore;
+
+ @ParameterizedTest
+ @ValueSource(strings = { "Sarcasm", "Regret", "Violence and Threats", "Greed, Power, and Money" })
+ void whenSearchingBreakingBadTheme_thenRelevantQuotesReturned(String theme) {
+ SearchRequest searchRequest = SearchRequest
+ .builder()
+ .query(theme)
+ .topK(MAX_RESULTS)
+ .build();
+
+ List documents = vectorStore.similaritySearch(searchRequest);
+
+ assertThat(documents)
+ .hasSizeGreaterThan(0)
+ .hasSizeLessThanOrEqualTo(MAX_RESULTS)
+ .allSatisfy(document -> {
+ assertThat(document.getText())
+ .isNotBlank();
+ assertThat(String.valueOf(document.getMetadata().get("author")))
+ .isNotBlank();
+ });
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "Walter White, Pride",
+ "Walter White, Control",
+ "Jesse Pinkman, Abuse and foul language",
+ "Mike Ehrmantraut, Wisdom",
+ "Saul Goodman, Law"
+ })
+ void whenSearchingCharacterTheme_thenRelevantQuotesReturned(String author, String theme) {
+ SearchRequest searchRequest = SearchRequest
+ .builder()
+ .query(theme)
+ .topK(MAX_RESULTS)
+ .filterExpression(String.format("author == '%s'", author))
+ .build();
+
+ List documents = vectorStore.similaritySearch(searchRequest);
+
+ assertThat(documents)
+ .hasSizeGreaterThan(0)
+ .hasSizeLessThanOrEqualTo(MAX_RESULTS)
+ .allSatisfy(document -> {
+ assertThat(document.getText())
+ .isNotBlank();
+ assertThat(String.valueOf(document.getMetadata().get("author")))
+ .contains(author);
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/TestcontainersConfiguration.java b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/TestcontainersConfiguration.java
new file mode 100644
index 000000000000..14de1c21008b
--- /dev/null
+++ b/spring-ai-modules/spring-ai-vector-stores/spring-ai-oracle/src/test/java/com/baeldung/springai/vectorstore/oracle/TestcontainersConfiguration.java
@@ -0,0 +1,17 @@
+package com.baeldung.springai.vectorstore.oracle;
+
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.testcontainers.oracle.OracleContainer;
+
+@TestConfiguration(proxyBeanMethods = false)
+class TestcontainersConfiguration {
+
+ @Bean
+ @ServiceConnection
+ OracleContainer oracleContainer() {
+ return new OracleContainer("gvenzl/oracle-free:23-slim");
+ }
+
+}
\ No newline at end of file