메모리 개요
메모리 개요 (Memory overview)
메모리는 이전 상호작용에 대한 정보를 기억하는 시스템이에요. AI 에이전트에게 메모리는 이전 상호작용을 기억하고, 피드백에서 배우며, 사용자 선호도에 적응할 수 있게 해주기 때문에 매우 중요해요. 에이전트가 수많은 사용자 상호작용과 함께 더 복잡한 작업을 처리하게 되면서, 이 기능은 효율성과 사용자 만족 모두에 필수적이게 되었어요.
출처: 문서
본문
이 개념 가이드는 회상 범위(recall scope)에 따라 두 가지 유형의 메모리를 다룹니다:
- 단기 메모리, 즉 스레드(thread) 범위 메모리는 세션 내에서 메시지 기록을 유지하며 진행 중인 대화를 추적합니다. LangGraph는 에이전트의 state의 일부로 단기 메모리를 관리합니다. state는 체크포인터(checkpointer)를 사용해 데이터베이스에 저장되므로 언제든지 스레드를 재개할 수 있어요. 단기 메모리는 그래프가 호출되거나 한 단계가 완료될 때 업데이트되며, 각 단계의 시작 시 state를 읽습니다.
- 장기 메모리는 세션을 넘어 사용자 특정 또는 애플리케이션 수준의 데이터를 저장하며 대화 스레드 전체에 걸쳐 공유됩니다. 언제든지, 어느 스레드에서든 회상할 수 있어요. 메모리는 단일 스레드 ID뿐 아니라 임의의 커스텀 네임스페이스 범위로 지정됩니다. LangGraph는 스토어 (참고 문서)를 제공해 장기 메모리를 저장하고 회상할 수 있게 합니다.

단기 메모리 (Short-term memory)
단기 메모리는 애플리케이션이 단일 스레드 또는 대화 내에서 이전 상호작용을 기억할 수 있게 해줍니다. 스레드는 이메일이 한 대화에서 메시지를 묶는 것처럼 세션의 여러 상호작용을 구성합니다.
LangGraph는 단기 메모리를 에이전트의 state의 일부로 관리하고, 스레드 범위의 체크포인트를 통해 저장합니다. 이 state에는 일반적으로 대화 기록과 함께 업로드된 파일, 검색된 문서, 생성된 산출물 같은 다른 상태 데이터도 포함될 수 있어요. 이런 것들을 그래프의 state에 저장함으로써, 봇은 서로 다른 스레드 간의 분리를 유지하면서 주어진 대화의 전체 컨텍스트에 접근할 수 있습니다.
단기 메모리 관리
대화 기록은 단기 메모리의 가장 흔한 형태이며, 긴 대화는 오늘날의 LLM에게 도전 과제가 됩니다. 전체 기록이 LLM의 컨텍스트 윈도우에 들어가지 못하면 복구할 수 없는 오류가 발생할 수 있어요. LLM이 전체 컨텍스트 길이를 지원하더라도, 대부분의 LLM은 긴 컨텍스트에서 여전히 성능이 낮습니다. 오래되었거나 주제와 무관한 내용에 "주의가 분산"되면서 응답 시간은 느려지고 비용은 높아지는 것입니다.
채팅 모델은 개발자가 제공한 지침(시스템 메시지)과 사용자 입력(인간 메시지)을 포함하는 메시지를 사용해 컨텍스트를 받아들입니다. 채팅 애플리케이션에서 메시지는 인간 입력과 모델 응답이 번갈아 가며 나타나므로, 메시지 목록은 시간이 지남에 따라 점점 길어집니다. 컨텍스트 윈도우는 한정적이고 토큰이 많은 메시지 목록은 비용이 들 수 있기 때문에, 많은 애플리케이션이 오래된 정보를 수동으로 제거하거나 잊는 기법을 활용하면 도움이 됩니다.

메시지 관리의 흔한 기법에 대한 자세한 내용은 메모리 추가 및 관리 가이드를 참고하세요.
장기 메모리 (Long-term memory)
LangGraph의 장기 메모리는 시스템이 서로 다른 대화나 세션에 걸쳐 정보를 유지할 수 있게 합니다. 스레드 범위인 단기 메모리와 달리, 장기 메모리는 커스텀 "네임스페이스" 안에 저장됩니다.
장기 메모리는 만능 해결책이 없는 복잡한 과제입니다. 하지만 다음 질문들은 여러 기법을 탐색하는 데 도움이 되는 프레임워크를 제공합니다:
- 메모리의 유형은 무엇인가? 인간은 사실을 기억하기 위해 (의미 기억), 경험을 위해 (일화 기억), 규칙을 위해 (절차 기억) 메모리를 사용합니다. AI 에이전트도 같은 방식으로 메모리를 사용할 수 있어요. 예를 들어 AI 에이전트는 작업을 완수하기 위해 사용자에 대한 특정 사실을 기억하는 데 메모리를 사용할 수 있습니다.
- 메모리는 언제 업데이트하고 싶은가? 메모리는 에이전트의 애플리케이션 로직의 일부로 업데이트될 수 있습니다 (예: "핫 패스(hot path)에서"). 이 경우 에이전트는 보통 사용자에게 응답하기 전에 사실을 기억하기로 결정합니다. 또는 메모리를 배경 작업(백그라운드/비동기로 실행되며 메모리를 생성하는 로직)으로 업데이트할 수 있어요. 이 두 접근 방식 사이의 트레이드오프는 아래 섹션에서 설명합니다.
각 애플리케이션은 다양한 유형의 메모리를 요구합니다. 완벽한 비유는 아니지만, 인간의 메모리 유형을 살펴보면 통찰을 얻을 수 있어요. 일부 연구(예: CoALA 논문)는 이러한 인간 메모리 유형을 AI 에이전트에 사용되는 것에 매핑하기도 했습니다.
| 메모리 유형 | 저장 내용 | 인간의 예 | 에이전트의 예 |
|---|---|---|---|
| 의미(Semantic) | 사실 | 학교에서 배운 것들 | 사용자에 대한 사실 |
| 일화(Episodic) | 경험 | 내가 한 일들 | 과거 에이전트 행동 |
| 절차(Procedural) | 지침 | 본능 또는 운동 기술 | 에이전트 시스템 프롬프트 |
의미 메모리 (Semantic memory)
의미 메모리는 인간과 AI 에이전트 모두에서 특정 사실과 개념의 보존을 포함합니다. 인간에게는 학교에서 배운 정보와 개념 및 그 관계에 대한 이해가 포함될 수 있어요. AI 에이전트에게 의미 메모리는 과거 상호작용에서 배운 사실이나 개념을 기억해 애플리케이션을 개인화하는 데 자주 사용됩니다.
의미 메모리는 여러 방식으로 관리할 수 있습니다:
프로필 (Profile)
메모리는 사용자, 조직 또는 다른 개체(에이전트 자신 포함)에 대한 잘 정의된 특정 정보의 단일하고 지속적으로 업데이트되는 "프로필"일 수 있습니다. 프로필은 일반적으로 여러분이 도메인을 나타내기 위해 선택한 다양한 키-값 쌍이 있는 JSON 문서입니다.
프로필을 기억할 때는 매번 프로필을 업데이트하고 싶을 것입니다. 결과적으로 이전 프로필을 전달하고 모델이 새 프로필을 생성하도록 요청하거나(또는 이전 프로필에 적용할 일부 JSON 패치) 해야 합니다. 프로필이 커질수록 이 과정은 오류가 나기 쉬워질 수 있으며, 메모리 스키마가 유효하게 유지되도록 프로필을 여러 문서로 나누거나 문서 생성 시 엄격한 디코딩을 사용하는 것이 도움이 될 수 있습니다.

컬렉션 (Collection)
대안으로, 메모리는 시간이 지남에 따라 지속적으로 업데이트되고 확장되는 문서 컬렉션일 수 있습니다. 각 개별 메모리는 범위가 더 좁고 생성하기 쉬울 수 있으며, 이는 시간이 지나도 정보를 잃을 가능성이 더 적다는 뜻입니다. LLM이 새 정보를 위해 새로운 객체를 생성하는 것은 새 정보를 기존 프로필과 조정하는 것보다 쉽습니다. 결과적으로 문서 컬렉션은 이후 회상률(recall)이 더 높아지는 경향이 있습니다.
하지만 이는 메모리 업데이트에 복잡성을 옮깁니다. 모델은 이제 목록의 기존 항목을 삭제하거나 업데이트해야 하며, 이는 까다로울 수 있어요. 또한 일부 모델은 과도하게 삽입(insert)하는 경향이 있고, 다른 모델은 과도하게 업데이트하는 경향이 있을 수 있습니다. 이를 관리하는 한 가지 방법은 Trustcall 패키지를 참고하고, 동작을 조정하는 데 도움이 되도록 (LangSmith 같은 도구로) 평가를 고려하세요.
문서 컬렉션을 사용하는 것은 메모리 검색에도 복잡성을 옮깁니다. Store는 현재 시맨틱 검색과 콘텐츠 필터링을 모두 지원합니다.
마지막으로 메모리 컬렉션을 사용하면 모델에게 포괄적인 컨텍스트를 제공하기 어려울 수 있습니다. 개별 메모리가 특정 스키마를 따를 수 있지만, 이 구조가 메모리 사이의 전체 컨텍스트나 관계를 포착하지 못할 수 있어요. 결과적으로, 이러한 메모리를 사용해 응답을 생성할 때 모델은 통합된 프로필 접근 방식에서 더 쉽게 얻을 수 있는 중요한 컨텍스트 정보가 부족할 수 있습니다.

메모리 관리 방법과 관계없이, 핵심은 에이전트가 의미 메모리를 사용해 응답을 근거화(ground)한다는 것이며, 이는 종종 더 개인화되고 관련성 높은 상호작용으로 이어집니다.
일화 메모리 (Episodic memory)
일화 메모리는 인간과 AI 에이전트 모두에서 과거 사건이나 행동을 회상하는 것을 포함합니다. CoALA 논문은 이를 잘 설명합니다: 사실은 의미 메모리에, 경험은 일화 메모리에 기록할 수 있습니다. AI 에이전트에게 일화 메모리는 에이전트가 작업을 수행하는 방법을 기억하도록 돕는 데 자주 사용됩니다.
실무에서 일화 메모리는 종종 few-shot 예제 프롬프팅으로 구현되며, 에이전트가 과거 시퀀스에서 배워 작업을 올바르게 수행합니다. 때로는 "말하는" 것보다 "보여주는" 것이 더 쉬울 때가 있고, LLM은 예제에서 잘 배웁니다. Few-shot 학습을 사용하면 의도된 동작을 보여주는 입력-출력 예제로 프롬프트를 업데이트하여 LLM을 "프로그래밍"할 수 있어요. few-shot 예제를 생성하는 다양한 모범 사례가 있지만, 종종 과제는 사용자 입력에 기반해 가장 관련성 높은 예제를 선택하는 데 있습니다.
메모리 스토어는 데이터를 few-shot 예제로 저장하는 한 가지 방법일 뿐입니다. 더 많은 개발자 개입을 원하거나 few-shot을 평가 하네스(evaluation harness)와 더 밀접하게 연결하고 싶다면, LangSmith Dataset을 사용해 데이터를 저장하고 사용자 입력에 기반해 가장 관련성 높은 예제를 선택하는 자체 검색 로직을 구현할 수도 있습니다.
도구 호출 성능을 개선하기 위한 few-shot 프롬프팅을 보여주는 블로그 포스트와 few-shot 예제를 사용해 LLM을 인간 선호도에 맞추는 블로그 포스트를 참고하세요.
절차 메모리 (Procedural memory)
절차 메모리는 인간과 AI 에이전트 모두에서 작업을 수행하는 데 사용되는 규칙을 기억하는 것을 포함합니다. 인간에게 절차 메모리는 자전거 타기 같은 작업을 수행하는 방법에 대한 내면화된 지식(기본 운동 기술과 균형)과 같아요. 반면 일화 메모리는 훈련 보조 바퀴 없이 처음으로 자전거를 성공적으로 탔을 때나 경치 좋은 길을 달린 기억에 남는 자전거 여행 같은 특정 경험을 회상하는 것입니다. AI 에이전트에게 절차 메모리는 모델 가중치, 에이전트 코드, 에이전트 프롬프트의 결합으로, 함께 에이전트의 기능을 결정합니다.
실무에서 에이전트가 모델 가중치를 수정하거나 코드를 다시 작성하는 일은 드뭅니다. 하지만 에이전트가 자신의 프롬프트를 수정하는 것은 더 흔합니다.
에이전트의 지침을 다듬는 효과적인 접근 방식 중 하나는 "Reflection" 또는 메타 프롬프팅입니다. 이는 에이전트에게 현재 지침(예: 시스템 프롬프트)을 최근 대화나 명시적인 사용자 피드백과 함께 프롬프트로 주는 것입니다. 그러면 에이전트는 이 입력을 기반으로 자신의 지침을 다듬습니다. 이 방법은 지침을 사전에 지정하기 어려운 작업에 특히 유용하며, 에이전트가 상호작용에서 배우고 적응할 수 있게 해줍니다.
예를 들어, 우리는 Twitter용 고품질 논문 요약을 생성하기 위해 외부 피드백과 프롬프트 재작성을 사용하는 Tweet 생성기를 만들었습니다. 이 경우 특정 요약 프롬프트를 사전에 지정하기는 어려웠지만, 사용자가 생성된 Tweet을 비판하고 요약 과정을 개선하는 피드백을 제공하는 것은 상당히 쉬웠습니다.
아래 의사 코드는 LangGraph 메모리 스토어로 이를 구현하는 방법을 보여줍니다. 스토어를 사용해 프롬프트를 저장하고, update_instructions 노드로 현재 프롬프트를 가져오며(state["messages"]에 담긴 사용자와의 대화 피드백 포함), 프롬프트를 업데이트하고, 새 프롬프트를 다시 스토어에 저장합니다. 그런 다음 call_model 노드는 스토어에서 업데이트된 프롬프트를 가져와 응답을 생성하는 데 사용합니다.
// Node that *uses* the instructions
const callModel = async (state: State, store: BaseStore) => {
const namespace = ["agent_instructions"];
const instructions = await store.get(namespace, "agent_a");
// Application logic
const prompt = promptTemplate.format({
instructions: instructions[0].value.instructions
});
// ...
};
// Node that updates instructions
const updateInstructions = async (state: State, store: BaseStore) => {
const namespace = ["instructions"];
const currentInstructions = await store.search(namespace);
// Memory logic
const prompt = promptTemplate.format({
instructions: currentInstructions[0].value.instructions,
conversation: state.messages
});
const output = await llm.invoke(prompt);
const newInstructions = output.new_instructions;
await store.put(["agent_instructions"], "agent_a", {
instructions: newInstructions
});
// ...
};

메모리 작성 (Writing memories)
에이전트가 메모리를 작성하는 두 가지 주요 방법이 있습니다: "핫 패스에서"와 "백그라운드에서".

핫 패스에서 (In the hot path)
런타임 중 메모리를 생성하는 것은 장점과 과제를 모두 제공합니다. 긍정적인 측면에서, 이 접근 방식은 실시간 업데이트가 가능해 새로운 메모리를 후속 상호작용에 즉시 사용할 수 있습니다. 또한 메모리가 생성되고 저장될 때 사용자에게 알릴 수 있으므로 투명성을 제공합니다.
하지만 이 방법은 과제도 제기합니다. 에이전트가 무엇을 메모리에 저장할지 결정하기 위해 새 도구가 필요하면 복잡성이 증가할 수 있어요. 또한 무엇을 저장할지 추론하는 과정이 에이전트 지연 시간(latency)에 영향을 줄 수 있습니다. 마지막으로 에이전트는 메모리 생성과 다른 책임들 사이에서 멀티태스킹해야 하므로, 생성되는 메모리의 양과 질에 영향을 줄 수 있습니다.
예를 들어 ChatGPT는 save_memories 도구를 사용해 콘텐츠 문자열로 메모리를 upsert하며, 각 사용자 메시지마다 이 도구를 사용할지 여부와 방법을 결정합니다. 참고 구현으로 memory-agent 템플릿을 확인하세요.
백그라운드에서 (In the background)
별도의 백그라운드 작업으로 메모리를 생성하는 것은 여러 장점이 있습니다. 주 애플리케이션의 지연 시간을 제거하고, 애플리케이션 로직을 메모리 관리와 분리하며, 에이전트가 더 집중적으로 작업을 완료할 수 있게 합니다. 이 접근 방식은 또한 중복 작업을 피하기 위해 메모리 생성 시점을 유연하게 조절할 수 있게 합니다.
하지만 이 방법은 자체적인 과제가 있습니다. 메모리 쓰기 빈도를 결정하는 것이 중요해지는데, 업데이트가 드물면 다른 스레드가 새로운 컨텍스트를 받지 못할 수 있어요. 메모리 형성을 언제 트리거할지 결정하는 것도 중요합니다. 일반적인 전략은 일정 시간 후 예약(새 이벤트가 발생하면 재예약), cron 스케줄 사용, 또는 사용자나 애플리케이션 로직에 의한 수동 트리거 허용입니다.
참고 구현으로 memory-service 템플릿을 확인하세요.
메모리 저장 (Memory storage)
LangGraph는 장기 메모리를 스토어에 JSON 문서로 저장합니다. 각 메모리는 커스텀 namespace(폴더와 유사)와 고유한 key(파일 이름과 유사) 아래에 구성됩니다. 네임스페이스에는 종종 사용자 또는 조직 ID나 정보를 쉽게 구성하게 해주는 다른 라벨이 포함됩니다. 이 구조는 메모리의 계층적 구성을 가능하게 합니다. 네임스페이스 간 검색은 콘텐츠 필터를 통해 지원됩니다.
import { InMemoryStore } from "@langchain/langgraph";
const embed = (texts: string[]): number[][] => {
// Replace with an actual embedding function or LangChain embeddings object
return texts.map(() => [1.0, 2.0]);
};
// InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use.
const store = new InMemoryStore({ index: { embed, dims: 2 } });
const userId = "my-user";
const applicationContext = "chitchat";
const namespace = [userId, applicationContext];
await store.put(
namespace,
"a-memory",
{
rules: [
"User likes short, direct language",
"User only speaks English & TypeScript",
],
"my-key": "my-value",
}
);
// get the "memory" by ID
const item = await store.get(namespace, "a-memory");
// search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity
const items = await store.search(
namespace,
{
filter: { "my-key": "my-value" },
query: "language preferences"
}
);
메모리 스토어에 대한 자세한 내용은 영속성(Persistence) 가이드를 참고하세요.
더 알아보기
- 이 문서를 MCP로 연결하면 Claude, VSCode 등에서 실시간 답변을 받을 수 있어요.
- GitHub에서 이 페이지 편집하기 또는 이슈 제출하기.