테마
단기 메모리 관리 전략
긴 대화 처리와 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에서 영구적으로 삭제한다. 트림과 달리 한번 삭제된 메시지는 복구할 수 없다.
트림과 삭제의 비교
| 비교 항목 | trimMessages | removeMessage |
|---|---|---|
| 원본 데이터 | 전체 유지 | 영구 삭제 |
| 적용 시점 | LLM 호출 시 (읽기 단계) | State 갱신 시 (쓰기 단계) |
| 복구 가능성 | 가능 (max_tokens 조절) | 불가능 |
| 메모리 절약 | 저장소 공간 계속 사용 | 삭제된 만큼 절약 |
| 사용 도구 | LangChain trim_messages | LangGraph removeMessage |
동작 예시
| 설정 | 동작 | 결과 |
|---|---|---|
| 최근 1개만 유지 | "내 이름은 바비야" 등 오래된 메시지 삭제 | 이름 기억 못함 |
| 최근 3개 유지 | "내 이름은 바비야" 포함 범위 | 이름 기억함 |
선택 기준
- 트림: 데이터 보존이 중요하거나, 나중에 전체 대화를 참조할 가능성이 있을 때
- 삭제: 메모리 절약이 중요하거나, 오래된 대화를 영구적으로 정리해야 할 때
4. MongoDB 영구 저장
InMemorySaver의 한계
지금까지 사용한 InMemorySaver는 메모리 기반 체크포인터다. 프로세스가 종료되면 저장된 모든 대화 상태가 소실된다. 개발과 테스트에는 편리하지만, 프로덕션 환경에서는 사용할 수 없다.
| 체크포인터 | 저장 위치 | 프로세스 종료 시 | 용도 |
|---|---|---|---|
| InMemorySaver | 메모리 (RAM) | 데이터 소실 | 개발/테스트 |
| MongoDBSaver | MongoDB (디스크) | 데이터 유지 | 프로덕션 |
| PostgresSaver | PostgreSQL (디스크) | 데이터 유지 | 프로덕션 |
| RedisSaver | Redis (메모리+디스크) | 설정에 따라 다름 | 프로덕션 |
아키텍처
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"}}
result = graph.invoke({"messages": [...]}, config=config)핵심 포인트: 체크포인터만 InMemorySaver에서 MongoDBSaver로 교체하면 나머지 코드는 한 줄도 바꿀 필요가 없다. LangGraph의 체크포인터 추상화가 잘 설계되어 있기 때문이다.
5. 메모리 조회/삭제 API
체크포인터에 저장된 대화 상태를 프로그래밍 방식으로 조회하거나 삭제할 수 있다. thread_id를 키로 특정 스레드의 전체 대화 내역을 관리한다.
상태 조회
python
# 방법 1: 체크포인터에서 직접 조회
config = {"configurable": {"thread_id": "conversation-1"}}
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 영구 저장:
InMemorySaver를MongoDBSaver로 교체하기만 하면 프로덕션 환경에서 대화 상태를 영구 보존할 수 있다. 체크포인터 추상화 덕분에 나머지 코드는 변경할 필요가 없다.- 메모리 API:
get_tuple(),get_state(),delete_thread()로 저장된 대화 상태를 조회하고 관리할 수 있다.트림과 삭제 중 어느 전략을 선택할지는 데이터 보존 필요성과 메모리 효율성 사이의 트레이드오프로 결정된다.