Bug description When calling
ChatClient.builder(chatModel)
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build()
.prompt()
.call()
.chatResponse()
MessageChatMemoryAdvisor
adds an empty UserMessage
in the chat memory (see line 94).
In our use case we send the user message in a POST, store it in the conversation, then send a GET to benefit from SseEmitter
. Hence the empty call to prompt()
.
Environment 1.0.0-M3
Expected behavior
When using an empty prompt MessageChatMemoryAdvisor
should probably not add an empty UserMessage
in the chat memory.
Comment From: DHKIM-0511
Fix PR https://github.com/spring-projects/spring-ai/pull/1708
Comment From: ThomasVitale
@fmunch thanks for reporting this. Could you elaborate a bit more on this use case? I'm asking because I see the empty prompt and, in particular, the absence of a user message in ChatClient
as an incorrect state (unless it's part of a function calling special flow). Indeed, the current snapshot version of Spring AI throws an exception if there's no user message (whereas in M3 an empty message was created, as you noticed) after adding null-safety to the ChatClient and Advisor APIs. Supporting a null/empty user message would make the behaviour of ChatClient
unpredictable and hide pesky errors from developers.
If I understood correctly, your application is adding user messages to the ChatMemory
object explicitly, outside the ChatClient
workflow. The MessageChatMemoryAdvisor
is designed to handle the entire lifecycle of a chat memory for each conversation using an internal ChatMemory
object. It might not be the best candidate for handling only parts of the lifecycle.
Since you have already a ChatMemory
object where you're storing a user message explicitly, you could pass directly the messages from the memory via the .prompt()
clause. And then save the response back into the memory.
Something like this:
var chatResponse = ChatClient.builder(chatModel).build()
.prompt(new Prompt(chatMemory.get(conversationId, lastN)))
.call()
.chatResponse();
chatMemory.add(chatResponse.getResult().getOutput());
You can also use the .messages()
clause, if you prefer.
var chatResponse = ChatClient.builder(chatModel).build()
.prompt()
.messages(chatMemory.get(conversationId, lastN))
.call()
.chatResponse();
chatMemory.add(chatResponse.getResult().getOutput());
Full, standalone example to showcase the behavior.
@Bean
CommandLineRunner chat(ChatClient.Builder chatClientBuilder) {
return _ -> {
var chatClient = chatClientBuilder.build();
var chatMemory = new InMemoryChatMemory();
var lastN = 10;
var conversationId = "007";
var userMessage1 = new UserMessage("My name is Bond. James Bond.");
chatMemory.add(conversationId, userMessage1);
var assistantMessage1 = chatClient
.prompt(new Prompt(chatMemory.get(conversationId, lastN)))
.call()
.chatResponse().getResult().getOutput();
chatMemory.add(conversationId, assistantMessage1);
System.out.println(assistantMessage1.toString());
var userMessage2 = new UserMessage("What's my name?");
chatMemory.add(conversationId, userMessage2);
var assistantMessage2 = chatClient
.prompt(new Prompt(chatMemory.get(conversationId, lastN)))
.call()
.chatResponse().getResult().getOutput();
chatMemory.add(conversationId, assistantMessage2);
System.out.println(assistantMessage2.toString());
};
}
Comment From: fmunch
@ThomasVitale Thank you for taking the time to reply to this.
To summarize, our Spring AI application has a REST API to add messages in a conversation (via POST calls) and to submit the conversation and stream the text/event-stream
response (which requires a GET call when using EventSource
AFAIK). The POST requests update the chat memory and the text/event-stream
request does not send anything, that's why the prompt is empty, everything is already stored in the chat memory.
Your suggestion of using .messages(chatMemory.get(conversationId, lastN))
would work great for us. I initially decided to use the advisor because it was convenient and to benefit from the rest of it (handling the chatMemoryRetrieveSize
for example and what might come in the future).
Comment From: markpollack
closing ashe empty prompt behavior was fixed, and the special use case reported is supported as shown in the example.