Skip to content

Commit 13709f9

Browse files
tzolovmarkpollack
authored andcommitted
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
1 parent 300561a commit 13709f9

File tree

6 files changed

+197
-14
lines changed

6 files changed

+197
-14
lines changed

spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/AbstractChatMemoryAdvisor.java

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisor;
2727
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
2828
import org.springframework.util.Assert;
29+
import org.stringtemplate.v4.compiler.CodeGenerator.includeExpr_return;
2930

3031
import reactor.core.publisher.Flux;
3132
import reactor.core.publisher.Mono;
@@ -56,12 +57,20 @@ public abstract class AbstractChatMemoryAdvisor<T> implements CallAroundAdvisor,
5657

5758
private final boolean protectFromBlocking;
5859

60+
private final int order;
61+
5962
protected AbstractChatMemoryAdvisor(T chatMemory) {
6063
this(chatMemory, DEFAULT_CHAT_MEMORY_CONVERSATION_ID, DEFAULT_CHAT_MEMORY_RESPONSE_SIZE, true);
6164
}
6265

6366
protected AbstractChatMemoryAdvisor(T chatMemory, String defaultConversationId, int defaultChatMemoryRetrieveSize,
6467
boolean protectFromBlocking) {
68+
this(chatMemory, defaultConversationId, defaultChatMemoryRetrieveSize, protectFromBlocking,
69+
Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER);
70+
}
71+
72+
protected AbstractChatMemoryAdvisor(T chatMemory, String defaultConversationId, int defaultChatMemoryRetrieveSize,
73+
boolean protectFromBlocking, int order) {
6574

6675
Assert.notNull(chatMemory, "The chatMemory must not be null!");
6776
Assert.hasText(defaultConversationId, "The conversationId must not be empty!");
@@ -71,6 +80,7 @@ protected AbstractChatMemoryAdvisor(T chatMemory, String defaultConversationId,
7180
this.defaultConversationId = defaultConversationId;
7281
this.defaultChatMemoryRetrieveSize = defaultChatMemoryRetrieveSize;
7382
this.protectFromBlocking = protectFromBlocking;
83+
this.order = order;
7484
}
7585

7686
@Override
@@ -80,11 +90,11 @@ public String getName() {
8090

8191
@Override
8292
public int getOrder() {
83-
// The (Ordered.HIGHEST_PRECEDENCE + 1000) value ensures this order has lower
84-
// priority (e.g. precedences) than the internal Spring AI advisors. It leaves
85-
// room (1000 slots) for the user to plug in their own advisors with higher
93+
// by default the (Ordered.HIGHEST_PRECEDENCE + 1000) value ensures this order has
94+
// lower priority (e.g. precedences) than the internal Spring AI advisors. It
95+
// leaves room (1000 slots) for the user to plug in their own advisors with higher
8696
// priority.
87-
return Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER;
97+
return this.order;
8898
}
8999

90100
protected T getChatMemoryStore() {
@@ -118,5 +128,43 @@ protected Flux<AdvisedResponse> doNextWithProtectFromBlockingBefore(AdvisedReque
118128
: chain.nextAroundStream(beforeAdvise.apply(advisedRequest));
119129
}
120130

131+
public static abstract class AbstractBuilder<T> {
132+
133+
protected String conversationId = DEFAULT_CHAT_MEMORY_CONVERSATION_ID;
134+
135+
protected int chatMemoryRetrieveSize = DEFAULT_CHAT_MEMORY_RESPONSE_SIZE;
136+
137+
protected boolean protectFromBlocking = true;
138+
139+
protected int order = Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER;
140+
141+
protected T chatMemory;
142+
143+
protected AbstractBuilder(T chatMemory) {
144+
this.chatMemory = chatMemory;
145+
}
146+
147+
public AbstractBuilder withConversationId(String conversationId) {
148+
this.conversationId = conversationId;
149+
return this;
150+
}
151+
152+
public AbstractBuilder withChatMemoryRetrieveSize(int chatMemoryRetrieveSize) {
153+
this.chatMemoryRetrieveSize = chatMemoryRetrieveSize;
154+
return this;
155+
}
156+
157+
public AbstractBuilder withProtectFromBlocking(boolean protectFromBlocking) {
158+
this.protectFromBlocking = protectFromBlocking;
159+
return this;
160+
}
161+
162+
public AbstractBuilder withOrder(int order) {
163+
this.order = order;
164+
return this;
165+
}
166+
167+
abstract public <T> AbstractChatMemoryAdvisor<T> build();
168+
}
121169

122170
}

spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/MessageChatMemoryAdvisor.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
2323
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
24+
import org.springframework.ai.chat.client.advisor.api.Advisor;
2425
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
2526
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
2627
import org.springframework.ai.chat.memory.ChatMemory;
@@ -43,7 +44,12 @@ public MessageChatMemoryAdvisor(ChatMemory chatMemory) {
4344
}
4445

4546
public MessageChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize) {
46-
super(chatMemory, defaultConversationId, chatHistoryWindowSize, true);
47+
this(chatMemory, defaultConversationId, chatHistoryWindowSize, Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER);
48+
}
49+
50+
public MessageChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
51+
int order) {
52+
super(chatMemory, defaultConversationId, chatHistoryWindowSize, true, order);
4753
}
4854

4955
@Override
@@ -101,4 +107,21 @@ private void observeAfter(AdvisedResponse advisedResponse) {
101107
this.getChatMemoryStore().add(this.doGetConversationId(advisedResponse.adviseContext()), assistantMessages);
102108
}
103109

110+
public static Builder builder(ChatMemory chatMemory) {
111+
return new Builder(chatMemory);
112+
}
113+
114+
public static class Builder extends AbstractChatMemoryAdvisor.AbstractBuilder<ChatMemory> {
115+
116+
protected Builder(ChatMemory chatMemory) {
117+
super(chatMemory);
118+
}
119+
120+
public MessageChatMemoryAdvisor build() {
121+
return new MessageChatMemoryAdvisor(this.chatMemory, this.conversationId, this.chatMemoryRetrieveSize,
122+
this.order);
123+
}
124+
125+
}
126+
104127
}

spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/PromptChatMemoryAdvisor.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
2525
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
26+
import org.springframework.ai.chat.client.advisor.api.Advisor;
2627
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
2728
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
2829
import org.springframework.ai.chat.memory.ChatMemory;
@@ -66,7 +67,13 @@ public PromptChatMemoryAdvisor(ChatMemory chatMemory, String systemTextAdvise) {
6667

6768
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
6869
String systemTextAdvise) {
69-
super(chatMemory, defaultConversationId, chatHistoryWindowSize, true);
70+
this(chatMemory, defaultConversationId, chatHistoryWindowSize, systemTextAdvise,
71+
Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER);
72+
}
73+
74+
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
75+
String systemTextAdvise, int order) {
76+
super(chatMemory, defaultConversationId, chatHistoryWindowSize, true, order);
7077
this.systemTextAdvise = systemTextAdvise;
7178
}
7279

@@ -133,4 +140,28 @@ private void observeAfter(AdvisedResponse advisedResponse) {
133140
this.getChatMemoryStore().add(this.doGetConversationId(advisedResponse.adviseContext()), assistantMessages);
134141
}
135142

143+
public static Builder builder(ChatMemory chatMemory) {
144+
return new Builder(chatMemory);
145+
}
146+
147+
public static class Builder extends AbstractChatMemoryAdvisor.AbstractBuilder<ChatMemory> {
148+
149+
private String systemTextAdvise = DEFAULT_SYSTEM_TEXT_ADVISE;
150+
151+
protected Builder(ChatMemory chatMemory) {
152+
super(chatMemory);
153+
}
154+
155+
public Builder withSystemTextAdvise(String systemTextAdvise) {
156+
this.systemTextAdvise = systemTextAdvise;
157+
return this;
158+
}
159+
160+
public PromptChatMemoryAdvisor build() {
161+
return new PromptChatMemoryAdvisor(this.chatMemory, this.conversationId, this.chatMemoryRetrieveSize,
162+
this.systemTextAdvise, this.order);
163+
}
164+
165+
}
166+
136167
}

spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/QuestionAnswerAdvisor.java

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424

2525
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
2626
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
27-
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
2827
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor;
28+
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
2929
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisor;
3030
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
3131
import org.springframework.ai.chat.model.ChatResponse;
@@ -61,6 +61,8 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
6161
the user that you can't answer the question.
6262
""";
6363

64+
private static final int DEFAULT_ORDER = 0;
65+
6466
private final VectorStore vectorStore;
6567

6668
private final String userTextAdvise;
@@ -73,6 +75,8 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
7375

7476
private final boolean protectFromBlocking;
7577

78+
private final int order;
79+
7680
/**
7781
* The QuestionAnswerAdvisor retrieves context information from a Vector Store and
7882
* combines it with the user's text.
@@ -121,6 +125,25 @@ public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchReques
121125
*/
122126
public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest, String userTextAdvise,
123127
boolean protectFromBlocking) {
128+
this(vectorStore, searchRequest, userTextAdvise, protectFromBlocking, DEFAULT_ORDER);
129+
}
130+
131+
/**
132+
* The QuestionAnswerAdvisor retrieves context information from a Vector Store and
133+
* combines it with the user's text.
134+
* @param vectorStore The vector store to use
135+
* @param searchRequest The search request defined using the portable filter
136+
* expression syntax
137+
* @param userTextAdvise the user text to append to the existing user prompt. The text
138+
* should contain a placeholder named "question_answer_context".
139+
* @param protectFromBlocking if true the advisor will protect the execution from
140+
* blocking threads. If false the advisor will not protect the execution from blocking
141+
* threads. This is useful when the advisor is used in a non-blocking environment. It
142+
* is true by default.
143+
* @param order the order of the advisor.
144+
*/
145+
public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest, String userTextAdvise,
146+
boolean protectFromBlocking, int order) {
124147

125148
Assert.notNull(vectorStore, "The vectorStore must not be null!");
126149
Assert.notNull(searchRequest, "The searchRequest must not be null!");
@@ -130,6 +153,7 @@ public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchReques
130153
this.searchRequest = searchRequest;
131154
this.userTextAdvise = userTextAdvise;
132155
this.protectFromBlocking = protectFromBlocking;
156+
this.order = order;
133157
}
134158

135159
@Override
@@ -139,7 +163,7 @@ public String getName() {
139163

140164
@Override
141165
public int getOrder() {
142-
return 0;
166+
return this.order;
143167
}
144168

145169
@Override
@@ -249,6 +273,8 @@ public static class Builder {
249273

250274
private boolean protectFromBlocking = true;
251275

276+
private int order = DEFAULT_ORDER;
277+
252278
private Builder(VectorStore vectorStore) {
253279
Assert.notNull(vectorStore, "The vectorStore must not be null!");
254280
this.vectorStore = vectorStore;
@@ -271,9 +297,14 @@ public Builder withProtectFromBlocking(boolean protectFromBlocking) {
271297
return this;
272298
}
273299

300+
public Builder withOrder(int order) {
301+
this.order = order;
302+
return this;
303+
}
304+
274305
public QuestionAnswerAdvisor build() {
275306
return new QuestionAnswerAdvisor(this.vectorStore, this.searchRequest, this.userTextAdvise,
276-
this.protectFromBlocking);
307+
this.protectFromBlocking, this.order);
277308
}
278309

279310
}

spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/VectorStoreChatMemoryAdvisor.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
2525
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
26+
import org.springframework.ai.chat.client.advisor.api.Advisor;
2627
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
2728
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
2829
import org.springframework.ai.chat.messages.AssistantMessage;
@@ -78,7 +79,23 @@ public VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConve
7879

7980
public VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId,
8081
int chatHistoryWindowSize, String systemTextAdvise) {
81-
super(vectorStore, defaultConversationId, chatHistoryWindowSize, true);
82+
this(vectorStore, defaultConversationId, chatHistoryWindowSize, systemTextAdvise,
83+
Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER);
84+
}
85+
86+
/**
87+
* Constructor for VectorStoreChatMemoryAdvisor.
88+
* @param vectorStore the vector store instance used for managing and querying
89+
* documents.
90+
* @param defaultConversationId the default conversation ID used if none is provided
91+
* in the context.
92+
* @param chatHistoryWindowSize the window size for the chat history retrieval.
93+
* @param systemTextAdvise the system text advice used for the chat advisor system.
94+
* @param order the order of precedence for this advisor in the chain.
95+
*/
96+
public VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId,
97+
int chatHistoryWindowSize, String systemTextAdvise, int order) {
98+
super(vectorStore, defaultConversationId, chatHistoryWindowSize, true, order);
8299
this.systemTextAdvise = systemTextAdvise;
83100
}
84101

@@ -168,4 +185,29 @@ else if (message instanceof AssistantMessage assistantMessage) {
168185
return docs;
169186
}
170187

188+
public static Builder builder(VectorStore chatMemory) {
189+
return new Builder(chatMemory);
190+
}
191+
192+
public static class Builder extends AbstractChatMemoryAdvisor.AbstractBuilder<VectorStore> {
193+
194+
private String systemTextAdvise = DEFAULT_SYSTEM_TEXT_ADVISE;
195+
196+
protected Builder(VectorStore chatMemory) {
197+
super(chatMemory);
198+
}
199+
200+
public Builder withSystemTextAdvise(String systemTextAdvise) {
201+
this.systemTextAdvise = systemTextAdvise;
202+
return this;
203+
}
204+
205+
@Override
206+
public VectorStoreChatMemoryAdvisor build() {
207+
return new VectorStoreChatMemoryAdvisor(this.chatMemory, this.conversationId, this.chatMemoryRetrieveSize,
208+
this.systemTextAdvise);
209+
}
210+
211+
}
212+
171213
}

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,16 @@ The following advisor implementations use the `ChatMemory` interface to advice t
423423

424424
* `MessageChatMemoryAdvisor` : Memory is retrieved and added as a collection of messages to the prompt
425425
* `PromptChatMemoryAdvisor` : Memory is retrieved and added into the prompt's system text.
426-
* `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.
426+
* `VectorStoreChatMemoryAdvisor` : The constructor `VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize, int order)` This constructor allows you to:
427+
428+
. Specify the VectorStore instance used for managing and querying documents.
429+
. Set a default conversation ID to be used if none is provided in the context.
430+
. Define the window size for chat history retrieval in terms of token size.
431+
. Provide system text advice used for the chat advisor system.
432+
. Set the order of precedence for this advisor in the chain.
433+
434+
435+
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.
427436

428437
A sample `@Service` implementation that uses several advisors is shown below.
429438

@@ -452,10 +461,9 @@ public class CustomerSupportAssistant {
452461
If there is a charge for the change, you MUST ask the user to consent before proceeding.
453462
""")
454463
.defaultAdvisors(
455-
new PromptChatMemoryAdvisor(chatMemory),
456-
// new MessageChatMemoryAdvisor(chatMemory), // CHAT MEMORY
464+
new MessageChatMemoryAdvisor(chatMemory), // CHAT MEMORY
457465
new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()), // RAG
458-
new LoggingAdvisor())
466+
new SimpleLoggerAdvisor())
459467
.defaultFunctions("getBookingDetails", "changeBooking", "cancelBooking") // FUNCTION CALLING
460468
.build();
461469
}

0 commit comments

Comments
 (0)