3939import org .springframework .util .StringUtils ;
4040
4141import reactor .core .publisher .Flux ;
42+ import reactor .core .publisher .Mono ;
43+ import reactor .core .scheduler .Schedulers ;
4244
4345/**
4446 * Context for the question is retrieved from a Vector Store and added to the prompt's
@@ -69,10 +71,24 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
6971
7072 public static final String FILTER_EXPRESSION = "qa_filter_expression" ;
7173
74+ private final boolean protectFromBlocking ;
75+
76+ /**
77+ * The QuestionAnswerAdvisor retrieves context information from a Vector Store and
78+ * combines it with the user's text.
79+ * @param vectorStore The vector store to use
80+ */
7281 public QuestionAnswerAdvisor (VectorStore vectorStore ) {
7382 this (vectorStore , SearchRequest .defaults (), DEFAULT_USER_TEXT_ADVISE );
7483 }
7584
85+ /**
86+ * The QuestionAnswerAdvisor retrieves context information from a Vector Store and
87+ * combines it with the user's text.
88+ * @param vectorStore The vector store to use
89+ * @param searchRequest The search request defined using the portable filter
90+ * expression syntax
91+ */
7692 public QuestionAnswerAdvisor (VectorStore vectorStore , SearchRequest searchRequest ) {
7793 this (vectorStore , searchRequest , DEFAULT_USER_TEXT_ADVISE );
7894 }
@@ -85,9 +101,26 @@ public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchReques
85101 * expression syntax
86102 * @param userTextAdvise the user text to append to the existing user prompt. The text
87103 * should contain a placeholder named "question_answer_context".
88- *
89104 */
90105 public QuestionAnswerAdvisor (VectorStore vectorStore , SearchRequest searchRequest , String userTextAdvise ) {
106+ this (vectorStore , searchRequest , userTextAdvise , true );
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+ * @param protectFromBlocking if true the advisor will protect the execution from
118+ * blocking threads. If false the advisor will not protect the execution from blocking
119+ * threads. This is useful when the advisor is used in a non-blocking environment. It
120+ * is true by default.
121+ */
122+ public QuestionAnswerAdvisor (VectorStore vectorStore , SearchRequest searchRequest , String userTextAdvise ,
123+ boolean protectFromBlocking ) {
91124
92125 Assert .notNull (vectorStore , "The vectorStore must not be null!" );
93126 Assert .notNull (searchRequest , "The searchRequest must not be null!" );
@@ -96,6 +129,7 @@ public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchReques
96129 this .vectorStore = vectorStore ;
97130 this .searchRequest = searchRequest ;
98131 this .userTextAdvise = userTextAdvise ;
132+ this .protectFromBlocking = protectFromBlocking ;
99133 }
100134
101135 @ Override
@@ -121,9 +155,19 @@ public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvis
121155 @ Override
122156 public Flux <AdvisedResponse > aroundStream (AdvisedRequest advisedRequest , StreamAroundAdvisorChain chain ) {
123157
124- advisedRequest = before (advisedRequest );
125-
126- return chain .nextAroundStream (advisedRequest ).map (ar -> {
158+ // This can be executed by both blocking and non-blocking Threads
159+ // E.g. a command line or Tomcat blocking Thread implementation
160+ // or by a WebFlux dispatch in a non-blocking manner.
161+ Flux <AdvisedResponse > advisedResponses = (this .protectFromBlocking ) ?
162+ // @formatter:off
163+ Mono .just (advisedRequest )
164+ .publishOn (Schedulers .boundedElastic ())
165+ .map (this ::before )
166+ .flatMapMany (request -> chain .nextAroundStream (request ))
167+ : chain .nextAroundStream (before (advisedRequest ));
168+ // @formatter:on
169+
170+ return advisedResponses .map (ar -> {
127171 if (onFinishReason ().test (ar )) {
128172 ar = after (ar );
129173 }
@@ -191,4 +235,47 @@ private Predicate<AdvisedResponse> onFinishReason() {
191235 .isPresent ();
192236 }
193237
238+ public static Builder builder (VectorStore vectorStore ) {
239+ return new Builder (vectorStore );
240+ }
241+
242+ public static class Builder {
243+
244+ private final VectorStore vectorStore ;
245+
246+ private SearchRequest searchRequest = SearchRequest .defaults ();
247+
248+ private String userTextAdvise = DEFAULT_USER_TEXT_ADVISE ;
249+
250+ private boolean protectFromBlocking = true ;
251+
252+ private Builder (VectorStore vectorStore ) {
253+ Assert .notNull (vectorStore , "The vectorStore must not be null!" );
254+ this .vectorStore = vectorStore ;
255+ }
256+
257+ public Builder withSearchRequest (SearchRequest searchRequest ) {
258+ Assert .notNull (searchRequest , "The searchRequest must not be null!" );
259+ this .searchRequest = searchRequest ;
260+ return this ;
261+ }
262+
263+ public Builder withUserTextAdvise (String userTextAdvise ) {
264+ Assert .hasText (userTextAdvise , "The userTextAdvise must not be empty!" );
265+ this .userTextAdvise = userTextAdvise ;
266+ return this ;
267+ }
268+
269+ public Builder withProtectFromBlocking (boolean protectFromBlocking ) {
270+ this .protectFromBlocking = protectFromBlocking ;
271+ return this ;
272+ }
273+
274+ public QuestionAnswerAdvisor build () {
275+ return new QuestionAnswerAdvisor (this .vectorStore , this .searchRequest , this .userTextAdvise ,
276+ this .protectFromBlocking );
277+ }
278+
279+ }
280+
194281}
0 commit comments