Skip to content

Commit cf2c3c5

Browse files
authored
Extract VectorStoreRetriever interface from VectorStore (#3827)
- Created new VectorStoreRetriever functional interface for read-only operations - Modified VectorStore to extend VectorStoreRetriever - Removed duplicate similaritySearch methods from VectorStore - Updated API documentation to reflect the interface separation - Added package-info.java documentation for clarity This change follows the principle of least privilege by providing a read-only interface for retrieval operations while maintaining the full functionality in VectorStore. Fixes #1290 Signed-off-by: Mark Pollack <[email protected]>
1 parent ffad572 commit cf2c3c5

File tree

4 files changed

+268
-42
lines changed

4 files changed

+268
-42
lines changed

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs.adoc

Lines changed: 170 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,32 @@ The last section is intended to demystify the underlying approach of similarity
2222
== API Overview
2323
This section serves as a guide to the `VectorStore` interface and its associated classes within the Spring AI framework.
2424

25-
Spring AI offers an abstracted API for interacting with vector databases through the `VectorStore` interface.
25+
Spring AI offers an abstracted API for interacting with vector databases through the `VectorStore` interface and its read-only counterpart, the `VectorStoreRetriever` interface.
2626

27-
Here is the `VectorStore` interface definition:
27+
=== VectorStoreRetriever Interface
28+
29+
Spring AI provides a read-only interface called `VectorStoreRetriever` that exposes only the document retrieval functionality:
30+
31+
```java
32+
@FunctionalInterface
33+
public interface VectorStoreRetriever {
34+
35+
List<Document> similaritySearch(SearchRequest request);
36+
37+
default List<Document> similaritySearch(String query) {
38+
return this.similaritySearch(SearchRequest.builder().query(query).build());
39+
}
40+
}
41+
```
42+
43+
This functional interface is designed for use cases where you only need to retrieve documents from a vector store without performing any mutation operations. It follows the principle of least privilege by exposing only the necessary functionality for document retrieval.
44+
45+
=== VectorStore Interface
46+
47+
The `VectorStore` interface extends `VectorStoreRetriever` and adds mutation capabilities:
2848

2949
```java
30-
public interface VectorStore extends DocumentWriter {
50+
public interface VectorStore extends DocumentWriter, VectorStoreRetriever {
3151

3252
default String getName() {
3353
return this.getClass().getSimpleName();
@@ -41,17 +61,15 @@ public interface VectorStore extends DocumentWriter {
4161

4262
default void delete(String filterExpression) { ... };
4363

44-
List<Document> similaritySearch(String query);
45-
46-
List<Document> similaritySearch(SearchRequest request);
47-
4864
default <T> Optional<T> getNativeClient() {
4965
return Optional.empty();
5066
}
5167
}
5268
```
5369

54-
and the related `SearchRequest` builder:
70+
The `VectorStore` interface combines both read and write operations, allowing you to add, delete, and search for documents in a vector database.
71+
72+
=== SearchRequest Builder
5573

5674
```java
5775
public class SearchRequest {
@@ -392,32 +410,163 @@ For example, with OpenAI's ChatGPT, we use the `OpenAiEmbeddingModel` and a mode
392410

393411
The Spring Boot starter's auto-configuration for OpenAI makes an implementation of `EmbeddingModel` available in the Spring application context for dependency injection.
394412

395-
The general usage of loading data into a vector store is something you would do in a batch-like job, by first loading data into Spring AI's `Document` class and then calling the `save` method.
413+
=== Writing to a Vector Store
414+
415+
The general usage of loading data into a vector store is something you would do in a batch-like job, by first loading data into Spring AI's `Document` class and then calling the `add` method on the `VectorStore` interface.
396416

397417
Given a `String` reference to a source file that represents a JSON file with data we want to load into the vector database, we use Spring AI's `JsonReader` to load specific fields in the JSON, which splits them up into small pieces and then passes those small pieces to the vector store implementation.
398418
The `VectorStore` implementation computes the embeddings and stores the JSON and the embedding in the vector database:
399419

400420
```java
401-
@Autowired
402-
VectorStore vectorStore;
403-
404-
void load(String sourceFile) {
405-
JsonReader jsonReader = new JsonReader(new FileSystemResource(sourceFile),
406-
"price", "name", "shortDescription", "description", "tags");
407-
List<Document> documents = jsonReader.get();
408-
this.vectorStore.add(documents);
409-
}
421+
@Autowired
422+
VectorStore vectorStore;
423+
424+
void load(String sourceFile) {
425+
JsonReader jsonReader = new JsonReader(new FileSystemResource(sourceFile),
426+
"price", "name", "shortDescription", "description", "tags");
427+
List<Document> documents = jsonReader.get();
428+
this.vectorStore.add(documents);
429+
}
410430
```
411431

412-
Later, when a user question is passed into the AI model, a similarity search is done to retrieve similar documents, which are then "'stuffed'" into the prompt as context for the user's question.
432+
=== Reading from a Vector Store
433+
434+
Later, when a user question is passed into the AI model, a similarity search is done to retrieve similar documents, which are then "stuffed" into the prompt as context for the user's question.
435+
436+
For read-only operations, you can use either the `VectorStore` interface or the more focused `VectorStoreRetriever` interface:
413437

414438
```java
415-
String question = <question from user>
416-
List<Document> similarDocuments = store.similaritySearch(this.question);
439+
@Autowired
440+
VectorStoreRetriever retriever; // Could also use VectorStore here
441+
442+
String question = "<question from user>";
443+
List<Document> similarDocuments = retriever.similaritySearch(question);
444+
445+
// Or with more specific search parameters
446+
SearchRequest request = SearchRequest.builder()
447+
.query(question)
448+
.topK(5) // Return top 5 results
449+
.similarityThreshold(0.7) // Only return results with similarity score >= 0.7
450+
.build();
451+
452+
List<Document> filteredDocuments = retriever.similaritySearch(request);
417453
```
418454

419455
Additional options can be passed into the `similaritySearch` method to define how many documents to retrieve and a threshold of the similarity search.
420456

457+
=== Separation of Read and Write Operations
458+
459+
Using the separate interfaces allows you to clearly define which components need write access and which only need read access:
460+
461+
```java
462+
// Write operations in a service that needs full access
463+
@Service
464+
class DocumentIndexer {
465+
private final VectorStore vectorStore;
466+
467+
DocumentIndexer(VectorStore vectorStore) {
468+
this.vectorStore = vectorStore;
469+
}
470+
471+
public void indexDocuments(List<Document> documents) {
472+
vectorStore.add(documents);
473+
}
474+
}
475+
476+
// Read-only operations in a service that only needs retrieval
477+
@Service
478+
class DocumentRetriever {
479+
private final VectorStoreRetriever retriever;
480+
481+
DocumentRetriever(VectorStoreRetriever retriever) {
482+
this.retriever = retriever;
483+
}
484+
485+
public List<Document> findSimilar(String query) {
486+
return retriever.similaritySearch(query);
487+
}
488+
}
489+
```
490+
491+
This separation of concerns helps create more maintainable and secure applications by limiting access to mutation operations only to components that truly need them.
492+
493+
== Retrieval Operations with VectorStoreRetriever
494+
495+
The `VectorStoreRetriever` interface provides a read-only view of a vector store, exposing only the similarity search functionality. This follows the principle of least privilege and is particularly useful in RAG (Retrieval-Augmented Generation) applications where you only need to retrieve documents without modifying the underlying data.
496+
497+
=== Benefits of Using VectorStoreRetriever
498+
499+
1. **Separation of Concerns**: Clearly separates read operations from write operations.
500+
2. **Interface Segregation**: Clients that only need retrieval functionality aren't exposed to mutation methods.
501+
3. **Functional Interface**: Can be implemented with lambda expressions or method references for simple use cases.
502+
4. **Reduced Dependencies**: Components that only need to perform searches don't need to depend on the full `VectorStore` interface.
503+
504+
=== Example Usage
505+
506+
You can use `VectorStoreRetriever` directly when you only need to perform similarity searches:
507+
508+
```java
509+
@Service
510+
public class DocumentRetrievalService {
511+
512+
private final VectorStoreRetriever retriever;
513+
514+
public DocumentRetrievalService(VectorStoreRetriever retriever) {
515+
this.retriever = retriever;
516+
}
517+
518+
public List<Document> findSimilarDocuments(String query) {
519+
return retriever.similaritySearch(query);
520+
}
521+
522+
public List<Document> findSimilarDocumentsWithFilters(String query, String country) {
523+
SearchRequest request = SearchRequest.builder()
524+
.query(query)
525+
.topK(5)
526+
.filterExpression("country == '" + country + "'")
527+
.build();
528+
529+
return retriever.similaritySearch(request);
530+
}
531+
}
532+
```
533+
534+
In this example, the service only depends on the `VectorStoreRetriever` interface, making it clear that it only performs retrieval operations and doesn't modify the vector store.
535+
536+
=== Integration with RAG Applications
537+
538+
The `VectorStoreRetriever` interface is particularly useful in RAG applications, where you need to retrieve relevant documents to provide context for an AI model:
539+
540+
```java
541+
@Service
542+
public class RagService {
543+
544+
private final VectorStoreRetriever retriever;
545+
private final ChatModel chatModel;
546+
547+
public RagService(VectorStoreRetriever retriever, ChatModel chatModel) {
548+
this.retriever = retriever;
549+
this.chatModel = chatModel;
550+
}
551+
552+
public String generateResponse(String userQuery) {
553+
// Retrieve relevant documents
554+
List<Document> relevantDocs = retriever.similaritySearch(userQuery);
555+
556+
// Extract content from documents to use as context
557+
String context = relevantDocs.stream()
558+
.map(Document::getContent)
559+
.collect(Collectors.joining("\n\n"));
560+
561+
// Generate response using the retrieved context
562+
String prompt = "Context information:\n" + context + "\n\nUser query: " + userQuery;
563+
return chatModel.generate(prompt);
564+
}
565+
}
566+
```
567+
568+
This pattern allows for a clean separation between the retrieval component and the generation component in RAG applications.
569+
421570
== Metadata Filters [[metadata-filters]]
422571

423572
This section describes various filters that you can use against the results of a query.

spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStore.java

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
* This interface allows for adding, deleting, and searching documents based on their
3838
* similarity to a given query.
3939
*/
40-
public interface VectorStore extends DocumentWriter {
40+
public interface VectorStore extends DocumentWriter, VectorStoreRetriever {
4141

4242
default String getName() {
4343
return this.getClass().getSimpleName();
@@ -83,26 +83,6 @@ default void delete(String filterExpression) {
8383
this.delete(textExpression);
8484
}
8585

86-
/**
87-
* Retrieves documents by query embedding similarity and metadata filters to retrieve
88-
* exactly the number of nearest-neighbor results that match the request criteria.
89-
* @param request Search request for set search parameters, such as the query text,
90-
* topK, similarity threshold and metadata filter expressions.
91-
* @return Returns documents th match the query request conditions.
92-
*/
93-
List<Document> similaritySearch(SearchRequest request);
94-
95-
/**
96-
* Retrieves documents by query embedding similarity using the default
97-
* {@link SearchRequest}'s' search criteria.
98-
* @param query Text to use for embedding similarity comparison.
99-
* @return Returns a list of documents that have embeddings similar to the query text
100-
* embedding.
101-
*/
102-
default List<Document> similaritySearch(String query) {
103-
return this.similaritySearch(SearchRequest.builder().query(query).build());
104-
}
105-
10686
/**
10787
* Returns the native client if available in this vector store implementation.
10888
*
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2023-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.vectorstore;
18+
19+
import java.util.List;
20+
21+
import org.springframework.ai.document.Document;
22+
23+
/**
24+
* A functional interface that provides read-only access to vector store retrieval
25+
* operations. This interface extracts only the document retrieval functionality from
26+
* {@link VectorStore}, ensuring that mutation operations (add, delete) are not exposed.
27+
*
28+
* <p>
29+
* This is useful when you want to provide retrieval-only access to a vector store,
30+
* following the principle of least privilege by not exposing write operations.
31+
*
32+
* @author Mark Pollack
33+
* @since 1.0.0
34+
*/
35+
@FunctionalInterface
36+
public interface VectorStoreRetriever {
37+
38+
/**
39+
* Retrieves documents by query embedding similarity and metadata filters to retrieve
40+
* exactly the number of nearest-neighbor results that match the request criteria.
41+
* @param request Search request for set search parameters, such as the query text,
42+
* topK, similarity threshold and metadata filter expressions.
43+
* @return Returns documents that match the query request conditions.
44+
*/
45+
List<Document> similaritySearch(SearchRequest request);
46+
47+
/**
48+
* Retrieves documents by query embedding similarity using the default
49+
* {@link SearchRequest}'s search criteria.
50+
* @param query Text to use for embedding similarity comparison.
51+
* @return Returns a list of documents that have embeddings similar to the query text
52+
* embedding.
53+
*/
54+
default List<Document> similaritySearch(String query) {
55+
return this.similaritySearch(SearchRequest.builder().query(query).build());
56+
}
57+
58+
}

spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/package-info.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,45 @@
1414
* limitations under the License.
1515
*/
1616

17+
/**
18+
* Provides interfaces and implementations for working with vector databases in Spring AI.
19+
* <p>
20+
* Vector databases store embeddings (numerical vector representations) of data along with
21+
* the original content and metadata, enabling similarity search operations. This package
22+
* contains two primary interfaces:
23+
* <ul>
24+
* <li>{@link org.springframework.ai.vectorstore.VectorStoreRetriever} - A read-only
25+
* functional interface that provides similarity search capabilities for retrieving
26+
* documents from a vector store. This interface follows the principle of least privilege
27+
* by exposing only retrieval operations.</li>
28+
* <li>{@link org.springframework.ai.vectorstore.VectorStore} - Extends
29+
* VectorStoreRetriever and adds mutation operations (add, delete) for managing documents
30+
* in a vector store. This interface provides complete access to vector database
31+
* functionality.</li>
32+
* </ul>
33+
* <p>
34+
* The package also includes supporting classes such as:
35+
* <ul>
36+
* <li>{@link org.springframework.ai.vectorstore.SearchRequest} - Configures similarity
37+
* search parameters including query text, result limits, similarity thresholds, and
38+
* metadata filters.</li>
39+
* <li>{@link org.springframework.ai.vectorstore.filter.Filter} - Provides filtering
40+
* capabilities for metadata-based document selection (located in the filter
41+
* subpackage).</li>
42+
* </ul>
43+
* <p>
44+
* This package is designed to support Retrieval Augmented Generation (RAG) applications
45+
* by providing a clean separation between read and write operations, allowing components
46+
* to access only the functionality they need.
47+
*
48+
* @see org.springframework.ai.vectorstore.VectorStoreRetriever
49+
* @see org.springframework.ai.vectorstore.VectorStore
50+
* @see org.springframework.ai.vectorstore.SearchRequest
51+
* @see org.springframework.ai.vectorstore.filter.Filter
52+
*
53+
* @author Mark Pollack
54+
* @since 1.0.0
55+
*/
1756
@NonNullApi
1857
@NonNullFields
1958
package org.springframework.ai.vectorstore;

0 commit comments

Comments
 (0)