Description
Bug description
The ChatClient
API makes it possible to pass system and user messages via the system()
and user()
clauses.
In that case, the final List<Message>
passed to the Prompt
contains the following:
SystemMessage
built fromsystem()
UserMessage
built fromuser()
The API also allows passing a list of messages directly via messages()
.
In that case, the final List<Message>
passed to the Prompt
contains the following:
List<Message>
built frommessages()
SystemMessage
built fromsystem()
UserMessage
built fromuser()
.
There is inconsistency in the two scenarios about where will the SystemMessage
built from system()
end up in the chat history.
Now, imagine using the few-shot prompting strategy and using the messages()
clause to pass the few-shot examples (list of UserMessage
and AssistantMessage
pairs). In that case, the SystemMessage
built from system()
ends up at the bottom of the list, which makes the few-shot prompting strategy not working in many cases due to the wrong position of the SystemMessage
.
A possible workaround is to pass the desired SystemMessage
via messages()
together with the few-shot examples, perhaps even failing the ChatClient call request if both messages()
and system()
+user()
are defined, but that would limit the convenience of the API.
Environment
- Spring AI 1.0.0-SNAPSHOT
- Java 22
Steps to reproduce
Single system message:
var content = chatClient.prompt()
.system("System text")
.messages(
new UserMessage("My question"),
new AssistantMessage("Your answer")
)
.call().content();
Multiple system messages:
var content = chatClient.prompt()
.system("System text")
.messages(
new SystemMessage("Historical system text"),
new UserMessage("My question"),
new AssistantMessage("Your answer")
)
.call().content();
Expected behavior
I would expect the use of system()
to result in a SystemMessage
always placed on the top of the message list, unless another one already exists passed via messages()
.
When messages()
is used and it does NOT include any SystemMessage
, I expect the following List<Message>
passed to the Prompt
:
SystemMessage
built fromsystem()
List<Message>
built frommessages()
UserMessage
built fromuser()
.
When messages()
is used and it DOES include any SystemMessage
, I expect the following List<Message>
passed to the Prompt
:
List<Message>
built frommessages()
(including one or moreSystemMessage
)SystemMessage
built fromsystem()
UserMessage
built fromuser()
.
There's room for introducing more structured support for few-shot prompting via the Advisor
API. I'm working on a few proposals in that direction, but this issue of the SystemMessage
might need fixing first.
I have a PR ready for implementing what described above, but I'm not 100% sure it's a good expected behaviour. It might be worth considering this issue in more general terms, including other common prompting strategies and the handling of the chat memory.
@tzolov what do you think? I'd be happy to discuss further about it and perhaps share a few experiments I've been working one.