Skip to content

Commit

Permalink
Update document for SearchRequest builder changes
Browse files Browse the repository at this point in the history
  • Loading branch information
ilayaperumalg authored and Mark Pollack committed Dec 21, 2024
1 parent 6477c0c commit a55d09a
Show file tree
Hide file tree
Showing 23 changed files with 203 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
The Spring AI Advisors API provides a flexible and powerful way to intercept, modify, and enhance AI-driven interactions in your Spring applications.
By leveraging the Advisors API, developers can create more sophisticated, reusable, and maintainable AI components.

The key benefits include encapsulating recurring Generative AI patterns, transforming data sent to and from Language Models (LLMs), and providing portability across various models and use cases.
The key benefits include encapsulating recurring Generative AI patterns, transforming data sent to and from Large Language Models (LLMs), and providing portability across various models and use cases.

You can configure existing advisors using the xref:api/chatclient.adoc#_advisor_configuration_in_chatclient[ChatClient API] as shown in the following example:

Expand All @@ -14,7 +14,7 @@ You can configure existing advisors using the xref:api/chatclient.adoc#_advisor_
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory), // chat-memory advisor
new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()) // RAG advisor
new QuestionAnswerAdvisor(vectorStore) // RAG advisor
)
.build();
Expand All @@ -31,6 +31,8 @@ It is recommend to register the advisors at build time using builder's `defaultA

Advisors also participate in the Observability stack, so you can view metrics and traces related to their execution.

xref:ROOT:api/retrieval-augmented-generation.adoc#_questionansweradvisor[Learn about Question Answer Advisor]

== Core Components

The API consists of `CallAroundAdvisor` and `CallAroundAdvisorChain` for non-streaming scenarios, and `StreamAroundAdvisor` and `StreamAroundAdvisorChain` for streaming scenarios.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ ChatClient.builder(chatModel)
.prompt()
.advisors(
new MessageChatMemoryAdvisor(chatMemory),
new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults())
new QuestionAnswerAdvisor(vectorStore)
)
.user(userText)
.call()
Expand All @@ -364,6 +364,8 @@ ChatClient.builder(chatModel)

In this configuration, the `MessageChatMemoryAdvisor` will be executed first, adding the conversation history to the prompt. Then, the `QuestionAnswerAdvisor` will perform its search based on the user's question and the added conversation history, potentially providing more relevant results.

xref:ROOT:api/retrieval-augmented-generation.adoc#_questionansweradvisor[Learn about Question Answer Advisor]

=== Retrieval Augmented Generation

Refer to the xref:_retrieval_augmented_generation[Retrieval Augmented Generation] guide.
Expand Down Expand Up @@ -424,7 +426,7 @@ public class CustomerSupportAssistant {
""")
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory), // CHAT MEMORY
new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()), // RAG
new QuestionAnswerAdvisor(vectorStore), // RAG
new SimpleLoggerAdvisor())
.defaultFunctions("getBookingDetails", "changeBooking", "cancelBooking") // FUNCTION CALLING
.build();
Expand All @@ -443,7 +445,7 @@ public class CustomerSupportAssistant {
}
----


xref:ROOT:api/retrieval-augmented-generation.adoc#_questionansweradvisor[Learn about Question Answer Advisor]

=== Logging

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,36 @@ Assuming you have already loaded data into a `VectorStore`, you can perform Retr
----
ChatResponse response = ChatClient.builder(chatModel)
.build().prompt()
.advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()))
.advisors(new QuestionAnswerAdvisor(vectorStore)
.user(userText)
.call()
.chatResponse();
----

In this example, the `SearchRequest.defaults()` will perform a similarity search over all documents in the Vector Database.
In this example, the `QuestionAnswerAdvisor` will perform a similarity search over all documents in the Vector Database.
To restrict the types of documents that are searched, the `SearchRequest` takes an SQL like filter expression that is portable across all `VectorStores`.

This filter expression can be configured when creating the `QuestionAnswerAdvisor` and hence will always apply to all `ChatClient` requests or it can be provided at runtime per request.

Here is how to create an instance of `QuestionAnswerAdvisor` where the threshold is `0.8` and to return the top `6` reulsts.


[source,java]
----
var qaAdvisor = new QuestionAnswerAdvisor(this.vectorStore,
SearchRequest.builder().similarityThreshold(0.8d).topK(6).build());
----



==== Dynamic Filter Expressions

Update the `SearchRequest` filter expression at runtime using the `FILTER_EXPRESSION` advisor context parameter:

[source,java]
----
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()))
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.builder().build()))
.build();
// Update filter expression at runtime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ void testEvaluation() {
ChatResponse response = ChatClient.builder(chatModel)
.build().prompt()
.advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()))
.advisors(new QuestionAnswerAdvisor(vectorStore))
.user(userText)
.call()
.chatResponse();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,69 @@ and the related `SearchRequest` builder:
```java
public class SearchRequest {

public final String query;
private int topK = 4;
private double similarityThreshold = SIMILARITY_THRESHOLD_ALL;
private Filter.Expression filterExpression;
public static final double SIMILARITY_THRESHOLD_ACCEPT_ALL = 0.0;

public static final int DEFAULT_TOP_K = 4;

private String query = "";

public static SearchRequest query(String query) { return new SearchRequest(query); }
private int topK = DEFAULT_TOP_K;

private SearchRequest(String query) { this.query = query; }
private double similarityThreshold = SIMILARITY_THRESHOLD_ACCEPT_ALL;

@Nullable
private Filter.Expression filterExpression;

public SearchRequest topK(int topK) {...}
public SearchRequest similarityThreshold(double threshold) {...}
public SearchRequest similarityThresholdAll() {...}
public SearchRequest filterExpression(Filter.Expression expression) {...}
public SearchRequest filterExpression(String textExpression) {...}
public static Builder from(SearchRequest originalSearchRequest) {
return builder().query(originalSearchRequest.getQuery())
.topK(originalSearchRequest.getTopK())
.similarityThreshold(originalSearchRequest.getSimilarityThreshold())
.filterExpression(originalSearchRequest.getFilterExpression());
}

public static class Builder {

private final SearchRequest searchRequest = new SearchRequest();

public Builder query(String query) {
Assert.notNull(query, "Query can not be null.");
this.searchRequest.query = query;
return this;
}

public Builder topK(int topK) {
Assert.isTrue(topK >= 0, "TopK should be positive.");
this.searchRequest.topK = topK;
return this;
}

public Builder similarityThreshold(double threshold) {
Assert.isTrue(threshold >= 0 && threshold <= 1, "Similarity threshold must be in [0,1] range.");
this.searchRequest.similarityThreshold = threshold;
return this;
}

public Builder similarityThresholdAll() {
this.searchRequest.similarityThreshold = 0.0;
return this;
}

public Builder filterExpression(@Nullable Filter.Expression expression) {
this.searchRequest.filterExpression = expression;
return this;
}

public Builder filterExpression(@Nullable String textExpression) {
this.searchRequest.filterExpression = (textExpression != null)
? new FilterExpressionTextParser().parse(textExpression) : null;
return this;
}

public SearchRequest build() {
return this.searchRequest;
}

}

public String getQuery() {...}
public int getTopK() {...}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,17 +142,17 @@ And retrieve documents similar to a query:
[source,java]
----
List<Document> results = vectorStore.similaritySearch(
SearchRequest.query("Spring").topK(5));
SearchRequest.builder().query("Spring").topK(5).build());
----

You can also limit results based on a similarity threshold:

[source,java]
----
List<Document> results = vectorStore.similaritySearch(
SearchRequest.query("Spring")
SearchRequest.builder().query("Spring")
.topK(5)
.similarityThreshold(0.5d));
.similarityThreshold(0.5d).build());
----

=== Advanced Configuration
Expand Down Expand Up @@ -192,9 +192,9 @@ For example, you can use either the text expression language:
[source,java]
----
vectorStore.similaritySearch(
SearchRequest.query("The World")
SearchRequest.builder().query("The World")
.topK(5)
.filterExpression("country in ['UK', 'NL'] && year >= 2020"));
.filterExpression("country in ['UK', 'NL'] && year >= 2020").build());
----

or programmatically using the expression DSL:
Expand All @@ -208,9 +208,9 @@ Filter.Expression f = new FilterExpressionBuilder()
).build();
vectorStore.similaritySearch(
SearchRequest.query("The World")
SearchRequest.builder().query("The World")
.topK(5)
.filterExpression(f));
.filterExpression(f).build());
----

The portable filter expressions get automatically converted into link:https://cassandra.apache.org/doc/latest/cassandra/developing/cql/index.html[CQL queries].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public class DemoApplication implements CommandLineRunner {
Document document1 = new Document(UUID.randomUUID().toString(), "Sample content1", Map.of("key1", "value1"));
Document document2 = new Document(UUID.randomUUID().toString(), "Sample content2", Map.of("key2", "value2"));
this.vectorStore.add(List.of(document1, document2));
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.query("Sample content").topK(1));
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.builder().query("Sample content").topK(1).build());

log.info("Search results: {}", results);

Expand Down Expand Up @@ -139,9 +139,9 @@ Document document2 = new Document("2", "A document about the Netherlands", this.
vectorStore.add(List.of(document1, document2));
FilterExpressionBuilder builder = new FilterExpressionBuilder();
List<Document> results = vectorStore.similaritySearch(SearchRequest.query("The World")
List<Document> results = vectorStore.similaritySearch(SearchRequest.builder().query("The World")
.topK(10)
.filterExpression((this.builder.in("country", "UK", "NL")).build()));
.filterExpression((this.builder.in("country", "UK", "NL")).build()).build());
----

== Setting up Azure Cosmos DB Vector Store without Auto Configuration
Expand Down Expand Up @@ -192,7 +192,7 @@ public class DemoApplication implements CommandLineRunner {
Document document2 = new Document(UUID.randomUUID().toString(), "Sample content2", Map.of("key2", "value2"));
this.vectorStore.add(List.of(document1, document2));

List<Document> results = this.vectorStore.similaritySearch(SearchRequest.query("Sample content").topK(1));
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.builder().query("Sample content").topK(1).build());
log.info("Search results: {}", results);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ And finally, retrieve documents similar to a query:
[source,java]
----
List<Document> results = vectorStore.similaritySearch(
SearchRequest
SearchRequest.builder()
.query("Spring")
.topK(5));
.topK(5).build());
----

If all goes well, you should retrieve the document containing the text "Spring AI rocks!!".
Expand All @@ -185,11 +185,11 @@ For example, you can use either the text expression language:
[source,java]
----
vectorStore.similaritySearch(
SearchRequest
SearchRequest.builder()
.query("The World")
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression("country in ['UK', 'NL'] && year >= 2020"));
.filterExpression("country in ['UK', 'NL'] && year >= 2020").build());
----

or programmatically using the expression DSL:
Expand All @@ -199,13 +199,13 @@ or programmatically using the expression DSL:
FilterExpressionBuilder b = new FilterExpressionBuilder();
vectorStore.similaritySearch(
SearchRequest
SearchRequest.builder()
.query("The World")
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression(b.and(
b.in("country", "UK", "NL"),
b.gte("year", 2020)).build()));
b.gte("year", 2020)).build()).build());
----

The portable filter expressions get automatically converted into the proprietary Azure Search link:https://learn.microsoft.com/en-us/azure/search/search-query-odata-filter[OData filters]. For example, the following portable filter expression:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ List <Document> documents = List.of(
vectorStore.add(documents);
// Retrieve documents similar to a query
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.query("Spring").topK(5));
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.builder().query("Spring").topK(5).build());
----

=== Configuration properties
Expand Down Expand Up @@ -137,11 +137,11 @@ For example, you can use either the text expression language:
[source,java]
----
vectorStore.similaritySearch(
SearchRequest.defaults()
.queryString("The World")
SearchRequest.builder()
.query("The World")
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression("author in ['john', 'jill'] && article_type == 'blog'"));
.filterExpression("author in ['john', 'jill'] && article_type == 'blog'").build());
----

or programmatically using the `Filter.Expression` DSL:
Expand All @@ -150,13 +150,13 @@ or programmatically using the `Filter.Expression` DSL:
----
FilterExpressionBuilder b = new FilterExpressionBuilder();
vectorStore.similaritySearch(SearchRequest.defaults()
.queryString("The World")
vectorStore.similaritySearch(SearchRequest.builder()
.query("The World")
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression(b.and(
b.in("john", "jill"),
b.eq("article_type", "blog")).build()));
b.eq("article_type", "blog")).build()).build());
----

NOTE: Those (portable) filter expressions get automatically converted into the proprietary Chroma `where` link:https://docs.trychroma.com/usage-guide#using-where-filters[filter expressions].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ List <Document> documents = List.of(
vectorStore.add(documents);
// Retrieve documents similar to a query
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.query("Spring").topK(5));
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.builder().query("Spring").topK(5).build());
----

[[elasticsearchvector-properties]]
Expand Down Expand Up @@ -171,11 +171,11 @@ For example, you can use either the text expression language:

[source,java]
----
vectorStore.similaritySearch(SearchRequest.defaults()
.queryString("The World")
vectorStore.similaritySearch(SearchRequest.builder()
.query("The World")
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression("author in ['john', 'jill'] && 'article_type' == 'blog'"));
.filterExpression("author in ['john', 'jill'] && 'article_type' == 'blog'").build());
----

or programmatically using the `Filter.Expression` DSL:
Expand All @@ -184,13 +184,13 @@ or programmatically using the `Filter.Expression` DSL:
----
FilterExpressionBuilder b = new FilterExpressionBuilder();
vectorStore.similaritySearch(SearchRequest.defaults()
.queryString("The World")
vectorStore.similaritySearch(SearchRequest.builder()
.query("The World")
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression(b.and(
b.in("author", "john", "jill"),
b.eq("article_type", "blog")).build()));
b.eq("article_type", "blog")).build()).build());
----

NOTE: Those (portable) filter expressions get automatically converted into the proprietary Elasticsearch link:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html[Query string query].
Expand Down
Loading

0 comments on commit a55d09a

Please sign in to comment.