Skip to content

단기 메모리 관리 전략

긴 대화 처리와 MongoDB 영구 저장


학습 목표

  • 긴 대화(Long Conversation)에서 Context Window 초과 문제가 발생하는 원인을 이해한다.
  • 메시지 트림(trimMessages)과 메시지 삭제(removeMessage)의 차이를 구분하고, 상황에 맞게 선택할 수 있다.
  • MongoDB Atlas를 활용하여 대화 상태를 영구 저장하는 프로덕션 환경을 구축할 수 있다.
  • 체크포인터 기반 메모리 조회/삭제 API를 활용할 수 있다.

1. 긴 대화(Long Conversation) 관리의 필요성

LLM은 한 번에 처리할 수 있는 토큰 수에 제한이 있다. 이를 Context Window라 부르며, 대화가 길어지면 이 크기를 초과하게 된다. Context Window를 초과하면 LLM은 이전 대화 내용을 참조할 수 없게 되거나, 아예 오류가 발생한다.

이 문제를 해결하기 위해 LangGraph와 LangChain은 5가지 관리 전략을 제공한다.

전략설명
메시지 트림최근 N 토큰만 읽어오고, 나머지는 생략 (원본 유지)
메시지 삭제오래된 메시지를 State에서 영구적으로 제거
메시지 요약오래된 메시지를 요약본으로 교체
체크포인터 활용대화 상태를 외부 저장소에 저장하여 복원
커스텀 전략위 전략들을 조합하거나 도메인에 맞게 직접 구현

이번 챕터에서는 트림과 삭제를 중점적으로 다루고, 프로덕션 환경을 위한 MongoDB 영구 저장까지 다룬다.


2. 메시지 트림 (trimMessages)

개념

LangChain의 trimMessages 유틸리티 함수는 대화 메시지 목록에서 최근 N 토큰에 해당하는 메시지만 추출한다. 핵심은 원본 데이터를 삭제하지 않는다는 점이다. 저장소에는 전체 대화가 그대로 남아 있고, LLM에 전달하는 시점에서만 필터링한다.

동작 원리

max_tokens에 따른 동작 차이

설정동작결과
max_tokens=64최근 소량의 메시지만 포함"내 이름은 바비야" 생략 → 이름 기억 못함
max_tokens=256더 많은 이전 메시지 포함"내 이름은 바비야" 포함 → 이름 기억함

max_tokens 값을 얼마로 설정하느냐에 따라 LLM이 참조할 수 있는 대화 범위가 결정된다. 값이 너무 작으면 중요한 맥락을 놓치고, 너무 크면 Context Window를 효율적으로 활용하지 못한다.

구현 패턴

python
from langchain_core.messages import trim_messages

def call_model(state):
    # 원본 메시지에서 최근 64 토큰만 추출
    trimmed = trim_messages(state["messages"], max_tokens=64)
    # 트림된 메시지만 LLM에 전달
    return llm.invoke(trimmed)

핵심 특징:

  • 저장소의 원본 메시지는 그대로 유지된다.
  • LLM 호출 시점에서만 필터링이 적용된다.
  • max_tokens 값을 조절하여 맥락 범위를 유연하게 제어할 수 있다.
  • 이전 대화가 필요해지면 max_tokens를 늘리기만 하면 된다.

3. 메시지 삭제 (removeMessage)

개념

LangGraph의 removeMessage 유틸리티 함수는 지정한 개수를 넘어가는 오래된 메시지를 State에서 영구적으로 삭제한다. 트림과 달리 한번 삭제된 메시지는 복구할 수 없다.

트림과 삭제의 비교

비교 항목trimMessagesremoveMessage
원본 데이터전체 유지영구 삭제
적용 시점LLM 호출 시 (읽기 단계)State 갱신 시 (쓰기 단계)
복구 가능성가능 (max_tokens 조절)불가능
메모리 절약저장소 공간 계속 사용삭제된 만큼 절약
사용 도구LangChain trim_messagesLangGraph removeMessage

동작 예시

설정동작결과
최근 1개만 유지"내 이름은 바비야" 등 오래된 메시지 삭제이름 기억 못함
최근 3개 유지"내 이름은 바비야" 포함 범위이름 기억함

선택 기준

  • 트림: 데이터 보존이 중요하거나, 나중에 전체 대화를 참조할 가능성이 있을 때
  • 삭제: 메모리 절약이 중요하거나, 오래된 대화를 영구적으로 정리해야 할 때

4. MongoDB 영구 저장

InMemorySaver의 한계

지금까지 사용한 InMemorySaver메모리 기반 체크포인터다. 프로세스가 종료되면 저장된 모든 대화 상태가 소실된다. 개발과 테스트에는 편리하지만, 프로덕션 환경에서는 사용할 수 없다.

체크포인터저장 위치프로세스 종료 시용도
InMemorySaver메모리 (RAM)데이터 소실개발/테스트
MongoDBSaverMongoDB (디스크)데이터 유지프로덕션
PostgresSaverPostgreSQL (디스크)데이터 유지프로덕션
RedisSaverRedis (메모리+디스크)설정에 따라 다름프로덕션

아키텍처

MongoDB Atlas 설정 단계

Step 1. 회원가입 및 클러스터 생성

MongoDB Atlas(https://www.mongodb.com/atlas)에 가입한 뒤, Free Plan(무료 512MB)으로 클러스터를 생성한다. 리전은 AWS Seoul(ap-northeast-2) 을 선택하면 한국에서 가장 빠른 응답 속도를 얻을 수 있다.

Step 2. Network Access 설정

개발 편의를 위해 모든 IP에서 접근을 허용한다. 프로덕션 환경에서는 특정 IP만 허용하는 것이 보안상 바람직하다.

  • Network Access → Add IP Address → 0.0.0.0/0 (모든 IP 허용)

Step 3. Database Access 설정

데이터베이스 관리자 계정을 생성한다.

  • Database Access → Add New Database User
  • Authentication Method: Password
  • 권한: Atlas Admin (또는 readWriteAnyDatabase)

Step 4. Connection String 획득

클러스터 대시보드에서 Connect → Drivers → Python을 선택하면 Connection String을 확인할 수 있다.

mongodb+srv://<username>:<password>@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority

구현 코드

python
from pymongo import MongoClient
from langgraph.checkpoint.mongodb import MongoDBSaver

# MongoDB 연결
connection_string = "mongodb+srv://admin:password@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority"
client = MongoClient(connection_string)

# MongoDB 체크포인터 생성
checkpointer = MongoDBSaver(client)

# 그래프 컴파일 시 체크포인터만 교체
graph = builder.compile(checkpointer=checkpointer)

# 이후 코드는 InMemorySaver와 완전히 동일
config = {"configurable": {"thread_id": "conversation-1"&#125;&#125;
result = graph.invoke({"messages": [...]}, config=config)

핵심 포인트: 체크포인터만 InMemorySaver에서 MongoDBSaver로 교체하면 나머지 코드는 한 줄도 바꿀 필요가 없다. LangGraph의 체크포인터 추상화가 잘 설계되어 있기 때문이다.


5. 메모리 조회/삭제 API

체크포인터에 저장된 대화 상태를 프로그래밍 방식으로 조회하거나 삭제할 수 있다. thread_id를 키로 특정 스레드의 전체 대화 내역을 관리한다.

상태 조회

python
# 방법 1: 체크포인터에서 직접 조회
config = {"configurable": {"thread_id": "conversation-1"&#125;&#125;
checkpoint_tuple = checkpointer.get_tuple(config)
print(checkpoint_tuple)

# 방법 2: 그래프를 통해 조회
state = graph.get_state(config)
print(state.values["messages"])
  • checkpointer.get_tuple(config): 체크포인트의 전체 메타데이터를 포함한 튜플을 반환한다.
  • graph.get_state(config): State 객체 형태로 현재 상태를 반환한다. 대화 메시지만 필요하면 이 방법이 더 간결하다.

스레드 삭제

python
# thread_id에 해당하는 전체 대화 내역 삭제
checkpointer.delete_thread("conversation-1")
  • 삭제 후 해당 thread_id로 조회하면 빈 상태가 반환된다.
  • 삭제는 되돌릴 수 없으므로, 필요한 경우 삭제 전에 백업을 권장한다.

API 정리

API설명반환값
checkpointer.get_tuple(config)체크포인트 전체 메타데이터 조회CheckpointTuple
graph.get_state(config)현재 State 상태 조회StateSnapshot
checkpointer.delete_thread(thread_id)스레드 전체 삭제None

6. 핵심 정리

긴 대화에서 Context Window 초과 문제를 해결하는 핵심은 메시지 관리 전략의 선택이다.

  • 메시지 트림(trimMessages): 원본 데이터를 보존하면서 LLM에 전달하는 메시지만 필터링한다. max_tokens 값으로 범위를 조절하며, 데이터 보존이 중요한 상황에 적합하다.
  • 메시지 삭제(removeMessage): 오래된 메시지를 State에서 영구적으로 제거한다. 메모리 절약이 필요하지만, 삭제된 데이터는 복구할 수 없다.
  • MongoDB 영구 저장: InMemorySaverMongoDBSaver로 교체하기만 하면 프로덕션 환경에서 대화 상태를 영구 보존할 수 있다. 체크포인터 추상화 덕분에 나머지 코드는 변경할 필요가 없다.
  • 메모리 API: get_tuple(), get_state(), delete_thread()로 저장된 대화 상태를 조회하고 관리할 수 있다.

트림과 삭제 중 어느 전략을 선택할지는 데이터 보존 필요성메모리 효율성 사이의 트레이드오프로 결정된다.