메모리·상태 관리
에이전트가 어제 한 말을 기억하지 못하면 그것은 챗봇에 불과하다. , , 로 기억의 층을 나눠야 한다.
왜 메모리가 어려운가
는 비싸고, 무한하지 않다. 모든 대화를 매번 통째로 넣으면 비용과 지연이 함께 폭발한다. 는 컨텍스트 윈도 내부의 일이고, 는 외부 저장소의 일이다. 둘 사이에 다리를 놓는 것이 이 챕터의 주제다.
메모리는 LLM의 약점이자 에이전트의 무기다.
네 가지 메모리 유형
심리학에서 빌려온 분류가 그대로 통한다. 는 작업 중인 컨텍스트, 는 누적 저장소, 는 시간순 사건 로그, 는 일반화된 지식이다.
각 유형은 저장소도 다르고, 갱신 시점도 다르다.
LangGraph Store — 장기 메모리
는 네임스페이스 + 키 + 값(JSON)으로 구성된 키-값 저장소다. 를 그래프 외부에 두고, 어떤 노드에서든 꺼낸다. 검색도 함께 지원한다.
# Verified against: https://langchain-ai.github.io/langgraph/concepts/persistence/
# Verified at: 2026-06-02
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
ns = ("user-42", "preferences")
store.put(ns, "lang", {"value": "한국어"})
store.put(ns, "tone", {"value": "친근"})
# 단건 조회
item = store.get(ns, "lang")
print(item.value) # {"value": "한국어"}
# 네임스페이스 내 전체 검색
for it in store.search(ns):
print(it.key, it.value)CrewAI Memory
는 단일 Memory 인스턴스 안에 ··엔티티를 통합한다. 가중치(최신성·의미·중요도)로 점수가 결정된다. 임베더 설정이 핵심이다.
# Verified against: https://docs.crewai.com/concepts/memory
# Verified at: 2026-06-02
from crewai import Crew, Agent, Task, Memory
memory = Memory(
recency_weight=0.4,
semantic_weight=0.4,
importance_weight=0.2,
recency_half_life_days=14,
embedder={
"provider": "openai",
"config": {"model_name": "text-embedding-3-small"},
},
)
agent = Agent(role="조사관", goal="자료 수집", backstory="끈질긴 분석가")
task = Task(description="A2A 표준 현황", agent=agent, expected_output="요약")
crew = Crew(agents=[agent], tasks=[task], memory=memory)
crew.kickoff()벡터 스토어 — Chroma
는 을 색인해 의미 기반 을 가능하게 한다. Chroma는 가장 단순한 출발점이다.
# Verified against: https://docs.trychroma.com/docs/overview/getting-started
# Verified at: 2026-06-02
import chromadb
client = chromadb.PersistentClient(path="./.chroma")
collection = client.get_or_create_collection(name="agent_memory")
collection.add(
ids=["m1", "m2"],
documents=[
"사용자는 한국어로 답해주길 선호한다",
"사용자의 시간대는 KST(UTC+9)이다",
],
)
result = collection.query(query_texts=["사용자 언어 설정"], n_results=2)
print(result["documents"])임베딩 모델 — Voyage AI
은 텍스트를 고차원 벡터로 바꾼다. Voyage AI는 Anthropic이 추천하는 임베딩 제공자다. 모델별 차원 수가 다르므로 컬렉션 생성 시 못 박는 것이 안전하다. 품질은 임베딩 모델 선택에 크게 좌우된다.
# Verified against: https://docs.voyageai.com/
# Verified at: 2026-06-02
import voyageai
vo = voyageai.Client() # VOYAGE_API_KEY 환경변수
out = vo.embed(
["사용자는 한국어를 선호", "시간대는 KST"],
model="voyage-3",
input_type="document",
)
print(len(out.embeddings[0])) # 차원 수RAG — 검색 후 주입
의 결과를 그대로 프롬프트에 끼워 넣는 패턴이 다. 가 답을 직접 주지는 않는다. 후보를 추리고 LLM이 합성한다.
순서가 핵심: 질문 → 임베딩 → 유사도 검색 → 상위 K개를 컨텍스트로 → LLM 호출.
컨텍스트 부풀기 — 요약과 프루닝
는 채울수록 비싸진다. 으로 오래된 대화를 한 단락으로 축약하고, 으로 더 이상 쓰이지 않는 항목을 잘라낸다. 두 작업은 비동기 백그라운드 잡으로 돌리는 게 안전하다.
# Verified against: 본 챕터 본문 패턴
# Verified at: 2026-06-02
from anthropic import Anthropic
client = Anthropic()
def summarize(history: list[dict]) -> str:
"""오래된 대화를 한 문단으로 요약."""
prompt = "다음 대화를 4문장으로 요약하라:\n" + str(history)
r = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=400,
messages=[{"role": "user", "content": prompt}],
)
return r.content[0].text
def prune(items: list[dict], keep: int = 50) -> list[dict]:
"""최근 N개만 유지, 나머지는 별도 저장."""
items.sort(key=lambda x: x["ts"], reverse=True)
return items[:keep]에피소드 vs 의미
는 “언제 무엇이 일어났는지”의 로그다. 는 “사용자는 한국어를 선호한다” 같은 일반화된 사실이다. 안에서 에피소드를 누적하다 일정 빈도가 넘으면 의미 메모리로 승격하는 패턴이 흔하다.
승격이 잘 되면 컨텍스트 부풀기가 줄어든다.
검색 정확도와 hybrid
은 의미만으로 부족할 때가 많다. 기반 의미 검색과 키워드 기반 BM25를 합친 hybrid 방식이 흔한 안전판이다. 단독으론 “정확히 그 키워드를 포함” 같은 조건을 놓친다.
정확도가 부족하다고 느낄 때 첫 번째 처방은 hybrid다.
청크 분할 크기, 오버랩, top-K 같은 하이퍼파라미터를 평가 단계에서 같이 본다.
메모리 거버넌스
장기 메모리는 개인정보를 머금는다. 항목에는 TTL, 삭제 권한, 감사 로그가 필요하다. 정책은 보안 정책이기도 하다. 의 원문도 같이 마스킹해 두지 않으면 임베딩만 삭제해도 누출 위험이 남는다.
다음 장으로
메모리가 채워질수록 디버깅이 어려워진다. “왜 이 답이 나왔는가” 를 추적하려면 한 사이클을 로 분해해야 한다. 다음 장은 과 ··다.