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 <ThomasVitale@users.noreply.github.com>
This commit is contained in:
committed by
Mark Pollack
parent
2ea518686f
commit
d619e25cb5
@@ -25,6 +25,7 @@ import org.springframework.ai.chat.client.ChatClientRequest;
|
||||
import org.springframework.ai.chat.client.ChatClientResponse;
|
||||
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
|
||||
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
@@ -56,6 +57,7 @@ public class QuestionAnswerAdvisor implements BaseAdvisor {
|
||||
public static final String FILTER_EXPRESSION = "qa_filter_expression";
|
||||
|
||||
private static final PromptTemplate DEFAULT_PROMPT_TEMPLATE = new PromptTemplate("""
|
||||
{query}
|
||||
|
||||
Context information is below, surrounded by ---------------------
|
||||
|
||||
@@ -124,12 +126,9 @@ public class QuestionAnswerAdvisor implements BaseAdvisor {
|
||||
: documents.stream().map(Document::getText).collect(Collectors.joining(System.lineSeparator()));
|
||||
|
||||
// 3. Augment the user prompt with the document context.
|
||||
String augmentedUserText = this.promptTemplate.mutate()
|
||||
.template(chatClientRequest.prompt().getUserMessage().getText() + System.lineSeparator()
|
||||
+ this.promptTemplate.getTemplate())
|
||||
.variables(Map.of("question_answer_context", documentContext))
|
||||
.build()
|
||||
.render();
|
||||
UserMessage userMessage = chatClientRequest.prompt().getUserMessage();
|
||||
String augmentedUserText = this.promptTemplate
|
||||
.render(Map.of("query", userMessage.getText(), "question_answer_context", documentContext));
|
||||
|
||||
// 4. Update ChatClientRequest with augmented prompt.
|
||||
return chatClientRequest.mutate()
|
||||
|
||||
@@ -56,7 +56,8 @@ public class VectorStoreChatMemoryAdvisor extends AbstractChatMemoryAdvisor<Vect
|
||||
|
||||
private static final String DOCUMENT_METADATA_MESSAGE_TYPE = "messageType";
|
||||
|
||||
private static final String DEFAULT_SYSTEM_TEXT_ADVISE = """
|
||||
private static final PromptTemplate DEFAULT_SYSTEM_PROMPT_TEMPLATE = new PromptTemplate("""
|
||||
{instructions}
|
||||
|
||||
Use the long term conversation memory from the LONG_TERM_MEMORY section to provide accurate answers.
|
||||
|
||||
@@ -64,24 +65,14 @@ public class VectorStoreChatMemoryAdvisor extends AbstractChatMemoryAdvisor<Vect
|
||||
LONG_TERM_MEMORY:
|
||||
{long_term_memory}
|
||||
---------------------
|
||||
""";
|
||||
""");
|
||||
|
||||
private final String systemTextAdvise;
|
||||
private final PromptTemplate systemPromptTemplate;
|
||||
|
||||
/**
|
||||
* Constructor for VectorStoreChatMemoryAdvisor.
|
||||
* @param vectorStore the vector store instance used for managing and querying
|
||||
* documents.
|
||||
* @param defaultConversationId the default conversation ID used if none is provided
|
||||
* in the context.
|
||||
* @param chatHistoryWindowSize the window size for the chat history retrieval.
|
||||
* @param systemTextAdvise the system text advice used for the chat advisor system.
|
||||
* @param order the order of precedence for this advisor in the chain.
|
||||
*/
|
||||
private VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId,
|
||||
int chatHistoryWindowSize, String systemTextAdvise, int order) {
|
||||
int chatHistoryWindowSize, PromptTemplate systemPromptTemplate, int order) {
|
||||
super(vectorStore, defaultConversationId, chatHistoryWindowSize, true, order);
|
||||
this.systemTextAdvise = systemTextAdvise;
|
||||
this.systemPromptTemplate = systemPromptTemplate;
|
||||
}
|
||||
|
||||
public static Builder builder(VectorStore chatMemory) {
|
||||
@@ -127,11 +118,8 @@ public class VectorStoreChatMemoryAdvisor extends AbstractChatMemoryAdvisor<Vect
|
||||
|
||||
// 2. Augment the system message.
|
||||
SystemMessage systemMessage = chatClientRequest.prompt().getSystemMessage();
|
||||
String augmentedSystemText = PromptTemplate.builder()
|
||||
.template(systemMessage.getText() + System.lineSeparator() + this.systemTextAdvise)
|
||||
.variables(Map.of("long_term_memory", longTermMemory))
|
||||
.build()
|
||||
.render();
|
||||
String augmentedSystemText = this.systemPromptTemplate
|
||||
.render(Map.of("instructions", systemMessage.getText(), "long_term_memory", longTermMemory));
|
||||
|
||||
// 3. Create a new request with the augmented system message.
|
||||
ChatClientRequest processedChatClientRequest = chatClientRequest.mutate()
|
||||
@@ -187,21 +175,26 @@ public class VectorStoreChatMemoryAdvisor extends AbstractChatMemoryAdvisor<Vect
|
||||
|
||||
public static class Builder extends AbstractChatMemoryAdvisor.AbstractBuilder<VectorStore> {
|
||||
|
||||
private String systemTextAdvise = DEFAULT_SYSTEM_TEXT_ADVISE;
|
||||
private PromptTemplate systemPromptTemplate = DEFAULT_SYSTEM_PROMPT_TEMPLATE;
|
||||
|
||||
protected Builder(VectorStore chatMemory) {
|
||||
super(chatMemory);
|
||||
}
|
||||
|
||||
public Builder systemTextAdvise(String systemTextAdvise) {
|
||||
this.systemTextAdvise = systemTextAdvise;
|
||||
this.systemPromptTemplate = new PromptTemplate(systemTextAdvise);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder systemPromptTemplate(PromptTemplate systemPromptTemplate) {
|
||||
this.systemPromptTemplate = systemPromptTemplate;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VectorStoreChatMemoryAdvisor build() {
|
||||
return new VectorStoreChatMemoryAdvisor(this.chatMemory, this.conversationId, this.chatMemoryRetrieveSize,
|
||||
this.systemTextAdvise, this.order);
|
||||
this.systemPromptTemplate, this.order);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ import org.springframework.ai.chat.model.MessageAggregator;
|
||||
*/
|
||||
public class PromptChatMemoryAdvisor extends AbstractChatMemoryAdvisor<ChatMemory> {
|
||||
|
||||
private static final String DEFAULT_SYSTEM_TEXT_ADVISE = """
|
||||
private static final PromptTemplate DEFAULT_SYSTEM_PROMPT_TEMPLATE = new PromptTemplate("""
|
||||
{instructions}
|
||||
|
||||
Use the conversation memory from the MEMORY section to provide accurate answers.
|
||||
|
||||
@@ -55,29 +56,34 @@ public class PromptChatMemoryAdvisor extends AbstractChatMemoryAdvisor<ChatMemor
|
||||
{memory}
|
||||
---------------------
|
||||
|
||||
""";
|
||||
""");
|
||||
|
||||
private final String systemTextAdvise;
|
||||
private final PromptTemplate systemPromptTemplate;
|
||||
|
||||
public PromptChatMemoryAdvisor(ChatMemory chatMemory) {
|
||||
this(chatMemory, DEFAULT_SYSTEM_TEXT_ADVISE);
|
||||
this(chatMemory, DEFAULT_SYSTEM_PROMPT_TEMPLATE.getTemplate());
|
||||
}
|
||||
|
||||
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String systemTextAdvise) {
|
||||
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String systemPromptTemplate) {
|
||||
super(chatMemory);
|
||||
this.systemTextAdvise = systemTextAdvise;
|
||||
this.systemPromptTemplate = new PromptTemplate(systemPromptTemplate);
|
||||
}
|
||||
|
||||
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
|
||||
String systemTextAdvise) {
|
||||
this(chatMemory, defaultConversationId, chatHistoryWindowSize, systemTextAdvise,
|
||||
String systemPromptTemplate) {
|
||||
this(chatMemory, defaultConversationId, chatHistoryWindowSize, new PromptTemplate(systemPromptTemplate),
|
||||
Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER);
|
||||
}
|
||||
|
||||
public PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
|
||||
String systemTextAdvise, int order) {
|
||||
String systemPromptTemplate, int order) {
|
||||
this(chatMemory, defaultConversationId, chatHistoryWindowSize, new PromptTemplate(systemPromptTemplate), order);
|
||||
}
|
||||
|
||||
private PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize,
|
||||
PromptTemplate systemPromptTemplate, int order) {
|
||||
super(chatMemory, defaultConversationId, chatHistoryWindowSize, true, order);
|
||||
this.systemTextAdvise = systemTextAdvise;
|
||||
this.systemPromptTemplate = systemPromptTemplate;
|
||||
}
|
||||
|
||||
public static Builder builder(ChatMemory chatMemory) {
|
||||
@@ -119,11 +125,8 @@ public class PromptChatMemoryAdvisor extends AbstractChatMemoryAdvisor<ChatMemor
|
||||
|
||||
// 2. Augment the system message.
|
||||
SystemMessage systemMessage = chatClientRequest.prompt().getSystemMessage();
|
||||
String augmentedSystemText = PromptTemplate.builder()
|
||||
.template(systemMessage.getText() + System.lineSeparator() + this.systemTextAdvise)
|
||||
.variables(Map.of("memory", memory))
|
||||
.build()
|
||||
.render();
|
||||
String augmentedSystemText = this.systemPromptTemplate
|
||||
.render(Map.of("instructions", systemMessage.getText(), "memory", memory));
|
||||
|
||||
// 3. Create a new request with the augmented system message.
|
||||
ChatClientRequest processedChatClientRequest = chatClientRequest.mutate()
|
||||
@@ -151,20 +154,25 @@ public class PromptChatMemoryAdvisor extends AbstractChatMemoryAdvisor<ChatMemor
|
||||
|
||||
public static class Builder extends AbstractChatMemoryAdvisor.AbstractBuilder<ChatMemory> {
|
||||
|
||||
private String systemTextAdvise = DEFAULT_SYSTEM_TEXT_ADVISE;
|
||||
private PromptTemplate systemPromptTemplate = DEFAULT_SYSTEM_PROMPT_TEMPLATE;
|
||||
|
||||
protected Builder(ChatMemory chatMemory) {
|
||||
super(chatMemory);
|
||||
}
|
||||
|
||||
public Builder systemTextAdvise(String systemTextAdvise) {
|
||||
this.systemTextAdvise = systemTextAdvise;
|
||||
this.systemPromptTemplate = new PromptTemplate(systemTextAdvise);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder systemPromptTemplate(PromptTemplate systemPromptTemplate) {
|
||||
this.systemPromptTemplate = systemPromptTemplate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PromptChatMemoryAdvisor build() {
|
||||
return new PromptChatMemoryAdvisor(this.chatMemory, this.conversationId, this.chatMemoryRetrieveSize,
|
||||
this.systemTextAdvise, this.order);
|
||||
this.systemPromptTemplate, this.order);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -244,6 +244,32 @@ chatClient.prompt()
|
||||
.content();
|
||||
----
|
||||
|
||||
=== PromptChatMemoryAdvisor
|
||||
|
||||
==== Custom Template
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
* an `instructions` placeholder to receive the original system message.
|
||||
* a `memory` placeholder to receive the retrieved conversation memory.
|
||||
|
||||
=== VectorStoreChatMemoryAdvisor
|
||||
|
||||
==== Custom Template
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
* an `instructions` placeholder to receive the original system message.
|
||||
* a `long_term_memory` placeholder to receive the retrieved conversation memory.
|
||||
|
||||
== Memory in Chat Model
|
||||
|
||||
If you're working directly with a `ChatModel` instead of a `ChatClient`, you can manage the memory explicitly:
|
||||
|
||||
@@ -82,13 +82,18 @@ The `QuestionAnswerAdvisor` uses a default template to augment the user question
|
||||
|
||||
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.
|
||||
|
||||
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`.
|
||||
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:
|
||||
|
||||
* a `query` placeholder to receive the user question.
|
||||
* a `question_answer_context` placeholder to receive the retrieved context.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
PromptTemplate customPromptTemplate = PromptTemplate.builder()
|
||||
.renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
|
||||
.template("""
|
||||
<query>
|
||||
|
||||
Context information is below.
|
||||
|
||||
---------------------
|
||||
|
||||
@@ -36,6 +36,21 @@ For details, refer to:
|
||||
* `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.
|
||||
* 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.
|
||||
|
||||
==== Self-contained 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.
|
||||
If you were providing custom templates for the following advisors, you'll need to update them to ensure all expected placeholders are included.
|
||||
|
||||
* The `QuestionAnswerAdvisor` expects a template with the following placeholders (see xref:api/retrieval-augmented-generation.adoc#_questionansweradvisor[more details]):
|
||||
** a `query` placeholder to receive the user question.
|
||||
** a `question_answer_context` placeholder to receive the retrieved context.
|
||||
* The `PromptChatMemoryAdvisor` expects a template with the following placeholders (see xref:api/chat-memory.adoc#_promptchatmemoryadvisor[more details]):
|
||||
** an `instructions` placeholder to receive the original system message.
|
||||
** a `memory` placeholder to receive the retrieved conversation memory.
|
||||
* The `VectorStoreChatMemoryAdvisor` expects a template with the following placeholders (see xref:api/chat-memory.adoc#_vectorstorechatmemoryadvisor[more details]):
|
||||
** an `instructions` placeholder to receive the original system message.
|
||||
** a `long_term_memory` placeholder to receive the retrieved conversation memory.
|
||||
|
||||
=== Breaking Changes
|
||||
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.
|
||||
Hopefully Watson will reappear in a future version of Spring AI
|
||||
|
||||
@@ -128,6 +128,7 @@ public class QuestionAnswerAdvisorIT {
|
||||
PromptTemplate customPromptTemplate = PromptTemplate.builder()
|
||||
.renderer(StTemplateRenderer.builder().startDelimiterToken('$').endDelimiterToken('$').build())
|
||||
.template("""
|
||||
$query$
|
||||
|
||||
Context information is below, surrounded by ---------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user