Chat Memory
Chat Memory (채팅 메모리)
ChatMessage 를 손으로 관리하는 건 번거로워요. LangChain4j 는 ChatMemory 추상화와 함께 여러 즉시 사용 가능한 구현을 제공해요. 저수준 컴포넌트로 단독 사용하거나, AI Services 같은 고수준 컴포넌트의 일부로 쓸 수 있어요.
출처: 공식문서
Memory vs History
"메모리"와 "히스토리"는 비슷하지만 다른 개념이에요.
- History: 사용자와 AI 사이의 모든 메시지를 온전히 보존. UI 에서 사용자가 보는 것, 실제로 주고받은 대화 그 자체.
- Memory: LLM 이 대화를 "기억하는" 것처럼 행동하도록 일부 정보만 제시. 메모리 알고리즘에 따라 히스토리를 퇴출하거나, 여러 메시지를 요약하거나, 불필요한 세부를 빼거나, RAG 나 구조화 출력용 지시를 주입하는 식으로 변형해요.
LangChain4j 는 현재 "memory"만 제공하고 "history"는 제공하지 않아요. 전체 히스토리를 보존해야 한다면 직접 관리해야 해요.
퇴출 정책 (Eviction)
퇴출 정책이 필요한 이유는 세 가지예요:
- 컨텍스트 윈도우 한도 — LLM 이 한 번에 처리할 수 있는 토큰 수에 상한이 있으므로, 대화가 한도를 넘기 전에 옛 메시지를 퇴출해야 해요.
- 비용 제어 — 토큰마다 비용이 붙으므로 불필요한 메시지를 퇴출하면 비용이 줄어요.
- 지연 제어 — 보내는 토큰이 많을수록 처리 시간이 길어져요.
즉시 사용 가능한 구현 두 가지:
MessageWindowChatMemory— 슬라이딩 윈도우로 최근N개 메시지를 유지하고 넘치는 옛 메시지를 퇴출. 메시지마다 토큰 수가 달라서 빠른 프로토타이핑에 주로 유용.TokenWindowChatMemory— 역시 슬라이딩 윈도우지만 최근N개 토큰을 유지. 메시지는 나눌 수 없어서 한 메시지가 안 맞으면 통째로 퇴출.TokenCountEstimator가 필요해요.
영속화 (Persistence)
기본적으로 ChatMemory 구현은 ChatMessage 를 메모리에 저장해요. 영속화가 필요하면 커스텀 ChatMemoryStore 를 구현해 원하는 저장소에 메시지를 저장하면 돼요:
class PersistentChatMemoryStore implements ChatMemoryStore {
@Override
public List<ChatMessage> getMessages(Object memoryId) {
// TODO: Implement getting all messages from the persistent store by memory ID.
// ChatMessageDeserializer.messageFromJson(String) and
// ChatMessageDeserializer.messagesFromJson(String) helper methods can be used to
// easily deserialize chat messages from JSON.
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
// TODO: Implement updating all messages in the persistent store by memory ID.
// ChatMessageSerializer.messageToJson(ChatMessage) and
// ChatMessageSerializer.messagesToJson(List<ChatMessage>) helper methods can be used to
// easily serialize chat messages into JSON.
}
@Override
public void deleteMessages(Object memoryId) {
// TODO: Implement deleting all messages in the persistent store by memory ID.
}
}
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.id("12345")
.maxMessages(10)
.chatMemoryStore(new PersistentChatMemoryStore())
.build();
updateMessages()는 새ChatMessage가 추가될 때마다 호출돼요. LLM 과의 상호작용에서 보통UserMessage추가 시 한 번,AiMessage추가 시 한 번 해서 두 번 호출돼요. 모든 메시지가 함께 업데이트돼야 해요.- 메시지를 퇴출하면
ChatMemoryStore에서도 퇴출돼요. 퇴출 시updateMessages()가 퇴출된 메시지가 빠진 목록으로 호출돼요. getMessages()는ChatMemory사용자가 메시지를 요청할 때 호출되고,id인자로 여러 사용자·대화를 구분할 수 있어요.deleteMessages()는ChatMemory.clear()가 호출될 때 호출돼요. 안 쓰면 빈 메서드로 둬도 돼요.
SystemMessage 특별 취급
SystemMessage 는 다른 메시지 유형과 다르게 취급돼요:
- 한 번 추가되면 항상 유지돼요.
- 한 번에 하나만 보유돼요.
- 같은 내용의 새
SystemMessage는 무시되고, 다른 내용이면 이전 것을 대체해요. 기본으로 목록 끝에 추가되는데,alwaysKeepSystemMessageFirst속성으로 바꿀 수 있어요.
도구 메시지 특별 취급
ToolExecutionRequest 를 담은 AiMessage 가 퇴출되면, 관련된 고아 ToolExecutionResultMessage 들도 자동 퇴출돼요. OpenAI 같은 일부 프로바이더는 요청에 고아 ToolExecutionResultMessage 를 보내는 걸 금지하기 때문이에요.
:::note
I/O 를 수행하는 ChatMemory/ChatMemoryStore 는 비동기 대응판(addAsync/messagesAsync/setAsync, getMessagesAsync/updateMessagesAsync/deleteMessagesAsync)을 구현해, AI Service 를 논블로킹 모드로 쓸 때 스레드를 블로킹하지 않게 할 수 있어요. (참고: Non-blocking and Reactive)
:::
더 알아보기
- AI Services — AI Service 에 채팅 메모리 연동
- 도구 호출 — 도구 메시지 다루기
- 지원되는 모든 채팅 메모리 스토어: integrations/chat-memory-stores
- 예제: ServiceWithMemoryExample, ServiceWithPersistentMemoryForEachUserExample