Skip to content

Commit 4fe74d8

Browse files
ThomasVitaleilayaperumalg
authored andcommitted
Remove deprecations in ChatClient and Advisors
* Update remaining Advisors and related classes to use the new APIs. * In AbstractChatMemoryAdvisor, the “doNextWithProtectFromBlockingBefore()” protected method has been changed from accepting AdvisedRequest to ChatClientRequest. It’s a breaking change since the alternative was not part of M8. * MessageAggregator has a new method to aggregate messages from ChatClientRequest. The previous method aggregating messages from AdvisedRequest has been removed. Warning since it wasn’t marked as deprecated in M8. * In SimpleLoggerAdvisor, the “requestToString” input argument needs to be updated to use ChatClientRequest. It’s a breaking change since the alternative was not part of M8. Same thing about the constructor. * The “getTemplateRenderer” method has been removed from BaseAdvisorChain. Each Advisor is encouraged to accept a PromptTemplate to achieve self-contained prompt augmentation operations. * Remove deprecations in ChatClient and Advisors, and update tests accordingly. * When building a Prompt from the ChatClient input, the SystemMessage passed via systemText() is placed first in the message list. Before, it was put last, resulting in errors with several model providers. Relates to gh-2655 Signed-off-by: Thomas Vitale <[email protected]>
1 parent 88490b3 commit 4fe74d8

File tree

61 files changed

+1350
-3103
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1350
-3103
lines changed

advisors/spring-ai-advisors-vector-store/src/main/java/org/springframework/ai/chat/client/advisor/vectorstore/QuestionAnswerAdvisor.java

+48-159
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,13 @@
2121
import java.util.Map;
2222
import java.util.stream.Collectors;
2323

24-
import reactor.core.publisher.Flux;
25-
import reactor.core.publisher.Mono;
24+
import org.springframework.ai.chat.client.ChatClientRequest;
25+
import org.springframework.ai.chat.client.ChatClientResponse;
26+
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
27+
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
28+
import reactor.core.scheduler.Scheduler;
2629
import reactor.core.scheduler.Schedulers;
2730

28-
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
29-
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
30-
import org.springframework.ai.chat.client.advisor.api.AdvisedResponseStreamUtils;
31-
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor;
32-
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
33-
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisor;
34-
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
3531
import org.springframework.ai.chat.model.ChatResponse;
3632
import org.springframework.ai.chat.prompt.PromptTemplate;
3733
import org.springframework.ai.document.Document;
@@ -53,7 +49,7 @@
5349
* @author Thomas Vitale
5450
* @since 1.0.0
5551
*/
56-
public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
52+
public class QuestionAnswerAdvisor implements BaseAdvisor {
5753

5854
public static final String RETRIEVED_DOCUMENTS = "qa_retrieved_documents";
5955

@@ -80,198 +76,96 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
8076

8177
private final SearchRequest searchRequest;
8278

83-
private final boolean protectFromBlocking;
79+
private final Scheduler scheduler;
8480

8581
private final int order;
8682

87-
/**
88-
* The QuestionAnswerAdvisor retrieves context information from a Vector Store and
89-
* combines it with the user's text.
90-
* @param vectorStore The vector store to use
91-
*/
9283
public QuestionAnswerAdvisor(VectorStore vectorStore) {
93-
this(vectorStore, SearchRequest.builder().build(), DEFAULT_PROMPT_TEMPLATE, true, DEFAULT_ORDER);
94-
}
95-
96-
/**
97-
* The QuestionAnswerAdvisor retrieves context information from a Vector Store and
98-
* combines it with the user's text.
99-
* @param vectorStore The vector store to use
100-
* @param searchRequest The search request defined using the portable filter
101-
* expression syntax
102-
* @deprecated in favor of the builder: {@link #builder(VectorStore)}
103-
*/
104-
@Deprecated
105-
public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest) {
106-
this(vectorStore, searchRequest, DEFAULT_PROMPT_TEMPLATE, true, DEFAULT_ORDER);
107-
}
108-
109-
/**
110-
* The QuestionAnswerAdvisor retrieves context information from a Vector Store and
111-
* combines it with the user's text.
112-
* @param vectorStore The vector store to use
113-
* @param searchRequest The search request defined using the portable filter
114-
* expression syntax
115-
* @param userTextAdvise The user text to append to the existing user prompt. The text
116-
* should contain a placeholder named "question_answer_context".
117-
* @deprecated in favor of the builder: {@link #builder(VectorStore)}
118-
*/
119-
@Deprecated
120-
public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest, String userTextAdvise) {
121-
this(vectorStore, searchRequest, PromptTemplate.builder().template(userTextAdvise).build(), true,
84+
this(vectorStore, SearchRequest.builder().build(), DEFAULT_PROMPT_TEMPLATE, BaseAdvisor.DEFAULT_SCHEDULER,
12285
DEFAULT_ORDER);
12386
}
12487

125-
/**
126-
* The QuestionAnswerAdvisor retrieves context information from a Vector Store and
127-
* combines it with the user's text.
128-
* @param vectorStore The vector store to use
129-
* @param searchRequest The search request defined using the portable filter
130-
* expression syntax
131-
* @param userTextAdvise The user text to append to the existing user prompt. The text
132-
* should contain a placeholder named "question_answer_context".
133-
* @param protectFromBlocking If true the advisor will protect the execution from
134-
* blocking threads. If false the advisor will not protect the execution from blocking
135-
* threads. This is useful when the advisor is used in a non-blocking environment. It
136-
* is true by default.
137-
* @deprecated in favor of the builder: {@link #builder(VectorStore)}
138-
*/
139-
@Deprecated
140-
public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest, String userTextAdvise,
141-
boolean protectFromBlocking) {
142-
this(vectorStore, searchRequest, PromptTemplate.builder().template(userTextAdvise).build(), protectFromBlocking,
143-
DEFAULT_ORDER);
144-
}
145-
146-
/**
147-
* The QuestionAnswerAdvisor retrieves context information from a Vector Store and
148-
* combines it with the user's text.
149-
* @param vectorStore The vector store to use
150-
* @param searchRequest The search request defined using the portable filter
151-
* expression syntax
152-
* @param userTextAdvise The user text to append to the existing user prompt. The text
153-
* should contain a placeholder named "question_answer_context".
154-
* @param protectFromBlocking If true the advisor will protect the execution from
155-
* blocking threads. If false the advisor will not protect the execution from blocking
156-
* threads. This is useful when the advisor is used in a non-blocking environment. It
157-
* is true by default.
158-
* @param order The order of the advisor.
159-
* @deprecated in favor of the builder: {@link #builder(VectorStore)}
160-
*/
161-
@Deprecated
162-
public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest, String userTextAdvise,
163-
boolean protectFromBlocking, int order) {
164-
this(vectorStore, searchRequest, PromptTemplate.builder().template(userTextAdvise).build(), protectFromBlocking,
165-
order);
166-
}
167-
16888
QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest, @Nullable PromptTemplate promptTemplate,
169-
boolean protectFromBlocking, int order) {
89+
@Nullable Scheduler scheduler, int order) {
17090
Assert.notNull(vectorStore, "vectorStore cannot be null");
17191
Assert.notNull(searchRequest, "searchRequest cannot be null");
17292

17393
this.vectorStore = vectorStore;
17494
this.searchRequest = searchRequest;
17595
this.promptTemplate = promptTemplate != null ? promptTemplate : DEFAULT_PROMPT_TEMPLATE;
176-
this.protectFromBlocking = protectFromBlocking;
96+
this.scheduler = scheduler != null ? scheduler : BaseAdvisor.DEFAULT_SCHEDULER;
17797
this.order = order;
17898
}
17999

180100
public static Builder builder(VectorStore vectorStore) {
181101
return new Builder(vectorStore);
182102
}
183103

184-
@Override
185-
public String getName() {
186-
return this.getClass().getSimpleName();
187-
}
188-
189104
@Override
190105
public int getOrder() {
191106
return this.order;
192107
}
193108

194109
@Override
195-
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
196-
197-
AdvisedRequest advisedRequest2 = before(advisedRequest);
198-
199-
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest2);
200-
201-
return after(advisedResponse);
202-
}
203-
204-
@Override
205-
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
206-
207-
// This can be executed by both blocking and non-blocking Threads
208-
// E.g. a command line or Tomcat blocking Thread implementation
209-
// or by a WebFlux dispatch in a non-blocking manner.
210-
Flux<AdvisedResponse> advisedResponses = (this.protectFromBlocking) ?
211-
// @formatter:off
212-
Mono.just(advisedRequest)
213-
.publishOn(Schedulers.boundedElastic())
214-
.map(this::before)
215-
.flatMapMany(request -> chain.nextAroundStream(request))
216-
: chain.nextAroundStream(before(advisedRequest));
217-
// @formatter:on
218-
219-
return advisedResponses.map(ar -> {
220-
if (AdvisedResponseStreamUtils.onFinishReason().test(ar)) {
221-
ar = after(ar);
222-
}
223-
return ar;
224-
});
225-
}
226-
227-
private AdvisedRequest before(AdvisedRequest request) {
228-
229-
var context = new HashMap<>(request.adviseContext());
230-
110+
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
231111
// 1. Search for similar documents in the vector store.
232112
var searchRequestToUse = SearchRequest.from(this.searchRequest)
233-
.query(request.userText())
234-
.filterExpression(doGetFilterExpression(context))
113+
.query(chatClientRequest.prompt().getUserMessage().getText())
114+
.filterExpression(doGetFilterExpression(chatClientRequest.context()))
235115
.build();
236116

237117
List<Document> documents = this.vectorStore.similaritySearch(searchRequestToUse);
238118

239119
// 2. Create the context from the documents.
120+
Map<String, Object> context = new HashMap<>(chatClientRequest.context());
240121
context.put(RETRIEVED_DOCUMENTS, documents);
241122

242-
String documentContext = documents.stream()
243-
.map(Document::getText)
244-
.collect(Collectors.joining(System.lineSeparator()));
123+
String documentContext = documents == null ? ""
124+
: documents.stream().map(Document::getText).collect(Collectors.joining(System.lineSeparator()));
245125

246126
// 3. Augment the user prompt with the document context.
247127
String augmentedUserText = this.promptTemplate.mutate()
248-
.template(request.userText() + System.lineSeparator() + this.promptTemplate.getTemplate())
128+
.template(chatClientRequest.prompt().getUserMessage().getText() + System.lineSeparator()
129+
+ this.promptTemplate.getTemplate())
249130
.variables(Map.of("question_answer_context", documentContext))
250131
.build()
251132
.render();
252133

253-
AdvisedRequest advisedRequest = AdvisedRequest.from(request)
254-
.userText(augmentedUserText)
255-
.adviseContext(context)
134+
// 4. Update ChatClientRequest with augmented prompt.
135+
return chatClientRequest.mutate()
136+
.prompt(chatClientRequest.prompt().augmentUserMessage(augmentedUserText))
137+
.context(context)
256138
.build();
257-
258-
return advisedRequest;
259139
}
260140

261-
private AdvisedResponse after(AdvisedResponse advisedResponse) {
262-
ChatResponse.Builder chatResponseBuilder = ChatResponse.builder().from(advisedResponse.response());
263-
chatResponseBuilder.metadata(RETRIEVED_DOCUMENTS, advisedResponse.adviseContext().get(RETRIEVED_DOCUMENTS));
264-
return new AdvisedResponse(chatResponseBuilder.build(), advisedResponse.adviseContext());
141+
@Override
142+
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
143+
ChatResponse.Builder chatResponseBuilder;
144+
if (chatClientResponse.chatResponse() == null) {
145+
chatResponseBuilder = ChatResponse.builder();
146+
}
147+
else {
148+
chatResponseBuilder = ChatResponse.builder().from(chatClientResponse.chatResponse());
149+
}
150+
chatResponseBuilder.metadata(RETRIEVED_DOCUMENTS, chatClientResponse.context().get(RETRIEVED_DOCUMENTS));
151+
return ChatClientResponse.builder()
152+
.chatResponse(chatResponseBuilder.build())
153+
.context(chatClientResponse.context())
154+
.build();
265155
}
266156

157+
@Nullable
267158
protected Filter.Expression doGetFilterExpression(Map<String, Object> context) {
268-
269159
if (!context.containsKey(FILTER_EXPRESSION)
270160
|| !StringUtils.hasText(context.get(FILTER_EXPRESSION).toString())) {
271161
return this.searchRequest.getFilterExpression();
272162
}
273163
return new FilterExpressionTextParser().parse(context.get(FILTER_EXPRESSION).toString());
164+
}
274165

166+
@Override
167+
public Scheduler getScheduler() {
168+
return this.scheduler;
275169
}
276170

277171
public static final class Builder {
@@ -282,7 +176,7 @@ public static final class Builder {
282176

283177
private PromptTemplate promptTemplate;
284178

285-
private boolean protectFromBlocking = true;
179+
private Scheduler scheduler;
286180

287181
private int order = DEFAULT_ORDER;
288182

@@ -303,18 +197,13 @@ public Builder searchRequest(SearchRequest searchRequest) {
303197
return this;
304198
}
305199

306-
/**
307-
* @deprecated in favour of {@link #promptTemplate(PromptTemplate)}
308-
*/
309-
@Deprecated
310-
public Builder userTextAdvise(String userTextAdvise) {
311-
Assert.hasText(userTextAdvise, "The userTextAdvise must not be empty!");
312-
this.promptTemplate = PromptTemplate.builder().template(userTextAdvise).build();
200+
public Builder protectFromBlocking(boolean protectFromBlocking) {
201+
this.scheduler = protectFromBlocking ? BaseAdvisor.DEFAULT_SCHEDULER : Schedulers.immediate();
313202
return this;
314203
}
315204

316-
public Builder protectFromBlocking(boolean protectFromBlocking) {
317-
this.protectFromBlocking = protectFromBlocking;
205+
public Builder scheduler(Scheduler scheduler) {
206+
this.scheduler = scheduler;
318207
return this;
319208
}
320209

@@ -324,8 +213,8 @@ public Builder order(int order) {
324213
}
325214

326215
public QuestionAnswerAdvisor build() {
327-
return new QuestionAnswerAdvisor(this.vectorStore, this.searchRequest, this.promptTemplate,
328-
this.protectFromBlocking, this.order);
216+
return new QuestionAnswerAdvisor(this.vectorStore, this.searchRequest, this.promptTemplate, this.scheduler,
217+
this.order);
329218
}
330219

331220
}

0 commit comments

Comments
 (0)