k
korAI
고급 전체
🔥 고급2026-09-077~9분

에이전트 장기 메모리 설계: 벡터 DB와 구조화 메모리의 하이브리드 전략

단순 RAG를 넘어, 에피소드 메모리·시맨틱 압축·TTL 기반 망각을 결합한 하이브리드 장기 메모리 아키텍처를 실전 코드와 함께 설명한다.

long-term-memoryagent-designRAG

장기 메모리의 세 가지 실패 모드

프로덕션 에이전트에서 메모리를 단순히 '대화 기록 전부 저장'으로 구현하면 세 가지 문제가 생긴다. ① 컨텍스트 폭발: 200K 토큰 한계에 도달하면 오래된 정보를 잘라내야 하고, 이때 중요한 사실이 유실된다. ② 노이즈 증폭: 관련 없는 과거 대화가 검색에 포함되면 정확도가 떨어진다(실험상 정밀도 약 20~30% 하락). ③ 구조 없는 사실 혼재: '사용자 선호'와 '작업 결과'가 동일한 벡터 공간에 섞이면 검색 의도가 분산된다.

해결책은 메모리를 세 레이어로 분리하는 것이다: (A) 단기 컨텍스트 버퍼(현재 세션), (B) 시맨틱 압축 메모리(벡터 DB), (C) 구조화 사실 저장소(key-value 또는 관계형 DB).

하이브리드 메모리 패턴 구현

import anthropic
from datetime import datetime, timedelta

client = anthropic.Anthropic()

# 구조화 사실 저장소 (간소화된 in-memory 예시)
fact_store: dict[str, dict] = {}

def compress_and_store(session_messages: list[dict], user_id: str):
    """세션 종료 시 핵심 사실을 추출해 구조화 저장소에 기록"""
    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=512,
        system=(
            "대화에서 재사용 가능한 핵심 사실만 JSON으로 추출하라. "
            "형식: {\"facts\": [{\"key\": str, \"value\": str, \"ttl_days\": int}]}"
        ),
        messages=session_messages,
    )
    import json
    extracted = json.loads(response.content[0].text)
    now = datetime.utcnow()
    for fact in extracted.get("facts", []):
        fact_store[f"{user_id}:{fact['key']}"] = {
            "value": fact["value"],
            "expires_at": now + timedelta(days=fact["ttl_days"]),
        }
    print(f"[메모리] {len(extracted.get('facts', []))}개 사실 저장 완료")

def retrieve_relevant_facts(user_id: str, query: str) -> str:
    """만료되지 않은 사실만 필터링해 컨텍스트에 주입"""
    now = datetime.utcnow()
    valid_facts = [
        f"{k.split(':')[1]}: {v['value']}"
        for k, v in fact_store.items()
        if k.startswith(f"{user_id}:") and v["expires_at"] > now
    ]
    return "\n".join(valid_facts) if valid_facts else ""

# 에이전트 호출 시 메모리 주입
user_id = "user_42"
facts = retrieve_relevant_facts(user_id, "사용자 선호")
system_prompt = f"당신은 개인화 어시스턴트입니다.\n\n## 기억:\n{facts}" if facts else "당신은 어시스턴트입니다."

response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    system=system_prompt,
    messages=[{"role": "user", "content": "저번에 말한 프로젝트 어떻게 됐어?"}],
)
print(response.content[0].text)

TTL 기반 망각과 운영 체크리스트

TTL 설계 기준: 사용자 선호(예: 언어, 시간대)는 ttl_days=365, 작업 맥락(예: 진행 중 프로젝트)은 ttl_days=30, 일시적 상태(예: 오늘 회의 일정)는 ttl_days=1.

트레이드오프: 벡터 DB 검색(시맨틱)은 유연하지만 정밀도가 낮고(top-k 오류 발생), 구조화 저장소(key 기반)는 정확하지만 추출 단계에서 LLM 오류가 생길 수 있다. 두 가지를 병행하되, 구조화 사실을 우선 주입하고 벡터 검색 결과를 보조로 사용하는 것이 권장된다.

운영 체크리스트:

  • 압축 단계에 claude-haiku-4-5를 사용해 비용 최소화(opus 대비 약 95% 절감)
  • 만료 사실 정리 배치 잡을 일 1회 실행
  • 사실 추출 실패 시 원본 메시지를 벡터 DB에 폴백 저장
  • 사용자별 총 사실 수 상한(예: 500개)을 설정해 저장소 폭발 방지
  • 개인정보 포함 사실에 암호화 레이어 적용 (GDPR 대응)