🔥 고급2026-06-297~9분
에이전트 장기 메모리 설계: 에피소딕·시맨틱 분리와 망각 전략
컨텍스트 윈도우에 모든 히스토리를 넣으면 비용이 선형 증가하고 집중도가 떨어진다. 에피소딕 메모리와 시맨틱 메모리를 분리하고 TTL 기반 망각을 도입하면 토큰 소비를 70% 이상 줄이면서 응답 품질을 유지할 수 있다.
long-term-memoryagent-designrag
메모리 계층 설계: 두 가지 저장소
장기 메모리를 단일 벡터 DB에 모두 저장하는 방식은 간단하지만 두 가지 문제를 일으킨다. 첫째, 검색 시 에피소딕("어제 사용자가 요청한 것")과 시맨틱("사용자의 선호도 일반화") 정보가 혼재되어 관련도 계산이 흐려진다. 둘째, 오래된 에피소드가 최신 컨텍스트를 오염시킨다.
권장 계층 구조:
- 에피소딕 메모리: 개별 대화 세션의 요약본. TTL 7~30일. PostgreSQL + pgvector 또는 Redis Vector로 관리.
- 시맨틱 메모리: 반복 패턴에서 추출한 사실·선호도. TTL 무제한 또는 버전 관리.
user_id + fact_key기준 upsert. - 워킹 메모리: 현재 컨텍스트 윈도우. 최근 N턴 + 검색된 메모리 스니펫만 포함.
망각 전략: TTL과 중요도 기반 압축
모든 메모리를 영구 보존하면 검색 노이즈가 증가하고 개인정보 리스크도 커진다. 다음 세 가지 망각 기법을 조합한다.
- 시간 기반 TTL: 에피소딕 메모리는 30일 후 자동 만료. 갱신 없이 재접근되지 않은 메모리 우선 삭제.
- 중요도 스코어링: 세션 종료 시 Claude에게 해당 대화의 핵심 사실을 1~5점으로 평가 요청. 3점 미만은 에피소딕에만 저장, 4점 이상은 시맨틱으로 승격.
- 압축 배치: 주 1회 오래된 에피소딕 메모리를 배치로 요약·통합 후 원본 삭제. Batch API와 결합하면 비용 최소화.
import anthropic
from datetime import datetime, timedelta
client = anthropic.Anthropic()
def extract_and_score_memory(conversation_turns: list[dict]) -> dict:
"""세션 종료 시 메모리 추출 및 중요도 평가"""
history_text = "\n".join(
f"{t['role']}: {t['content']}" for t in conversation_turns
)
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=512,
system=(
"당신은 메모리 관리자입니다. 대화에서 장기 보존 가치가 있는 "
"사실·선호도·결정사항을 JSON으로 추출하세요.\n"
"형식: {\"facts\": [{\"content\": str, \"score\": 1-5, \"type\": \"episodic|semantic\"}]}"
),
messages=[{"role": "user", "content": f"대화:\n{history_text}"}],
)
raw = response.content[0].text
import json, re
match = re.search(r"\{.*\}", raw, re.DOTALL)
return json.loads(match.group()) if match else {"facts": []}
def store_memories(user_id: str, facts: list[dict], db_client) -> None:
now = datetime.utcnow()
for fact in facts:
ttl = None if fact["type"] == "semantic" else now + timedelta(days=30)
db_client.upsert(
collection="memories",
data={
"user_id": user_id,
"content": fact["content"],
"score": fact["score"],
"type": fact["type"],
"expires_at": ttl,
"created_at": now,
}
)
# 실제 세션 종료 후 호출 예시
# turns = [{"role": "user", "content": "..."}, ...]
# result = extract_and_score_memory(turns)
# store_memories("user-123", result["facts"], db)
운영 체크리스트
- [ ] 검색 시 필터 우선: 벡터 유사도만으로 검색하지 말고
user_id + type + expires_at > now메타 필터 선적용 - [ ] 토큰 예산 설정: 워킹 메모리에 삽입할 메모리 스니펫은 최대 500토큰으로 제한
- [ ] 점수 임계값 튜닝: score ≥ 4를 시맨틱 승격 기준으로 시작, A/B 평가 후 조정
- [ ] GDPR 삭제 API:
user_id기준 전체 메모리 즉시 삭제 엔드포인트 필수 구현 - [ ] 압축 배치 모니터링: 주간 압축 후 검색 정밀도(Precision@5) 비교로 품질 회귀 감지
- [ ] 실패 모드: 메모리 DB 장애 시 워킹 메모리만으로 graceful degradation 처리