LLM(거대 언어 모델) 자체는 '기억'이 없습니다(Stateless). 매번의 API 호출은 완전히 독립적입니다. "안녕"이라고 말한 뒤 "내 이름이 뭐야?"라고 물으면, LLM은 "안녕"이라는 이전 대화를 모르기 때문에 대답할 수 없습니다.
'기억'을 구현한다는 것은 "이전 대화 내용을 현재 프롬프트에 포함시켜서" LLM에게 '문맥(Context)'을 함께 전달하는 것을 의미합니다.
핵심 질문은 "이전 대화 중 어떤 것을 어떻게 프롬프트에 넣을 것인가?"입니다.
가장 고전적이고 기본적인 방법입니다.
Human: 안녕? AI: 안녕하세요! Human: 오늘 날씨 어때? AI: 서울은 맑습니다. Human: 어제 내가 뭐 물어봤는지 기억해? AI: ... (이제 LLM이 '오늘 날씨'를 물어본 것을 보고 대답함)from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
# 메모리 포함 체인 생성
memory = ConversationBufferMemory()
chain = ConversationChain(llm = llm, memory=memory)
# 대화 시작
print(chain.run("안녕? 나는 기영이야.")) # 기영아 안녕
print(chain.run("내 이름이 뭐라고?")) # 너의 이름은 기영이야.
A의 단점을 보완. 전체 대화가 아닌, 최근 k개의 대화만 잘라서 프롬프트에 넣습니다. (예: 최근 5번의 턴)k개 이전의 중요한 정보(예: "내 이름은 OOO이야")를 잊어버립니다.from langchain.memory import ConversationBufferWindowMemory
# 최근 2턴만 기억하는 메모리
memory = ConversationBufferWindowMemory(k=2)
chain = ConversationChain(llm=llm, memory=memory)
# 대화
print(chain.run("나는 기영이야."))
memory.load_memory_variables({})
print(chain.run("내가 좋아하는 색은 파란색이야."))
memory.load_memory_variables({})
print(chain.run("나는 영화보는 것을 좋아해"))
memory.load_memory_variables({})
print(chain.run("지금 배고픈데 뭘 먹을까?"))
memory.load_memory_variables({})
print(chain.run("내가 누구라고?")) # 누군지 기억못함.
memory.load_memory_variables({})
from langchain.memory import ConversationSummaryMemory
# 요약 메모리 생성 (요약용 LLM 필요)
memory = ConversationSummaryMemory(llm = llm)
# 체인 구성
chain = ConversationChain(llm = llm, memory = memory)
# 대화
print(chain.run("오늘은 운동하고, 친구랑 밥도 먹고, 강의도 들었어."))
print(chain.run("내가 오늘 뭐했는지 기억나?"))
print(chain.run("너는 어떻게 지냈어?"))
# 담긴 메모리 확인하기
memory.chat_memory.messages
# 내용 요약
print(memory.buffer)

최근 가장 많이 사용되는 방식이며, RAG(검색 증강 생성)의 원리를 대화 기억에 적용한 것입니다.
k개 대화] + [검색된 관련 과거 대화] + [새 질문]을 넣어 LLM에 전달합니다.from langchain.vectorstores import Chroma
# split_texts, embedding_model 정의
...
# ChromaDB를 만들면서 저장
vectorstore = Chroma.from_texts(split_texts, embedding_model, persist_directory="./chroma_db")
query = "농촌 계몽운동에 대한 내용"
retrieved_docs = vectorstore.similarity_search(query, k=3)
# 결과 출력
print("검색 결과:")
for doc in retrieved_docs:
print(doc.page_content)
print('-'*200)
둘 다 벡터를 저장하고 검색하는 핵심 역할을 하지만, 지향점이 약간 다릅니다.
from langchain.vectorstores import FAISS
# split_docs, embedding_model 정의
...
vectorstore = FAISS.from_documents(split_docs, embedding_model)
vectorstore.save_local("faiss_index") # 'faiss_index' 폴더에 저장됨
# 저장된 벡터DB를 로딩하기
new_vectorstore = FAISS.load_local(
"faiss_index",
embedding_model,
allow_dangerous_deserialization=True
)
new_docs = [
Document(page_content="조선시대의 교육 제도는 성균관 중심이었다.", metadata={"source": "추가"}),
Document(page_content="한국 전통 사회에서 글을 읽는 능력은 권력의 상징이었다.", metadata={"source": "추가"})
]
# 문서 추가
vectorstore.add_documents(new_docs)


MemorySaver의 역할사용자님이 언급하신 from langgraph.checkpoint.memory import MemorySaver는 위 1, 2번과는 약간 다른 차원의 이야기입니다.
MemorySaver는 "기억을 프롬프트로 만드는 방법"이 아니라, "대화의 상태(State)를 어디에 저장(Persistence)할 것인가"에 대한 도구입니다.
MemorySaver (인메모리): 가장 기본. 대화 기록을 그냥 RAM(메모리)에 저장합니다. 챗봇 서버가 재시작되면 모든 대화 기록이 사라집니다. (테스트용)RedisSaver / PostgresSaver 등: 대화 기록을 외부 DB(Redis, Postgres 등)에 저장합니다.LangGraph 같은 에이전트 프레임워크에서 MemorySaver는 단순히 채팅 기록뿐만 아니라, 에이전트의 현재 작업 상태(State) 전체(예: 'A' 작업 완료, 'B' 작업 대기 중)를 저장하는 '체크포인터(Checkpointer)' 역할을 합니다.
MemorySaver (혹은 LangChain의 ChatMessageHistory 인터페이스)를 통해 Redis나 Postgres 같은 외부 DB에 영구 저장합니다.단순한 챗봇은 '최근 대화만 기억(Window)'하는 방식을,
고급 챗봇이나 에이전트는 'RAG(검색)로 장기 기억'을 구현하고,
LangGraph를 쓴다면 MemorySaver를 이용해 이 모든 상태를 'DB에 저장'하는 것이 현재의 표준 방식입니다.