Spring AI provides chat memory features that allow you to store(저장) and retrieve(검색) information across multiple interactions with the LLM.
Chat Memory vs Chat History
Chat Memory. 대화 전반에 걸친 문맥을 유지하기위해 사용한다.
Chat History. 대화 전체를 기록하기 위해 사용한다.
However, it is not the best fit for storing the chat history. If you need to maintain a complete record of all the messages exchanged, you should consider using a different approach, such as relying on Spring Data for efficient storage and retrieval of the complete chat history.
하지만 ChatHistory
를 정말로 기록을 위해 사용하는 것은 추천하지 않는다.(이럴거면 왜 만든?)
별도의 저장 방식을 강구해야한다.
Memory Types (일종의 Chat Memory Manager)
The choice of memory type can significantly impact the performance and behavior of your application.
메모리 선택이 애플리케이션 성능에 상당한 영향을 끼친다.
Message Window Chat Memory
슬라이딩 윈도우 방식으로 메세지를 관리하는 일종의 매니저 역할이다.
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository) // 원하는 저장 방식 지정
.maxMessages(10) // 관리할 최대 윈도우(메세지) 수
.build();
별도의 설정을 하지 않는다면 ChatMemory
bean에 자동으로 설정되는 메모리 타입이다.
Memory Storage (메모리 저장소)
ChatMemoryRepository
객체를 통해 사용할 Repository 객체를 생성하면 된다.
In-Memory Repository (인메모리 방식)
InMemoryChatMemoryRepository
는 ConcurrentHashMap
을 사용하여 저장한다.
별도의 config 설정이 없다면 default로 설정이 된다.
따라서 바로 사용이 가능하다.
@Autowired
ChatMemoryRepository chatMemoryRepository;
앞으로 나오겠지만 수동으로 생성하는 방식은 다음과 같다.
ChatMemoryRepository repository = new InMemoryChatMemoryRepository();
JdbcChatMemoryRepository(RDB)
JdbcChatMemoryRepository is a built-in implementation that uses JDBC to store messages in a relational database. It supports multiple databases out-of-the-box and is suitable for applications that require persistent storage of chat memory.
쉽게 말해 RDB에 저장할 수 있게 해준다.
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-chat-memory-repository-jdbc'
}
의존성을 추가하고
@Autowired
JdbcChatMemoryRepository chatMemoryRepository;
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
ChatMemory
가 저장될 ChatMemoryRepository
를 지정한다.
ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(new MysqlChatMemoryRepositoryDialect()) // 공식문서에는 Postgresql을 사용
.build();
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
명시적으로 선언할 경우 ChatMemoryRepository
에 JdbcChatMemoryRepository
를 Builder
로 조립한다.
application-chat-memory.yml
spring:
ai:
chat:
memory:
repository:
jdbc:
initialize-schema: always # 스키마 초기화 여부 설정
# embedded(default) : 임베디드환경(인메모리환경)에서만 스키마 초기화
# always : 항상 스키마 초기화
# never : 스키마 초기화 비활성화
schema: classpath:/schema/schema-mysql.sql # 동작시킬 스키마 경로
platform: # 감지된 DB 명 동적 할당 ex) Driver
resources/schema/schema-mysql.sql
CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
conversation_id VARCHAR(36) NOT NULL,
content TEXT NOT NULL,
type VARCHAR(10) NOT NULL,
`timestamp` TIMESTAMP NOT NULL,
CONSTRAINT TYPE_CHECK CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'))
);
CREATE INDEX IF NOT EXISTS SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX
ON SPRING_AI_CHAT_MEMORY(conversation_id, `timestamp`);
해당 스키마를 활용해 ChatMemory
를 저장한다.
2025-05-29 원래 default로 스키마가 생성되고 동작하지만 현재 오류가 있어서 수동으로 작성한다.
만약 default 스키마의 필드 명을 변경하거나 현재 지원하지 않는 DB를 사용할 경우
ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(new MyCustomDbDialect())
.build();
JdbcChatMemoryRepositoryDialect
의 구현체인 MyCustomDbDialect
를 만들어서 직접 쿼리문을 작성하면 된다.
현재 지원하는 DB는 PostgreSQL, MySQL/MariaDB, SQL Server, HSQLDB 가 있다.
추가로 알게된 정보로
jpa:
defer-datasource-initialization: true
jpa에는 다음과 같이 스키마의 로드 순서를 지정하는 설정이 있다.
기본은 false로 schema.sql 같은 스키마가 먼저 동작하고 Entity 기반 스키마가 동작한다.
따라서 해당 스키마에서 Entity에 정의해 둔 필드로 insert 구문을 날리게 될 경우 테이블이 없기 때문에 오류가 발생한다. 사전에 Entity를 먼저 작성해 둔 상태라도 ddl-auto 가 create 일 경우도 동일하게 테이블이 없다고 오류가 발생할 것이다.
별도의 스키마를 작성할 일이 흔치 않으니 알아만 둔다.
CassandraChatMemoryRepository
Apach Cassandra
전용 ChatMemoryRepository
이다. 사용해본적 없는 DB이기도 하고 사용법은 JdbcChatMemoryRepository
와 유사하기 때문에 이런게 있다고만 알고 넘어간다.
Neo4j ChatMemoryRepository
그래프 DB를 지원하는 ChatMemoryRepository
이다. 그래프 DB라는 것을 처음 알기도 했지만 수많은 그래프 DB 중에서 Spring AI에서는 왜 Neo4j를 선택했는가에 대해서는 DB 랭킹에서 납득했다.
나는 추천 시스템을 위한 LLM 서비스를 개발하고 있었기 때문에 추천 로직에 필요한 사용자 정보와 연관된 상품 정보를 매 추천 요청마다 불러와야하기 때문에 Join 비용이 염려되었다.
무엇보다 상품 정보가 비정형 데이터이지만 각 상품에 연관된 혜택 정보가 여러 곳에서 사용되고 있었기 때문에 어느정도 NoSQL과 RDB의 성격이 필요했다.
Node과 Relationship 이 두 가지의 성질이 이 조건을 충족하고 무엇보다 공식 지원이 되고 있었기 때문에 최종 기술 스택으로 채택될 수 있었다.
Memory in Chat Client
이제 채팅 내역을 어디에 저장할지 정했다면,
저장된 채팅을 LLM이 어떻게 사용할지 지정해야 한다.
MessageChatMemoryAdvisor
대화 내역을 있는 그대로 넘겨준다.
User: 안녕? Assistance: 안녕하세요. 무엇을 도와드릴까요? User: 내 이름은 hyeonZIP 이야. Assistance: 만나서 반갑습니다 hyeonZIP 님! 무엇을 도와드릴까요?
PromptChatMemoryAdvisor
기존 대화 내역에 추가해서 프롬프트를 추가하여 넘겨준다.
{instructions} Use the conversation memory from the MEMORY section to provide accurate answers. --------------------- MEMORY: {memory} ---------------------
이를 사용하려면 PromptTemplate 객체를 생성해서 보내줄 프롬프트와 함께 Advisor를 생성하면 된다.
VectorStoreChatMemoryAdvisor
이젠 대화를 벡터로 임베딩해서 저장한 다음, 유사도 검색을 통해 대화 내역에서 가장 유사한 정보를 LLM이 스스로 판단해서 불러오는 방식이다.
ChatBotConfig
다음과 같이 기본적인 채팅 내역을 저장하는 ChatClient를 정의했다.
현재 포스트와 관련없는 모든 설정은 지웠습니다.
@Configuration
@RequiredArgsConstructor
public class ChatBotConfig {
private final Neo4jChatMemoryRepository neo4jChatMemoryRepository;
/**
* Recommend Build
*/
@Bean
public ChatClient recommendClient(
ChatClient.Builder chatClientBuilder,
RecommendTool recommendTool) {
return chatClientBuilder
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory()).build()
)
.build();
}
/**
* ChatMemory Build
*/
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(neo4jChatMemoryRepository)
.maxMessages(10)
.build();
}
}