Add builder pattern and order parameter to advisors

- Introduce builder pattern for MessageChatMemoryAdvisor, PromptChatMemoryAdvisor,
   QuestionAnswerAdvisor, SafeGuardAdvisor, and VectorStoreChatMemoryAdvisor.
 - Add 'order' parameter to control advisor execution priority
 - Modify constructors to include the new 'order' parameter
 - Update AbstractChatMemoryAdvisor to support the new 'order' parameter
 - Update docs
This commit is contained in:
Christian Tzolov
2024-10-08 12:52:35 +02:00
committed by Mark Pollack
parent 300561af53
commit 13709f9542
6 changed files with 197 additions and 14 deletions

View File

@@ -26,6 +26,7 @@ import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
import org.springframework.util.Assert;
import org.stringtemplate.v4.compiler.CodeGenerator.includeExpr_return;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -56,12 +57,20 @@ public abstract class AbstractChatMemoryAdvisor<T> implements CallAroundAdvisor,
private final boolean protectFromBlocking;
private final int order;
protected AbstractChatMemoryAdvisor(T chatMemory) {
this(chatMemory, DEFAULT_CHAT_MEMORY_CONVERSATION_ID, DEFAULT_CHAT_MEMORY_RESPONSE_SIZE, true);
}
protected AbstractChatMemoryAdvisor(T chatMemory, String defaultConversationId, int defaultChatMemoryRetrieveSize,
boolean protectFromBlocking) {
this(chatMemory, defaultConversationId, defaultChatMemoryRetrieveSize, protectFromBlocking,
Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER);
}
protected AbstractChatMemoryAdvisor(T chatMemory, String defaultConversationId, int defaultChatMemoryRetrieveSize,
boolean protectFromBlocking, int order) {
Assert.notNull(chatMemory, "The chatMemory must not be null!");
Assert.hasText(defaultConversationId, "The conversationId must not be empty!");
@@ -71,6 +80,7 @@ public abstract class AbstractChatMemoryAdvisor<T> implements CallAroundAdvisor,
this.defaultConversationId = defaultConversationId;
this.defaultChatMemoryRetrieveSize = defaultChatMemoryRetrieveSize;
this.protectFromBlocking = protectFromBlocking;
this.order = order;
}
@Override
@@ -80,11 +90,11 @@ public abstract class AbstractChatMemoryAdvisor<T> implements CallAroundAdvisor,
@Override
public int getOrder() {
// The (Ordered.HIGHEST_PRECEDENCE + 1000) value ensures this order has lower
// priority (e.g. precedences) than the internal Spring AI advisors. It leaves
// room (1000 slots) for the user to plug in their own advisors with higher
// by default the (Ordered.HIGHEST_PRECEDENCE + 1000) value ensures this order has
// lower priority (e.g. precedences) than the internal Spring AI advisors. It
// leaves room (1000 slots) for the user to plug in their own advisors with higher
// priority.
return Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER;
return this.order;
}
protected T getChatMemoryStore() {
@@ -118,5 +128,43 @@ public abstract class AbstractChatMemoryAdvisor<T> implements CallAroundAdvisor,
: chain.nextAroundStream(beforeAdvise.apply(advisedRequest));
}
public static abstract class AbstractBuilder<T> {
protected String conversationId = DEFAULT_CHAT_MEMORY_CONVERSATION_ID;
protected int chatMemoryRetrieveSize = DEFAULT_CHAT_MEMORY_RESPONSE_SIZE;
protected boolean protectFromBlocking = true;
protected int order = Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER;
protected T chatMemory;
protected AbstractBuilder(T chatMemory) {
this.chatMemory = chatMemory;
}
public AbstractBuilder withConversationId(String conversationId) {
this.conversationId = conversationId;
return this;
}
public AbstractBuilder withChatMemoryRetrieveSize(int chatMemoryRetrieveSize) {
this.chatMemoryRetrieveSize = chatMemoryRetrieveSize;
return this;
}
public AbstractBuilder withProtectFromBlocking(boolean protectFromBlocking) {
this.protectFromBlocking = protectFromBlocking;
return this;
}
public AbstractBuilder withOrder(int order) {
this.order = order;
return this;
}
abstract public <T> AbstractChatMemoryAdvisor<T> build();
}
}

View File

@@ -21,6 +21,7 @@ import java.util.List;
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
import org.springframework.ai.chat.memory.ChatMemory;
@@ -43,7 +44,12 @@ public class MessageChatMemoryAdvisor extends AbstractChatMemoryAdvisor<ChatMemo
}
public MessageChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize) {
super(chatMemory, defaultConversationId, chatHistoryWindowSize, true);
this(chatMemory, defaultConversationId, chatHistoryWindowSize, Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER);
}
public MessageChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
int order) {
super(chatMemory, defaultConversationId, chatHistoryWindowSize, true, order);
}
@Override
@@ -101,4 +107,21 @@ public class MessageChatMemoryAdvisor extends AbstractChatMemoryAdvisor<ChatMemo
this.getChatMemoryStore().add(this.doGetConversationId(advisedResponse.adviseContext()), assistantMessages);
}
public static Builder builder(ChatMemory chatMemory) {
return new Builder(chatMemory);
}
public static class Builder extends AbstractChatMemoryAdvisor.AbstractBuilder<ChatMemory> {
protected Builder(ChatMemory chatMemory) {
super(chatMemory);
}
public MessageChatMemoryAdvisor build() {
return new MessageChatMemoryAdvisor(this.chatMemory, this.conversationId, this.chatMemoryRetrieveSize,
this.order);
}
}
}

View File

@@ -23,6 +23,7 @@ import java.util.stream.Collectors;
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
import org.springframework.ai.chat.memory.ChatMemory;
@@ -66,7 +67,13 @@ public class PromptChatMemoryAdvisor extends AbstractChatMemoryAdvisor<ChatMemor
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
String systemTextAdvise) {
super(chatMemory, defaultConversationId, chatHistoryWindowSize, true);
this(chatMemory, defaultConversationId, chatHistoryWindowSize, systemTextAdvise,
Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER);
}
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
String systemTextAdvise, int order) {
super(chatMemory, defaultConversationId, chatHistoryWindowSize, true, order);
this.systemTextAdvise = systemTextAdvise;
}
@@ -133,4 +140,28 @@ public class PromptChatMemoryAdvisor extends AbstractChatMemoryAdvisor<ChatMemor
this.getChatMemoryStore().add(this.doGetConversationId(advisedResponse.adviseContext()), assistantMessages);
}
public static Builder builder(ChatMemory chatMemory) {
return new Builder(chatMemory);
}
public static class Builder extends AbstractChatMemoryAdvisor.AbstractBuilder<ChatMemory> {
private String systemTextAdvise = DEFAULT_SYSTEM_TEXT_ADVISE;
protected Builder(ChatMemory chatMemory) {
super(chatMemory);
}
public Builder withSystemTextAdvise(String systemTextAdvise) {
this.systemTextAdvise = systemTextAdvise;
return this;
}
public PromptChatMemoryAdvisor build() {
return new PromptChatMemoryAdvisor(this.chatMemory, this.conversationId, this.chatMemoryRetrieveSize,
this.systemTextAdvise, this.order);
}
}
}

View File

@@ -24,8 +24,8 @@ import java.util.stream.Collectors;
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
import org.springframework.ai.chat.model.ChatResponse;
@@ -61,6 +61,8 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
the user that you can't answer the question.
""";
private static final int DEFAULT_ORDER = 0;
private final VectorStore vectorStore;
private final String userTextAdvise;
@@ -73,6 +75,8 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
private final boolean protectFromBlocking;
private final int order;
/**
* The QuestionAnswerAdvisor retrieves context information from a Vector Store and
* combines it with the user's text.
@@ -121,6 +125,25 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
*/
public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest, String userTextAdvise,
boolean protectFromBlocking) {
this(vectorStore, searchRequest, userTextAdvise, protectFromBlocking, DEFAULT_ORDER);
}
/**
* The QuestionAnswerAdvisor retrieves context information from a Vector Store and
* combines it with the user's text.
* @param vectorStore The vector store to use
* @param searchRequest The search request defined using the portable filter
* expression syntax
* @param userTextAdvise the user text to append to the existing user prompt. The text
* should contain a placeholder named "question_answer_context".
* @param protectFromBlocking if true the advisor will protect the execution from
* blocking threads. If false the advisor will not protect the execution from blocking
* threads. This is useful when the advisor is used in a non-blocking environment. It
* is true by default.
* @param order the order of the advisor.
*/
public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest, String userTextAdvise,
boolean protectFromBlocking, int order) {
Assert.notNull(vectorStore, "The vectorStore must not be null!");
Assert.notNull(searchRequest, "The searchRequest must not be null!");
@@ -130,6 +153,7 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
this.searchRequest = searchRequest;
this.userTextAdvise = userTextAdvise;
this.protectFromBlocking = protectFromBlocking;
this.order = order;
}
@Override
@@ -139,7 +163,7 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
@Override
public int getOrder() {
return 0;
return this.order;
}
@Override
@@ -249,6 +273,8 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
private boolean protectFromBlocking = true;
private int order = DEFAULT_ORDER;
private Builder(VectorStore vectorStore) {
Assert.notNull(vectorStore, "The vectorStore must not be null!");
this.vectorStore = vectorStore;
@@ -271,9 +297,14 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
return this;
}
public Builder withOrder(int order) {
this.order = order;
return this;
}
public QuestionAnswerAdvisor build() {
return new QuestionAnswerAdvisor(this.vectorStore, this.searchRequest, this.userTextAdvise,
this.protectFromBlocking);
this.protectFromBlocking, this.order);
}
}

View File

@@ -23,6 +23,7 @@ import java.util.stream.Collectors;
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
import org.springframework.ai.chat.messages.AssistantMessage;
@@ -78,7 +79,23 @@ public class VectorStoreChatMemoryAdvisor extends AbstractChatMemoryAdvisor<Vect
public VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId,
int chatHistoryWindowSize, String systemTextAdvise) {
super(vectorStore, defaultConversationId, chatHistoryWindowSize, true);
this(vectorStore, defaultConversationId, chatHistoryWindowSize, systemTextAdvise,
Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER);
}
/**
* Constructor for VectorStoreChatMemoryAdvisor.
* @param vectorStore the vector store instance used for managing and querying
* documents.
* @param defaultConversationId the default conversation ID used if none is provided
* in the context.
* @param chatHistoryWindowSize the window size for the chat history retrieval.
* @param systemTextAdvise the system text advice used for the chat advisor system.
* @param order the order of precedence for this advisor in the chain.
*/
public VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId,
int chatHistoryWindowSize, String systemTextAdvise, int order) {
super(vectorStore, defaultConversationId, chatHistoryWindowSize, true, order);
this.systemTextAdvise = systemTextAdvise;
}
@@ -168,4 +185,29 @@ public class VectorStoreChatMemoryAdvisor extends AbstractChatMemoryAdvisor<Vect
return docs;
}
public static Builder builder(VectorStore chatMemory) {
return new Builder(chatMemory);
}
public static class Builder extends AbstractChatMemoryAdvisor.AbstractBuilder<VectorStore> {
private String systemTextAdvise = DEFAULT_SYSTEM_TEXT_ADVISE;
protected Builder(VectorStore chatMemory) {
super(chatMemory);
}
public Builder withSystemTextAdvise(String systemTextAdvise) {
this.systemTextAdvise = systemTextAdvise;
return this;
}
@Override
public VectorStoreChatMemoryAdvisor build() {
return new VectorStoreChatMemoryAdvisor(this.chatMemory, this.conversationId, this.chatMemoryRetrieveSize,
this.systemTextAdvise);
}
}
}

View File

@@ -423,7 +423,16 @@ The following advisor implementations use the `ChatMemory` interface to advice t
* `MessageChatMemoryAdvisor` : Memory is retrieved and added as a collection of messages to the prompt
* `PromptChatMemoryAdvisor` : Memory is retrieved and added into the prompt's system text.
* `VectorStoreChatMemoryAdvisor` : The constructor `VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize)` lets you specify the VectorStore to retrieve the chat history from, the unique conversation ID, the size of the chat history to be retrieved in token size.
* `VectorStoreChatMemoryAdvisor` : The constructor `VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize, int order)` This constructor allows you to:
. Specify the VectorStore instance used for managing and querying documents.
. Set a default conversation ID to be used if none is provided in the context.
. Define the window size for chat history retrieval in terms of token size.
. Provide system text advice used for the chat advisor system.
. Set the order of precedence for this advisor in the chain.
The `VectorStoreChatMemoryAdvisor.builder()` method lets you specify the default conversation ID, the chat history window size, and the order of the chat history to be retrieved.
A sample `@Service` implementation that uses several advisors is shown below.
@@ -452,10 +461,9 @@ public class CustomerSupportAssistant {
If there is a charge for the change, you MUST ask the user to consent before proceeding.
""")
.defaultAdvisors(
new PromptChatMemoryAdvisor(chatMemory),
// new MessageChatMemoryAdvisor(chatMemory), // CHAT MEMORY
new MessageChatMemoryAdvisor(chatMemory), // CHAT MEMORY
new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()), // RAG
new LoggingAdvisor())
new SimpleLoggerAdvisor())
.defaultFunctions("getBookingDetails", "changeBooking", "cancelBooking") // FUNCTION CALLING
.build();
}