Spring AI의 Chat Memory 기능을 MyBatis와 연동하여 AI와 사용자의 대화 내용을 데이터베이스에 저장하고, 이전 대화 내용을 다시 불러와 대화를 이어갈 수 있는 기능을 구현하였다.
기존에는 AI와의 대화가 한 번의 요청과 응답으로 끝나는 구조였다면, 이번에는 사용자의 대화 기록을 데이터베이스에 영속적으로 저장(Persistence) 하여 이전 대화를 기억하는 AI 서비스를 구현하는 방법을 학습하였다.
이를 위해 MyBatis를 이용하여 SQL을 직접 작성하고, Mapper 인터페이스를 통해 데이터베이스와 통신하는 구조를 설계하였다. 또한 Spring AI에서 제공하는 ChatMemoryRepository 인터페이스를 직접 구현하여 Spring AI와 MyBatis를 연결하는 방법도 익힐 수 있었다.
가장 먼저 이해해야 했던 개념은 Chat Memory였다.
일반적으로 생성형 AI는 사용자가 질문을 보내면 그 질문에 대해서만 답변을 생성한다.
예를 들어
사용자 : 나는 백엔드 개발자야.
AI : 반갑습니다.
-------------------------
사용자 : 내가 방금 무슨 직업이라고 했지?
AI : 알 수 없습니다.
이처럼 이전 대화를 기억하지 못한다.
하지만 Chat Memory를 사용하면 이전 대화를 저장해 두었다가 다음 요청 시 함께 AI에게 전달한다.
사용자 : 나는 백엔드 개발자야.
AI : 반갑습니다.
-------------------------
사용자 : 내가 방금 무슨 직업이라고 했지?
AI : 백엔드 개발자라고 말씀하셨습니다.
즉, Chat Memory는 AI가 기억력이 생긴 것처럼 보이도록 이전 대화를 함께 전달하는 기능이라는 점을 이해하였다.
Spring AI에서 제공하는 ChatMemoryRepository를 직접 구현하였다.
@Repository
@RequiredArgsConstructor
public class MyBatisChatMemoryRepository
implements ChatMemoryRepository
Spring AI는 Chat Memory를 어디에 저장할지 정해져 있지 않다.
대신
등 다양한 저장소를 사용할 수 있도록 ChatMemoryRepository 인터페이스를 제공한다.
이번 프로젝트에서는 MyBatis를 이용하여 MySQL에 저장하기 위해 직접 구현체를 작성하였다.
이를 통해 Spring AI의 인터페이스를 구현하여 원하는 저장 방식을 자유롭게 만들 수 있다는 점을 학습하였다.
MyBatis는 Repository가 SQL을 직접 실행하지 않는다.
반드시 Mapper를 통해 SQL을 실행한다.
@Mapper
public interface ChatMessageMapper {
List<String> findConversationIds();
List<ChatMessageMyBatis> findByConversationId(String conversationId);
void insertAll(List<ChatMessageMyBatis> messages);
void deleteByConversationId(String conversationId);
}
Mapper 인터페이스는 SQL과 Java를 연결해 주는 역할을 한다.
이번 프로젝트에서는
기능을 Mapper에 정의하였다.
실제 SQL은 XML Mapper에서 작성되고, Java에서는 메서드만 호출하면 SQL이 실행되는 구조라는 점을 이해하였다.
이를 통해 SQL과 Java 코드를 분리하는 MyBatis의 구조를 학습하였다.
이번 프로젝트에서는 여러 사용자의 대화를 구분하기 위해 Conversation ID를 사용하였다.
public record ChatDTO(
String message,
String conversationId
)
Controller에서는
dto.withID(session.getId())
를 이용하여 현재 세션 ID를 Conversation ID로 사용하였다.
사용자가 여러 명일 경우 모든 대화를 하나의 테이블에 저장하면 어떤 메시지가 누구의 것인지 구분할 수 없다.
따라서 세션 ID를 Conversation ID로 사용하여
conversation_id
AAA111
AAA111
AAA111
BBB222
BBB222
CCC333
처럼 사용자별 대화를 구분하였다.
이를 통해 AI 서비스에서도 사용자 식별이 매우 중요하다는 점을 학습하였다.
Spring AI는 Message 객체를 사용하지만
MyBatis는 Entity를 저장한다.
따라서 두 객체를 서로 변환하는 과정이 필요하였다.
public static ChatMessageMyBatis fromMessage(
Message message,
String conversationId,
int seq
)
그리고
public Message toMessage()
AI는 Message 객체를 사용한다.
하지만 데이터베이스에는 객체를 그대로 저장할 수 없기 때문에
conversation_id
message_type
content
seq
형태로 저장하였다.
필요할 때 다시 Message 객체로 변환하여 Spring AI가 사용할 수 있도록 구현하였다.
이를 통해 객체와 데이터베이스 간의 변환 과정을 직접 구현하는 방법을 학습하였다.
가장 중요한 로직은 saveAll()이었다.
chatMessageMapper.deleteByConversationId(conversationId);
...
chatMessageMapper.insertAll(chatMessages);
새로운 메시지가 저장될 때
기존 데이터를 UPDATE 하는 것이 아니라
기존 대화를 모두 삭제
↓
현재 메모리에 있는 대화를 모두 INSERT
하는 방식을 사용하였다.
예를 들어
USER
안녕
ASSISTANT
안녕하세요.
가 저장되어 있다가
새로운 질문
오늘 날씨 어때?
가 들어오면
기존 데이터를 삭제한 뒤
안녕
안녕하세요
오늘 날씨 어때?
맑습니다.
전체를 다시 저장한다.
이 방식은 코드가 단순하며 MessageWindowChatMemory와 잘 동작하도록 설계되어 있다는 점을 이해하였다.
이번 프로젝트에서는 모든 대화를 저장하지 않고 최근 메시지만 기억하도록 설정하였다.
MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(3)
maxMessages(3)은
최근 3개의 메시지만 AI에게 전달한다.
예를 들어
1
안녕
2
반가워
3
점심 추천해줘
4
김치찌개 추천
5
오늘 날씨는?
라면
AI는
점심 추천해줘
김치찌개 추천
오늘 날씨는?
만 전달받는다.
이를 통해 너무 많은 대화를 보내지 않아
효과를 얻을 수 있다는 점을 학습하였다.
MyBatis로 구현한 Chat Memory는 ChatClient에 연결하여 사용하였다.
@Bean
public ChatClient mybatisChatClient(
ChatModel chatModel,
@Qualifier("mybatisChatMemory")
ChatMemory memory
)
그리고
.advisors(
a -> a.param(
ChatMemory.CONVERSATION_ID,
dto.conversationId()
))
를 이용하여 현재 대화 ID를 전달하였다.
Conversation ID를 Advisor에 전달하면
Spring AI는
Conversation ID로 이전 대화를 조회하고
↓
Prompt 앞에 이전 대화를 붙인 뒤
↓
AI에게 함께 전송한다.
즉, 개발자가 직접 이전 대화를 Prompt에 이어붙이지 않아도 Spring AI가 자동으로 Chat Memory를 활용한다는 점을 학습하였다.
| 학습 내용 | 세부 내용 |
|---|---|
| MyBatis Mapper | Mapper 인터페이스를 통해 SQL과 Java를 연결하는 구조 |
| ChatMemoryRepository | Spring AI 인터페이스를 직접 구현하여 저장소를 개발하는 방법 |
| Conversation ID | 사용자별 대화를 구분하여 여러 사용자의 Chat Memory를 관리하는 방법 |
| Entity 변환 | Spring AI Message 객체와 DB Entity 간의 상호 변환 과정 |
| MessageWindowChatMemory | 최근 대화만 유지하여 토큰 사용량과 비용을 절감하는 방법 |
| ChatClient Advisor | Conversation ID를 전달하여 이전 대화를 자동으로 불러오는 방법 |
| 트랜잭션 처리 | 여러 메시지를 하나의 작업 단위로 저장하고 삭제하는 과정에서 데이터의 일관성을 유지하는 방법 |