-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Make chat memory available to the system message template
- Loading branch information
Showing
8 changed files
with
535 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
...c/main/java/io/quarkiverse/langchain4j/runtime/template/ChatMessageTemplateExtension.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package io.quarkiverse.langchain4j.runtime.template; | ||
|
||
import java.util.List; | ||
import java.util.StringJoiner; | ||
|
||
import dev.langchain4j.data.message.AiMessage; | ||
import dev.langchain4j.data.message.ChatMessage; | ||
import io.quarkus.qute.TemplateExtension; | ||
|
||
@TemplateExtension | ||
public class ChatMessageTemplateExtension { | ||
|
||
/** | ||
* Extracts and formats a dialogue between the user and the assistant from a list of chat messages. The user and assistant | ||
* messages are prefixed with the provided {@code userPrefix} and {@code assistantPrefix}, separated by the specified | ||
* {@code delimiter}. | ||
* | ||
* @param chatMessages the list of chat messages to process. | ||
* @param userPrefix the prefix for user messages. | ||
* @param assistantPrefix the prefix for assistant messages. | ||
* @param delimiter the delimiter between each message. | ||
* @return A formatted string representing the conversation between the user and the assistant. | ||
*/ | ||
static String extractDialogue(List<ChatMessage> chatMessages, String userPrefix, String assistantPrefix, String delimiter) { | ||
|
||
if (chatMessages == null || chatMessages.isEmpty()) | ||
return ""; | ||
|
||
StringJoiner joiner = new StringJoiner(delimiter == null ? "\n" : delimiter); | ||
userPrefix = (userPrefix == null) ? "User: " : userPrefix; | ||
assistantPrefix = (assistantPrefix == null) ? "Assistant: " : assistantPrefix; | ||
|
||
for (ChatMessage chatMessage : chatMessages) { | ||
switch (chatMessage.type()) { | ||
case AI -> { | ||
AiMessage aiMessage = (AiMessage) chatMessage; | ||
if (!aiMessage.hasToolExecutionRequests()) | ||
joiner.add("%s%s".formatted(assistantPrefix, aiMessage.text())); | ||
} | ||
case USER -> joiner.add("%s%s".formatted(userPrefix, chatMessage.text())); | ||
case SYSTEM, TOOL_EXECUTION_RESULT -> { | ||
continue; | ||
} | ||
} | ||
} | ||
|
||
return joiner.toString(); | ||
} | ||
|
||
/** | ||
* Extracts and formats a dialogue between the user and the assistant from a list of chat messages. | ||
* | ||
* @param chatMessages the list of chat messages to process. | ||
* @param delimiter the delimiter between each message. | ||
* @return A formatted string representing the conversation between the user and the assistant. | ||
* | ||
*/ | ||
static String extractDialogue(List<ChatMessage> chatMessages, String delimiter) { | ||
return extractDialogue(chatMessages, null, null, delimiter); | ||
} | ||
|
||
/** | ||
* Extracts and formats a dialogue between the user and the assistant from a list of chat messages. | ||
* | ||
* @param chatMessages the list of chat messages to process. | ||
* @return A formatted string representing the conversation between the user and the assistant. | ||
* | ||
*/ | ||
static String extractDialogue(List<ChatMessage> chatMessages) { | ||
return extractDialogue(chatMessages, null, null, null); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
== Prompt Generation | ||
|
||
When writing a prompt, it may be useful to access or modify some of the variables passed as input to the `AiService`. | ||
https://quarkus.io/guides/qute[Qute] can be used to automatically handle these variables within the prompt. | ||
|
||
For example, suppose you want to create a prompt that, given a conversation and a follow-up question, rephrases the follow-up question as a standalone question. https://quarkus.io/guides/qute[Qute] simplifies this by allowing you to define the prompt in the following format: | ||
|
||
[source,java] | ||
---- | ||
@SystemMessage(""" | ||
Given the following conversation and a follow-up question, | ||
rephrase the follow-up question to be a standalone question. | ||
Context: | ||
{#for m in chatMessages} | ||
{#if m.type.name() == "USER"} | ||
User: {m.text()} | ||
{/if} | ||
{#if m.type.name() == "AI"} | ||
Assistant: {m.text()} | ||
{/if} | ||
{/for}""") | ||
public String rephrase(List<ChatMessage> chatMessages, @UserMessage String question); | ||
---- | ||
|
||
In this example, the `chatMessages` list is automatically processed by https://quarkus.io/guides/qute[Qute] and transformed into the following format: | ||
|
||
[source] | ||
---- | ||
User: <text> | ||
Assistant: <text> | ||
... | ||
---- | ||
|
||
This allows for the dynamic construction of prompts based on the provided input. For more information on how to use https://quarkus.io/guides/qute[Qute], see the official documentation. | ||
|
||
== ChatMessage Formatting with TemplateExtensions | ||
|
||
In the previous section we described how to use https://quarkus.io/guides/qute[Qute] to dynamically manage variables passed to an `AiService`. To simplify the prompt structure, a https://quarkus.io/guides/qute-reference#template_extension_methods[TemplateExtension] is provided for `List<ChatMessage>` objects that provides methods to automatically format the contents of the list. This means that whenever a `List<ChatMessage>` is passed as a parameter to an `AiService`, the extension methods can be used to format the list without having to manually write loops or conditionals. | ||
|
||
The list of extension methods are: | ||
|
||
- `extractDialogue(userPrefix, assistantPrefix, delimiter)`: + | ||
Formats the conversation by applying custom prefixes for user and assistant messages, and custom delimiter to separate them. This method is the most flexible and allows full customisation of the output format. | ||
|
||
- `extractDialogue(delimiter)`: + | ||
Formats the conversation using the default prefixes (`User:` and `Assistant:`) but allows for the specification of a custom delimiter between messages. | ||
|
||
- `extractDialogue()`: + | ||
Provides the simplest formatting, using the default prefixes (`User:` and `Assistant:`) and separating messages with a newline. This is useful for basic formatting without the need for additional customization. | ||
|
||
*Example 1: Using custom prefixes and delimiter*: | ||
|
||
[source,java] | ||
---- | ||
@SystemMessage(""" | ||
Given the following conversation and a follow-up question, | ||
rephrase the follow-up question to be a standalone question. | ||
Context: | ||
{chatMessages.extractDialogue("U:", "A:", "|")}""") | ||
public String rephrase(List<ChatMessage> chatMessages, @UserMessage String question); | ||
---- | ||
This would format the conversation using `U:` and `A:` as prefixes, and `|` as the delimiter between messages. | ||
|
||
*Example 2: Using a custom delimiter*: | ||
|
||
[source,java] | ||
---- | ||
@SystemMessage(""" | ||
Given the following conversation and a follow-up question, | ||
rephrase the follow-up question to be a standalone question. | ||
Context: | ||
{chatMessages.extractDialogue("-")}""") | ||
public String rephrase(List<ChatMessage> chatMessages, @UserMessage String question); | ||
---- | ||
In this case, the conversation will be formatted with the default `User:` and `Assistant:` prefixes, but messages will be separated by `-`. | ||
|
||
*Example 3: Using the default formatting*: | ||
|
||
[source,java] | ||
---- | ||
@SystemMessage(""" | ||
Given the following conversation and a follow-up question, | ||
rephrase the follow-up question to be a standalone question. | ||
Context: | ||
{chatMessages.extractDialogue}""") | ||
public String rephrase(List<ChatMessage> chatMessages, @UserMessage String question); | ||
---- | ||
This will format the conversation using the default prefixes (`User:` and `Assistant:`) and a newline between each message, resulting in a simple structured output. | ||
|
||
== Using the `chat_memory` placeholder | ||
|
||
When working with `AiService` instances that have memory enabled, you have access to a special placeholder called `chat_memory`. This placeholder allows you to refer directly to the list of `ChatMessage` objects stored in the memory of the `AiService`, simplifying your prompt construction. | ||
|
||
Instead of passing the `List<ChatMessage>` as a parameter, you can use the `chat_memory` placeholder in your `@SystemMessage` to automatically include the conversation history. + | ||
|
||
Since `chat_memory` refers to a `List<ChatMessage>`, you can use the https://quarkus.io/guides/qute-reference#template_extension_methods[TemplateExtension] methods available for `List<ChatMessage>` to format the list directly in the prompt. | ||
|
||
*Example*: | ||
|
||
[source,java] | ||
---- | ||
@SystemMessage(""" | ||
Given the following conversation and a follow-up question, | ||
rephrase the follow-up question to be a standalone question. | ||
Context: | ||
{chat_memory.extractDialogue}""") | ||
public String rephrase(@UserMessage String question); | ||
---- | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.