Skip to content

Commit 8c88a64

Browse files
committed
Update the ChatClient Advisor reference documentation
Signed-off-by: Christian Tzolov <[email protected]>
1 parent 73c0d4d commit 8c88a64

File tree

4 files changed

+121
-97
lines changed

4 files changed

+121
-97
lines changed
Loading
Loading
Loading

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

Lines changed: 121 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ You can configure existing advisors using the xref:api/chatclient.adoc#_advisor_
1111

1212
[source,java]
1313
----
14+
15+
ChatMemory chatMemory = ... // Initialize your chat memory store
16+
VectorStore vectorStore = ... // Initialize your vector store
17+
1418
var chatClient = ChatClient.builder(chatModel)
1519
.defaultAdvisors(
1620
MessageChatMemoryAdvisor.builder(chatMemory).build(), // chat-memory advisor
@@ -37,12 +41,12 @@ Advisors also participate in the Observability stack, so you can view metrics an
3741

3842
== Core Components
3943

40-
The API consists of `CallAroundAdvisor` and `CallAroundAdvisorChain` for non-streaming scenarios, and `StreamAroundAdvisor` and `StreamAroundAdvisorChain` for streaming scenarios.
41-
It also includes `AdvisedRequest` to represent the unsealed Prompt request, `AdvisedResponse` for the Chat Completion response. Both hold an `advise-context` to share state across the advisor chain.
44+
The API consists of `CallAdvisor` and `CallAdvisorChain` for non-streaming scenarios, and `StreamAdvisor` and `StreamAdvisorChain` for streaming scenarios.
45+
It also includes `ChatClientRequest` to represent the unsealed Prompt request, `ChatClientResponse` for the Chat Completion response. Both hold an `advise-context` to share state across the advisor chain.
4246

4347
image::advisors-api-classes.jpg[Advisors API Classes, width=600, align="center"]
4448

45-
The `nextAroundCall()` and the `nextAroundStream()` are the key advisor methods, typically performing actions such as examining the unsealed Prompt data, customizing and augmenting the Prompt data, invoking the next entity in the advisor chain, optionally blocking the request, examining the chat completion response, and throwing exceptions to indicate processing errors.
49+
The `adviseCall()` and the `adviseStream()` are the key advisor methods, typically performing actions such as examining the unsealed Prompt data, customizing and augmenting the Prompt data, invoking the next entity in the advisor chain, optionally blocking the request, examining the chat completion response, and throwing exceptions to indicate processing errors.
4650

4751
In addition the `getOrder()` method determines advisor order in the chain, while `getName()` provides a unique advisor name.
4852

@@ -52,14 +56,14 @@ The last advisor, added automatically, sends the request to the LLM.
5256

5357
Following flow diagram illustrates the interaction between the advisor chain and the Chat Model:
5458

55-
image::advisors-flow.jpg[Advisors API Flow, width=400, align="left"]
59+
image::advisors-flow.jpg[Advisors API Flow, width=400, align="center"]
5660

57-
. The Spring AI framework creates an `AdvisedRequest` from user's `Prompt` along with an empty `AdvisorContext` object.
61+
. The Spring AI framework creates an `ChatClientRequest` from user's `Prompt` along with an empty advisor `context` object.
5862
. Each advisor in the chain processes the request, potentially modifying it. Alternatively, it can choose to block the request by not making the call to invoke the next entity. In the latter case, the advisor is responsible for filling out the response.
5963
. The final advisor, provided by the framework, sends the request to the `Chat Model`.
60-
. The Chat Model's response is then passed back through the advisor chain and converted into `AdvisedResponse`. Later includes the shared `AdvisorContext` instance.
64+
. The Chat Model's response is then passed back through the advisor chain and converted into `ChatClientResponse`. Later includes the shared advisor `context` instance.
6165
. Each advisor can process or modify the response.
62-
. The final `AdvisedResponse` is returned to the client by extracting the `ChatCompletion`.
66+
. The final `ChatClientResponse` is returned to the client by extracting the `ChatCompletion`.
6367

6468
=== Advisor Order
6569
The execution order of advisors in the chain is determined by the `getOrder()` method. Key points to understand:
@@ -142,76 +146,86 @@ public interface Advisor extends Ordered {
142146
The two sub-interfaces for synchronous and reactive Advisors are
143147

144148
```java
145-
public interface CallAroundAdvisor extends Advisor {
149+
public interface CallAdvisor extends Advisor {
146150

147-
/**
148-
* Around advice that wraps the ChatModel#call(Prompt) method.
149-
* @param advisedRequest the advised request
150-
* @param chain the advisor chain
151-
* @return the response
152-
*/
153-
AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
151+
ChatClientResponse adviseCall(
152+
ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);
154153

155154
}
155+
156156
```
157157

158158
and
159159

160160
```java
161-
public interface StreamAroundAdvisor extends Advisor {
161+
public interface StreamAdvisor extends Advisor {
162162

163-
/**
164-
* Around advice that wraps the invocation of the advised request.
165-
* @param advisedRequest the advised request
166-
* @param chain the chain of advisors to execute
167-
* @return the result of the advised request
168-
*/
169-
Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);
163+
Flux<ChatClientResponse> adviseStream(
164+
ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);
170165

171166
}
172167
```
173168

174-
To continue the chain of Advice, use `CallAroundAdvisorChain` and `StreamAroundAdvisorChain` in your Advice implementation:
169+
To continue the chain of Advice, use `CallAdvisorChain` and `StreamAdvisorChain` in your Advice implementation:
175170

176171
The interfaces are
177172

178173
```java
179-
public interface CallAroundAdvisorChain {
174+
public interface CallAdvisorChain extends AdvisorChain {
180175

181-
AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);
176+
/**
177+
* Invokes the next {@link CallAdvisor} in the {@link CallAdvisorChain} with the given
178+
* request.
179+
*/
180+
ChatClientResponse nextCall(ChatClientRequest chatClientRequest);
181+
182+
/**
183+
* Returns the list of all the {@link CallAdvisor} instances included in this chain at
184+
* the time of its creation.
185+
*/
186+
List<CallAdvisor> getCallAdvisors();
182187

183188
}
184189
```
185190

186191
and
187192

188193
```java
189-
public interface StreamAroundAdvisorChain {
194+
public interface StreamAdvisorChain extends AdvisorChain {
190195

191-
Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);
196+
/**
197+
* Invokes the next {@link StreamAdvisor} in the {@link StreamAdvisorChain} with the
198+
* given request.
199+
*/
200+
Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest);
201+
202+
/**
203+
* Returns the list of all the {@link StreamAdvisor} instances included in this chain
204+
* at the time of its creation.
205+
*/
206+
List<StreamAdvisor> getStreamAdvisors();
192207

193208
}
194209
```
195210

196211

197-
198212
== Implementing an Advisor
199213

200-
To create an advisor, implement either `CallAroundAdvisor` or `StreamAroundAdvisor` (or both). The key method to implement is `nextAroundCall()` for non-streaming or `nextAroundStream()` for streaming advisors.
214+
To create an advisor, implement either `CallAdvisor` or `StreamAdvisor` (or both). The key method to implement is `nextCall()` for non-streaming or `nextStream()` for streaming advisors.
201215

202216
=== Examples
203217

204218
We will provide few hands-on examples to illustrate how to implement advisors for observing and augmenting use-cases.
205219

206220
==== Logging Advisor
207221

208-
We can implement a simple logging advisor that logs the `AdvisedRequest` before and the `AdvisedResponse` after the call to the next advisor in the chain.
222+
We can implement a simple logging advisor that logs the `ChatClientRequest` before and the `ChatClientResponse` after the call to the next advisor in the chain.
209223
Note that the advisor only observes the request and response and does not modify them.
210224
This implementation support both non-streaming and streaming scenarios.
211225

212226
[source,java]
213227
----
214-
public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
228+
public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {
215229
216230
private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);
217231
@@ -225,33 +239,41 @@ public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvis
225239
return 0;
226240
}
227241
228-
@Override
229-
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
230242
231-
logger.debug("BEFORE: {}", advisedRequest);
243+
@Override
244+
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
245+
logRequest(chatClientRequest);
232246
233-
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
247+
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
234248
235-
logger.debug("AFTER: {}", advisedResponse);
249+
logResponse(chatClientResponse);
236250
237-
return advisedResponse;
251+
return chatClientResponse;
238252
}
239253
240254
@Override
241-
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
255+
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest,
256+
StreamAdvisorChain streamAdvisorChain) {
257+
logRequest(chatClientRequest);
242258
243-
logger.debug("BEFORE: {}", advisedRequest);
259+
Flux<ChatClientResponse> chatClientResponses = streamAdvisorChain.nextStream(chatClientRequest);
244260
245-
Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
246-
247-
return new MessageAggregator().aggregateAdvisedResponse(advisedResponses,
248-
advisedResponse -> logger.debug("AFTER: {}", advisedResponse)); // <3>
261+
return new ChatClientMessageAggregator().aggregateChatClientResponse(chatClientResponses, this::logResponse); // <3>
249262
}
263+
264+
private void logRequest(ChatClientRequest request) {
265+
logger.debug("request: {}", request);
266+
}
267+
268+
private void logResponse(ChatClientResponse chatClientResponse) {
269+
logger.debug("response: {}", chatClientResponse);
270+
}
271+
250272
}
251273
----
252274
<1> Provides a unique name for the advisor.
253275
<2> You can control the order of execution by setting the order value. Lower values execute first.
254-
<3> The `MessageAggregator` is a utility class that aggregates the Flux responses into a single AdvisedResponse.
276+
<3> The `MessageAggregator` is a utility class that aggregates the Flux responses into a single ChatClientResponse.
255277
This can be useful for logging or other processing that observe the entire response rather than individual items in the stream.
256278
Note that you can not alter the response in the `MessageAggregator` as it is a read-only operation.
257279

@@ -269,49 +291,59 @@ Implementing an advisor that applies the Re2 technique to the user's input query
269291

270292
[source,java]
271293
----
272-
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
273294
295+
public class ReReadingAdvisor implements BaseAdvisor {
274296
275-
private AdvisedRequest before(AdvisedRequest advisedRequest) { // <1>
297+
private static final String DEFAULT_RE2_ADVISE_TEMPLATE = """
298+
{re2_input_query}
299+
Read the question again: {re2_input_query}
300+
""";
276301
277-
Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
278-
advisedUserParams.put("re2_input_query", advisedRequest.userText());
302+
private final String re2AdviseTemplate;
279303
280-
return AdvisedRequest.from(advisedRequest)
281-
.userText("""
282-
{re2_input_query}
283-
Read the question again: {re2_input_query}
284-
""")
285-
.userParams(advisedUserParams)
286-
.build();
304+
private int order = 0;
305+
306+
public ReReadingAdvisor() {
307+
this(DEFAULT_RE2_ADVISE_TEMPLATE);
308+
}
309+
310+
public ReReadingAdvisor(String re2AdviseTemplate) {
311+
this.re2AdviseTemplate = re2AdviseTemplate;
287312
}
288313
289314
@Override
290-
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { // <2>
291-
return chain.nextAroundCall(this.before(advisedRequest));
315+
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) { // <1>
316+
String augmentedUserText = PromptTemplate.builder()
317+
.template(this.re2AdviseTemplate)
318+
.variables(Map.of("re2_input_query", chatClientRequest.prompt().getUserMessage().getText()))
319+
.build()
320+
.render();
321+
322+
return chatClientRequest.mutate()
323+
.prompt(chatClientRequest.prompt().augmentUserMessage(augmentedUserText))
324+
.build();
292325
}
293326
294327
@Override
295-
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { // <3>
296-
return chain.nextAroundStream(this.before(advisedRequest));
328+
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
329+
return chatClientResponse;
297330
}
298331
299332
@Override
300-
public int getOrder() { // <4>
301-
return 0;
333+
public int getOrder() { // <2>
334+
return this.order;
302335
}
303336
304-
@Override
305-
public String getName() { // <5>
306-
return this.getClass().getSimpleName();
337+
public ReReadingAdvisor withOrder(int order) {
338+
this.order = order;
339+
return this;
307340
}
341+
308342
}
309343
----
310344
<1> The `before` method augments the user's input query applying the Re-Reading technique.
311-
<2> The `aroundCall` method intercepts the non-streaming request and applies the Re-Reading technique.
312-
<3> The `aroundStream` method intercepts the streaming request and applies the Re-Reading technique.
313-
<4> You can control the order of execution by setting the order value. Lower values execute first.
314-
<5> Provides a unique name for the advisor.
345+
<2> You can control the order of execution by setting the order value. Lower values execute first.
346+
315347

316348
==== Spring AI Built-in Advisors
317349

@@ -335,7 +367,19 @@ Retrieves memory from a VectorStore and adds it into the prompt's system text. T
335367
===== Question Answering Advisor
336368
* `QuestionAnswerAdvisor`
337369
+
338-
This advisor uses a vector store to provide question-answering capabilities, implementing the RAG (Retrieval-Augmented Generation) pattern.
370+
This advisor uses a vector store to provide question-answering capabilities, implementing the Naive RAG (Retrieval-Augmented Generation) pattern.
371+
372+
* `RetrievalAugmentationAdvisor`
373+
+
374+
Advisor that implements common Retrieval Augmented Generation (RAG) flows using the building blocks defined in the `org.springframework.ai.rag` package and following the Modular RAG Architecture.
375+
376+
377+
===== Reasoning Advisor
378+
* `ReReadingAdvisor`
379+
+
380+
Implements a re-reading strategy for LLM reasoning, dubbed RE2, to enhance understanding in the input phase.
381+
Based on the article: [Re-Reading Improves Reasoning in LLMs](https://arxiv.org/pdf/2309.06275).
382+
339383

340384
===== Content Safety Advisor
341385
* `SafeGuardAdvisor`
@@ -345,7 +389,7 @@ A simple advisor designed to prevent the model from generating harmful or inappr
345389

346390
=== Streaming vs Non-Streaming
347391

348-
image::advisors-non-stream-vs-stream.jpg[Advisors Streaming vs Non-Streaming Flow, width=800, align="left"]
392+
image::advisors-non-stream-vs-stream.jpg[Advisors Streaming vs Non-Streaming Flow, width=800, align="center"]
349393

350394
* Non-streaming advisors work with complete requests and responses.
351395
* Streaming advisors handle requests and responses as continuous streams, using reactive programming concepts (e.g., Flux for responses).
@@ -356,15 +400,15 @@ image::advisors-non-stream-vs-stream.jpg[Advisors Streaming vs Non-Streaming Flo
356400
[source,java]
357401
----
358402
@Override
359-
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
403+
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain chain) {
360404
361-
return Mono.just(advisedRequest)
405+
return Mono.just(chatClientRequest)
362406
.publishOn(Schedulers.boundedElastic())
363407
.map(request -> {
364408
// This can be executed by blocking and non-blocking Threads.
365409
// Advisor before next section
366410
})
367-
.flatMapMany(request -> chain.nextAroundStream(request))
411+
.flatMapMany(request -> chain.nextStream(request))
368412
.map(response -> {
369413
// Advisor after next section
370414
});
@@ -378,13 +422,7 @@ public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamA
378422
. Implement both streaming and non-streaming versions of your advisor for maximum flexibility.
379423
. Carefully consider the order of advisors in your chain to ensure proper data flow.
380424

381-
382-
== Backward Compatibility
383-
384-
IMPORTANT: The `AdvisedRequest` class is moved to a new package.
385-
386425
== Breaking API Changes
387-
The Spring AI Advisor Chain underwent significant changes from version 1.0 M2 to 1.0 M3. Here are the key modifications:
388426

389427
=== Advisor Interfaces
390428

@@ -395,6 +433,9 @@ The Spring AI Advisor Chain underwent significant changes from version 1.0 M2 to
395433
** `CallAroundAdvisor`
396434
** `StreamAroundAdvisor`
397435
* The `StreamResponseMode`, previously part of `ResponseAdvisor`, has been removed.
436+
* In 1.0.0 these interfaces have been replaced:
437+
** `CallAroundAdvisor` -> `CallAdvisor`, `StreamAroundAdvisor` -> `StreamAdvisor`, `CallAroundAdvisorChain` -> `CallAdvisorChain` and `StreamAroundAdvisorChain` -> `StreamAdvisorChain`.
438+
** `AdvisedRequest` -> `ChatClientRequest` are `AdivsedResponse` -> `ChatClientResponse`.
398439

399440
=== Context Map Handling
400441

@@ -405,20 +446,3 @@ The Spring AI Advisor Chain underwent significant changes from version 1.0 M2 to
405446
** The context map is now part of the `AdvisedRequest` and `AdvisedResponse` records.
406447
** The map is immutable.
407448
** To update the context, use the `updateContext` method, which creates a new unmodifiable map with the updated contents.
408-
409-
Example of updating the context in 1.0 M3:
410-
411-
[source,java]
412-
----
413-
@Override
414-
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
415-
416-
this.advisedRequest = advisedRequest.updateContext(context -> {
417-
context.put("aroundCallBefore" + getName(), "AROUND_CALL_BEFORE " + getName()); // Add multiple key-value pairs
418-
context.put("lastBefore", getName()); // Add a single key-value pair
419-
return context;
420-
});
421-
422-
// Method implementation continues...
423-
}
424-
----

0 commit comments

Comments
 (0)