메모리·상태 관리

에이전트가 어제 한 말을 기억하지 못하면 그것은 챗봇에 불과하다. , , 로 기억의 층을 나눠야 한다.


왜 메모리가 어려운가

는 비싸고, 무한하지 않다. 모든 대화를 매번 통째로 넣으면 비용과 지연이 함께 폭발한다. 는 컨텍스트 윈도 내부의 일이고, 는 외부 저장소의 일이다. 둘 사이에 다리를 놓는 것이 이 챕터의 주제다.

메모리는 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, 삭제 권한, 감사 로그가 필요하다. 정책은 보안 정책이기도 하다. 의 원문도 같이 마스킹해 두지 않으면 임베딩만 삭제해도 누출 위험이 남는다.


다음 장으로

메모리가 채워질수록 디버깅이 어려워진다. “왜 이 답이 나왔는가” 를 추적하려면 한 사이클을 로 분해해야 한다. 다음 장은 ··다.