Skip to content

Commit 6380b19

Browse files
ThomasVitalenamsoo2
authored andcommitted
Self-contained prompt templates in advisors
The built-in advisors that perform prompt augmentation have been updated to use self-contained templates. The goal is for each advisor to be able to perform templating operations without affecting nor being affected by templating and prompt decisions in other advisors. * QuestionAnswerAdvisor * PromptChatMemoryAdvisor * VectorStoreChatMemoryAdvisor Documentation and upgrade notes have been updated accordingly Signed-off-by: Thomas Vitale <[email protected]> Signed-off-by: minsoo.nam <[email protected]>
1 parent e2c4f36 commit 6380b19

File tree

7 files changed

+97
-50
lines changed

7 files changed

+97
-50
lines changed

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.ai.chat.client.ChatClientResponse;
2626
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
2727
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
28+
import org.springframework.ai.chat.messages.UserMessage;
2829
import reactor.core.scheduler.Scheduler;
2930
import reactor.core.scheduler.Schedulers;
3031

@@ -56,6 +57,7 @@ public class QuestionAnswerAdvisor implements BaseAdvisor {
5657
public static final String FILTER_EXPRESSION = "qa_filter_expression";
5758

5859
private static final PromptTemplate DEFAULT_PROMPT_TEMPLATE = new PromptTemplate("""
60+
{query}
5961
6062
Context information is below, surrounded by ---------------------
6163
@@ -124,12 +126,9 @@ public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChai
124126
: documents.stream().map(Document::getText).collect(Collectors.joining(System.lineSeparator()));
125127

126128
// 3. Augment the user prompt with the document context.
127-
String augmentedUserText = this.promptTemplate.mutate()
128-
.template(chatClientRequest.prompt().getUserMessage().getText() + System.lineSeparator()
129-
+ this.promptTemplate.getTemplate())
130-
.variables(Map.of("question_answer_context", documentContext))
131-
.build()
132-
.render();
129+
UserMessage userMessage = chatClientRequest.prompt().getUserMessage();
130+
String augmentedUserText = this.promptTemplate
131+
.render(Map.of("query", userMessage.getText(), "question_answer_context", documentContext));
133132

134133
// 4. Update ChatClientRequest with augmented prompt.
135134
return chatClientRequest.mutate()

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

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -56,32 +56,23 @@ public class VectorStoreChatMemoryAdvisor extends AbstractChatMemoryAdvisor<Vect
5656

5757
private static final String DOCUMENT_METADATA_MESSAGE_TYPE = "messageType";
5858

59-
private static final String DEFAULT_SYSTEM_TEXT_ADVISE = """
59+
private static final PromptTemplate DEFAULT_SYSTEM_PROMPT_TEMPLATE = new PromptTemplate("""
60+
{instructions}
6061
6162
Use the long term conversation memory from the LONG_TERM_MEMORY section to provide accurate answers.
6263
6364
---------------------
6465
LONG_TERM_MEMORY:
6566
{long_term_memory}
6667
---------------------
67-
""";
68-
69-
private final String systemTextAdvise;
70-
71-
/**
72-
* Constructor for VectorStoreChatMemoryAdvisor.
73-
* @param vectorStore the vector store instance used for managing and querying
74-
* documents.
75-
* @param defaultConversationId the default conversation ID used if none is provided
76-
* in the context.
77-
* @param chatHistoryWindowSize the window size for the chat history retrieval.
78-
* @param systemTextAdvise the system text advice used for the chat advisor system.
79-
* @param order the order of precedence for this advisor in the chain.
80-
*/
68+
""");
69+
70+
private final PromptTemplate systemPromptTemplate;
71+
8172
private VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId,
82-
int chatHistoryWindowSize, String systemTextAdvise, int order) {
73+
int chatHistoryWindowSize, PromptTemplate systemPromptTemplate, int order) {
8374
super(vectorStore, defaultConversationId, chatHistoryWindowSize, true, order);
84-
this.systemTextAdvise = systemTextAdvise;
75+
this.systemPromptTemplate = systemPromptTemplate;
8576
}
8677

8778
public static Builder builder(VectorStore chatMemory) {
@@ -127,11 +118,8 @@ private ChatClientRequest before(ChatClientRequest chatClientRequest) {
127118

128119
// 2. Augment the system message.
129120
SystemMessage systemMessage = chatClientRequest.prompt().getSystemMessage();
130-
String augmentedSystemText = PromptTemplate.builder()
131-
.template(systemMessage.getText() + System.lineSeparator() + this.systemTextAdvise)
132-
.variables(Map.of("long_term_memory", longTermMemory))
133-
.build()
134-
.render();
121+
String augmentedSystemText = this.systemPromptTemplate
122+
.render(Map.of("instructions", systemMessage.getText(), "long_term_memory", longTermMemory));
135123

136124
// 3. Create a new request with the augmented system message.
137125
ChatClientRequest processedChatClientRequest = chatClientRequest.mutate()
@@ -187,21 +175,26 @@ else if (message instanceof AssistantMessage assistantMessage) {
187175

188176
public static class Builder extends AbstractChatMemoryAdvisor.AbstractBuilder<VectorStore> {
189177

190-
private String systemTextAdvise = DEFAULT_SYSTEM_TEXT_ADVISE;
178+
private PromptTemplate systemPromptTemplate = DEFAULT_SYSTEM_PROMPT_TEMPLATE;
191179

192180
protected Builder(VectorStore chatMemory) {
193181
super(chatMemory);
194182
}
195183

196184
public Builder systemTextAdvise(String systemTextAdvise) {
197-
this.systemTextAdvise = systemTextAdvise;
185+
this.systemPromptTemplate = new PromptTemplate(systemTextAdvise);
186+
return this;
187+
}
188+
189+
public Builder systemPromptTemplate(PromptTemplate systemPromptTemplate) {
190+
this.systemPromptTemplate = systemPromptTemplate;
198191
return this;
199192
}
200193

201194
@Override
202195
public VectorStoreChatMemoryAdvisor build() {
203196
return new VectorStoreChatMemoryAdvisor(this.chatMemory, this.conversationId, this.chatMemoryRetrieveSize,
204-
this.systemTextAdvise, this.order);
197+
this.systemPromptTemplate, this.order);
205198
}
206199

207200
}

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

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
*/
4747
public class PromptChatMemoryAdvisor extends AbstractChatMemoryAdvisor<ChatMemory> {
4848

49-
private static final String DEFAULT_SYSTEM_TEXT_ADVISE = """
49+
private static final PromptTemplate DEFAULT_SYSTEM_PROMPT_TEMPLATE = new PromptTemplate("""
50+
{instructions}
5051
5152
Use the conversation memory from the MEMORY section to provide accurate answers.
5253
@@ -55,29 +56,34 @@ public class PromptChatMemoryAdvisor extends AbstractChatMemoryAdvisor<ChatMemor
5556
{memory}
5657
---------------------
5758
58-
""";
59+
""");
5960

60-
private final String systemTextAdvise;
61+
private final PromptTemplate systemPromptTemplate;
6162

6263
public PromptChatMemoryAdvisor(ChatMemory chatMemory) {
63-
this(chatMemory, DEFAULT_SYSTEM_TEXT_ADVISE);
64+
this(chatMemory, DEFAULT_SYSTEM_PROMPT_TEMPLATE.getTemplate());
6465
}
6566

66-
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String systemTextAdvise) {
67+
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String systemPromptTemplate) {
6768
super(chatMemory);
68-
this.systemTextAdvise = systemTextAdvise;
69+
this.systemPromptTemplate = new PromptTemplate(systemPromptTemplate);
6970
}
7071

7172
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
72-
String systemTextAdvise) {
73-
this(chatMemory, defaultConversationId, chatHistoryWindowSize, systemTextAdvise,
73+
String systemPromptTemplate) {
74+
this(chatMemory, defaultConversationId, chatHistoryWindowSize, new PromptTemplate(systemPromptTemplate),
7475
Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER);
7576
}
7677

7778
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
78-
String systemTextAdvise, int order) {
79+
String systemPromptTemplate, int order) {
80+
this(chatMemory, defaultConversationId, chatHistoryWindowSize, new PromptTemplate(systemPromptTemplate), order);
81+
}
82+
83+
private PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
84+
PromptTemplate systemPromptTemplate, int order) {
7985
super(chatMemory, defaultConversationId, chatHistoryWindowSize, true, order);
80-
this.systemTextAdvise = systemTextAdvise;
86+
this.systemPromptTemplate = systemPromptTemplate;
8187
}
8288

8389
public static Builder builder(ChatMemory chatMemory) {
@@ -119,11 +125,8 @@ private ChatClientRequest before(ChatClientRequest chatClientRequest) {
119125

120126
// 2. Augment the system message.
121127
SystemMessage systemMessage = chatClientRequest.prompt().getSystemMessage();
122-
String augmentedSystemText = PromptTemplate.builder()
123-
.template(systemMessage.getText() + System.lineSeparator() + this.systemTextAdvise)
124-
.variables(Map.of("memory", memory))
125-
.build()
126-
.render();
128+
String augmentedSystemText = this.systemPromptTemplate
129+
.render(Map.of("instructions", systemMessage.getText(), "memory", memory));
127130

128131
// 3. Create a new request with the augmented system message.
129132
ChatClientRequest processedChatClientRequest = chatClientRequest.mutate()
@@ -151,20 +154,25 @@ private void after(ChatClientResponse chatClientResponse) {
151154

152155
public static class Builder extends AbstractChatMemoryAdvisor.AbstractBuilder<ChatMemory> {
153156

154-
private String systemTextAdvise = DEFAULT_SYSTEM_TEXT_ADVISE;
157+
private PromptTemplate systemPromptTemplate = DEFAULT_SYSTEM_PROMPT_TEMPLATE;
155158

156159
protected Builder(ChatMemory chatMemory) {
157160
super(chatMemory);
158161
}
159162

160163
public Builder systemTextAdvise(String systemTextAdvise) {
161-
this.systemTextAdvise = systemTextAdvise;
164+
this.systemPromptTemplate = new PromptTemplate(systemTextAdvise);
165+
return this;
166+
}
167+
168+
public Builder systemPromptTemplate(PromptTemplate systemPromptTemplate) {
169+
this.systemPromptTemplate = systemPromptTemplate;
162170
return this;
163171
}
164172

165173
public PromptChatMemoryAdvisor build() {
166174
return new PromptChatMemoryAdvisor(this.chatMemory, this.conversationId, this.chatMemoryRetrieveSize,
167-
this.systemTextAdvise, this.order);
175+
this.systemPromptTemplate, this.order);
168176
}
169177

170178
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,32 @@ chatClient.prompt()
244244
.content();
245245
----
246246

247+
=== PromptChatMemoryAdvisor
248+
249+
==== Custom Template
250+
251+
The `PromptChatMemoryAdvisor` uses a default template to augment the system message with the retrieved conversation memory. You can customize this behavior by providing your own `PromptTemplate` object via the `.promptTemplate()` builder method.
252+
253+
NOTE: The `PromptTemplate` provided here customizes how the advisor merges retrieved memory with the system message. This is distinct from configuring a `TemplateRenderer` on the `ChatClient` itself (using `.templateRenderer()`), which affects the rendering of the initial user/system prompt content *before* the advisor runs. See xref:api/chatclient.adoc#_prompt_templates[ChatClient Prompt Templates] for more details on client-level template rendering.
254+
255+
The custom `PromptTemplate` can use any `TemplateRenderer` implementation (by default, it uses `StPromptTemplate` based on the https://www.stringtemplate.org/[StringTemplate] engine). The important requirement is that the template must contain the following two placeholders:
256+
257+
* an `instructions` placeholder to receive the original system message.
258+
* a `memory` placeholder to receive the retrieved conversation memory.
259+
260+
=== VectorStoreChatMemoryAdvisor
261+
262+
==== Custom Template
263+
264+
The `VectorStoreChatMemoryAdvisor` uses a default template to augment the system message with the retrieved conversation memory. You can customize this behavior by providing your own `PromptTemplate` object via the `.promptTemplate()` builder method.
265+
266+
NOTE: The `PromptTemplate` provided here customizes how the advisor merges retrieved memory with the system message. This is distinct from configuring a `TemplateRenderer` on the `ChatClient` itself (using `.templateRenderer()`), which affects the rendering of the initial user/system prompt content *before* the advisor runs. See xref:api/chatclient.adoc#_prompt_templates[ChatClient Prompt Templates] for more details on client-level template rendering.
267+
268+
The custom `PromptTemplate` can use any `TemplateRenderer` implementation (by default, it uses `StPromptTemplate` based on the https://www.stringtemplate.org/[StringTemplate] engine). The important requirement is that the template must contain the following two placeholders:
269+
270+
* an `instructions` placeholder to receive the original system message.
271+
* a `long_term_memory` placeholder to receive the retrieved conversation memory.
272+
247273
== Memory in Chat Model
248274

249275
If you're working directly with a `ChatModel` instead of a `ChatClient`, you can manage the memory explicitly:

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/retrieval-augmented-generation.adoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,18 @@ The `QuestionAnswerAdvisor` uses a default template to augment the user question
8282

8383
NOTE: The `PromptTemplate` provided here customizes how the advisor merges retrieved context with the user query. This is distinct from configuring a `TemplateRenderer` on the `ChatClient` itself (using `.templateRenderer()`), which affects the rendering of the initial user/system prompt content *before* the advisor runs. See xref:api/chatclient.adoc#_prompt_templates[ChatClient Prompt Templates] for more details on client-level template rendering.
8484

85-
The custom `PromptTemplate` can use any `TemplateRenderer` implementation (by default, it uses `StPromptTemplate` based on the https://www.stringtemplate.org/[StringTemplate] engine). The important requirement is that the template must contain a placeholder to receive the retrieved context, which the advisor provides under the key `question_answer_context`.
85+
The custom `PromptTemplate` can use any `TemplateRenderer` implementation (by default, it uses `StPromptTemplate` based on the https://www.stringtemplate.org/[StringTemplate] engine). The important requirement is that the template must contain the following two placeholders:
86+
87+
* a `query` placeholder to receive the user question.
88+
* a `question_answer_context` placeholder to receive the retrieved context.
8689

8790
[source,java]
8891
----
8992
PromptTemplate customPromptTemplate = PromptTemplate.builder()
9093
.renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
9194
.template("""
95+
<query>
96+
9297
Context information is below.
9398
9499
---------------------

spring-ai-docs/src/main/antora/modules/ROOT/pages/upgrade-notes.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ For details, refer to:
3636
* `MessageAggregator` has a new method to aggregate messages from `ChatClientRequest`. The previous method aggregating messages from the old `AdvisedRequest` has been removed, since it was already marked as deprecated in M8.
3737
* 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 yet. Same thing about the constructor.
3838

39+
==== Self-contained Templates in Advisors
40+
41+
The built-in advisors that perform prompt augmentation have been updated to use self-contained templates. The goal is for each advisor to be able to perform templating operations without affecting nor being affected by templating and prompt decisions in other advisors.
42+
If you were providing custom templates for the following advisors, you'll need to update them to ensure all expected placeholders are included.
43+
44+
* The `QuestionAnswerAdvisor` expects a template with the following placeholders (see xref:api/retrieval-augmented-generation.adoc#_questionansweradvisor[more details]):
45+
** a `query` placeholder to receive the user question.
46+
** a `question_answer_context` placeholder to receive the retrieved context.
47+
* The `PromptChatMemoryAdvisor` expects a template with the following placeholders (see xref:api/chat-memory.adoc#_promptchatmemoryadvisor[more details]):
48+
** an `instructions` placeholder to receive the original system message.
49+
** a `memory` placeholder to receive the retrieved conversation memory.
50+
* The `VectorStoreChatMemoryAdvisor` expects a template with the following placeholders (see xref:api/chat-memory.adoc#_vectorstorechatmemoryadvisor[more details]):
51+
** an `instructions` placeholder to receive the original system message.
52+
** a `long_term_memory` placeholder to receive the retrieved conversation memory.
53+
3954
=== Breaking Changes
4055
The Watson AI model was removed as it was based on the older text generation that is considered outdated as there is a new chat generation model available.
4156
Hopefully Watson will reappear in a future version of Spring AI

spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/client/advisor/QuestionAnswerAdvisorIT.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ void qaCustomPromptTemplate() {
128128
PromptTemplate customPromptTemplate = PromptTemplate.builder()
129129
.renderer(StTemplateRenderer.builder().startDelimiterToken('$').endDelimiterToken('$').build())
130130
.template("""
131+
$query$
131132
132133
Context information is below, surrounded by ---------------------
133134

0 commit comments

Comments
 (0)