////
Search

Spring AI ChatMemory SystemMessage 저장 문제

생성일
2025/11/12 02:25
태그

개요

필자는 이전 Spring AI 프로젝트 이슈를 살펴보는 중 버그 픽스 요청 이슈를 확인했다.
주된 문제는 ChatMemory를 이용하는데 SystemMessage의 순서가 문제가 되어 API에서 오류를 발생시킨 것이 문제였다.
기여를 위해서 원인을 분석했고 실제로 현재 다른 컨트리뷰터가 올린 PR이 Close되기 전까지는 문제가 지속될 것으로 보여서 블로그에 정보를 남긴다.

원인

주된 원인은 현재 Spring AI는 SystemMessage를 Memory에 저장하지 않는것이 문제라고 말할 수 있다.
다만 필자의 생각은 일반적으로 SystemMessage는 메모리에 저장되지 않는게 맞다고 생각한다.
Memory는 동적인 메세지를 저장하는게 옳다고 생각한다.
그에 비해 시스템 메세지는 동적이지 않고 정적인편이다.
때문에 매번 Append 되더라도 크게 문제없는게 아닌가 라는게 필자의 생각이다.
하지만 현재 이슈의 논의 방향은 이게 설계 철학에 맞는가로 진행되고 있다.
아무튼 일부 모델들은 SystemMessage가 “반드시” History 가장 상단에 위치해야 한다.
이를 어길 경우 Validation 오류를 발생시키기 때문에 원인을 해결하는 가장 쉬운 방법은 SystemMessage를 가장 상단으로 올리는 것이었다.

임시방편 해결책

필자는 처음 PR을 통해서 해당 메세지의 순서를 조정하려 했었다.
하지만 다른 컨트리뷰터의 리뷰를 통해서 해당 방식이 사이드 이펙트를 일으킬 우려가 있다는 말을 수용하여 해당 PR을 close하였다.
때문에 Issue에는 근본적 해결 방법(버그 픽스)이 아닌 일시적으로 문제를 해결할 수 있는 방안을 제시했는데
방안은 Custom Advisor를 구현해서 직접 메모리의 순서를 조정하는 것이다.
주요 컨셉은 다음과 같다
!! Error Case !! Memory(UserMessage, AssistMessage) -> MemoryAdvisor = Memory + [SystemMessage, UserMessage] => Messages(UserMessage, AssistMessage, SystemMessage, UserMessage) !! Correct Case !! Memory(UserMessage, AssistMessage) -> MemoryAdvisor = Messages(Memory + [SystemMessage, UserMessage]) -> CustomMemoryAdvisor = sort(Messages) => Messages(SystemMessage, UserMessage, AssistMessage, UserMessage)
Plain Text
복사
이 처럼 SystemMessage를 가장 상단으로 맞춰주는 Advisor를 구현하여 메모리 어드바이서 이후 실행 되게 끼워넣으면 된다.
내가 Issue에 첨부한 코드는 다음과 같다.
public class SystemFirstSortingAdvisor implements BaseAdvisor { @Override public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) { List<Message> processedMessages = chatClientRequest.prompt().getInstructions(); processedMessages.sort(Comparator.comparing(m -> m.getMessageType() == MessageType.SYSTEM ? 0 : 1)); return chatClientRequest.mutate() .prompt(chatClientRequest.prompt().mutate().messages(processedMessages).build()) .build(); } @Override public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) { return chatClientResponse; // no-op } @Override public int getOrder() { return 0; // larger than MessageChatMemoryAdvisor so it runs afterwards } } ... @Bean ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) { return ChatClient.builder(chatModel) .defaultSystem("You are a funny, charming and witty assistant who is always wise and thoughtful in your responses.") .defaultAdvisors( MessageChatMemoryAdvisor.builder(chatMemory).build(), // Ensures SystemMessage is always first for model compatibility new SystemFirstSortingAdvisor() ) .build(); }
Java
복사

진행

해당 문제는 #4170 이슈에서 여전히 진행 중 이다.
아무래도 메인테이너가 바쁘다보니 확실한 결정을 내려주지 못하는 상태같다.
언제 적용될지 모르는 상태이지만 현재 PR이 적용된다면 SystemMessage가 중복으로 들어가지 않고
가장 상단에만 저장될 것이기에 문제는 해결될 것이라고 생각한다.
다만, 메모리에만 중복으로 안들어갈 뿐이지 결과적으로 SystemMessage가 두번 들어갈 수도 있는데
이런 경우엔 또 다른 Issue로 접수되지 않을까 생각된다.

마무리

아무튼 임시방편이긴 하지만 Advisor를 사용해 강제로 SystemMessage를 상단으로 올리면 정상 동작하는 코드를 만들어낼 수 있다.
어서 빨리 PR이 적용되길 바라며 이후 다른 이슈를 접할 시 해결 방안을 적어나가 보겠다.