LLM 대화에서 메모리가 필요한 이유
LLM은 기본적으로 무상태(stateless)입니다. 매 API 호출마다 이전 대화를 기억하지 못하므로, 개발자가 직접 대화 이력을 관리하고 프롬프트에 포함해야 합니다.
메모리 유형 비교
| 메모리 타입 | 방식 | 토큰 사용 | 적합한 경우 |
|---|---|---|---|
| ConversationBufferMemory | 전체 대화 저장 | 많음 | 짧은 대화 |
| ConversationBufferWindowMemory | 최근 K턴만 | 중간 | 일반 챗봇 |
| ConversationSummaryMemory | 대화 요약 | 적음 | 긴 대화 |
| ConversationSummaryBufferMemory | 요약 + 최근 | 중간 | 균형잡힌 선택 |
| ConversationTokenBufferMemory | 토큰 수 기준 | 제어 가능 | 비용 최적화 |
ConversationBufferWindowMemory
from langchain.memory import ConversationBufferWindowMemory
from langchain.chains import ConversationChain
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4", temperature=0.7)
memory = ConversationBufferWindowMemory(
k=10, return_messages=True, memory_key="history"
)
chain = ConversationChain(llm=llm, memory=memory, verbose=True)
response1 = chain.predict(input="안녕하세요, 저는 김영수입니다.")
response2 = chain.predict(input="제가 방금 이름을 알려드렸는데, 기억하시나요?")
ConversationSummaryBufferMemory
from langchain.memory import ConversationSummaryBufferMemory
memory = ConversationSummaryBufferMemory(
llm=llm, max_token_limit=1000, return_messages=True, memory_key="history"
)
memory.save_context(
{"input": "Python으로 웹 크롤러를 만들고 싶어요"},
{"output": "BeautifulSoup이나 Scrapy를 추천드립니다..."}
)
print(memory.load_memory_variables({}))
Redis 기반 영속 메모리
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain.memory import ConversationSummaryBufferMemory
def get_memory(session_id: str):
message_history = RedisChatMessageHistory(
session_id=session_id,
url="redis://localhost:6379/0",
ttl=86400
)
memory = ConversationSummaryBufferMemory(
llm=ChatOpenAI(model="gpt-3.5-turbo"),
max_token_limit=1000,
chat_memory=message_history,
return_messages=True,
memory_key="history"
)
return memory
memory = get_memory("user-42-session-abc")
chain = ConversationChain(llm=llm, memory=memory)
LCEL 방식의 메모리 관리
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 도움이 되는 AI 어시스턴트입니다."),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
chain = prompt | llm
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history=lambda sid: RedisChatMessageHistory(
session_id=sid, url="redis://localhost:6379/0"
),
input_messages_key="input",
history_messages_key="history"
)
response = chain_with_history.invoke(
{"input": "안녕하세요!"},
config={"configurable": {"session_id": "user-42"}}
)
토큰 최적화 전략
- 대화 요약에는 gpt-3.5-turbo를, 실제 응답에는 gpt-4를 사용하면 비용을 줄일 수 있습니다.
- max_token_limit은 전체 컨텍스트 윈도우의 30-40% 이하로 설정하세요.
- 사용자별 세션에 TTL을 설정하여 오래된 대화를 자동으로 정리하세요.
- Entity Memory를 결합하면, 사용자 이름/선호 등 핵심 정보를 별도로 추적할 수 있습니다.
- 프로덕션에서는 ConversationSummaryBufferMemory가 가장 균형 잡힌 선택입니다.
댓글 0