Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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;

Expand Down Expand Up @@ -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 ---------------------

Expand Down Expand Up @@ -124,12 +126,9 @@ public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChai
: 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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,32 +56,23 @@ 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.

---------------------
LONG_TERM_MEMORY:
{long_term_memory}
---------------------
""";

private final String systemTextAdvise;

/**
* 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 final PromptTemplate systemPromptTemplate;

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) {
Expand Down Expand Up @@ -127,11 +118,8 @@ private ChatClientRequest before(ChatClientRequest chatClientRequest) {

// 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()
Expand Down Expand Up @@ -187,21 +175,26 @@ else if (message instanceof AssistantMessage assistantMessage) {

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);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
*/
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.

Expand All @@ -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) {
Expand Down Expand Up @@ -119,11 +125,8 @@ private ChatClientRequest before(ChatClientRequest chatClientRequest) {

// 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()
Expand Down Expand Up @@ -151,20 +154,25 @@ private void after(ChatClientResponse chatClientResponse) {

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);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ void qaCustomPromptTemplate() {
PromptTemplate customPromptTemplate = PromptTemplate.builder()
.renderer(StTemplateRenderer.builder().startDelimiterToken('$').endDelimiterToken('$').build())
.template("""
$query$

Context information is below, surrounded by ---------------------

Expand Down