
0. 개요
LangChain에서 대화 히스토리는 Memory 에서 관리한다.
모델이 이전에 어떤 맥락(대화 이력 등)을 가지고 있었는지 유지
이전 대화 내용을 연속적으로 반영하기 위한 구조
관리 방법
- 과거 대화 내용을 요약하거나
- 핵심만 발췌하거나
- 혹은 토큰 제한(token limit)에 맞춰서 관리
1. 채팅 히스토리(History)
채팅 내용을 물리적인 공간에 저장/관리 하는 기능
대화(메시지)들을 실제로 보관하는 역할
in-memory, redis, postgres, file, mongodb 등 다양한 곳에 저장할 수 있음
1.1. 인메모리(in-memory)에 저장
인메모리(in-memory) 대화 기록 저장
ChatMessageHistory 클래스 사용
프로그램이 동작하는 동안만 메시지를 기억하고, 종료 시에는 사라진다.
예시코드
from langchain.memory import ChatMessageHistory
from langchain.schema import HumanMessage, AIMessage
# 1) ChatMessageHistory 인스턴스 생성
chat_history = ChatMessageHistory()
# 2) 사용자와 AI 메시지를 직접 추가
chat_history.add_user_message("안녕하세요? 오늘 날씨 어때요?")
chat_history.add_ai_message("안녕하세요! 오늘은 화창하고 따뜻한 날씨가 예상됩니다.")
# 3) 현재까지의 메시지를 확인 (List[BaseMessage] 형태)
messages = chat_history.messages
for idx, msg in enumerate(messages, 1):
# msg.type은 "human" 또는 "ai", msg.content는 메시지 내용
print(f"메시지 {idx} [{msg.type}]: {msg.content}")
1.2. Redis에 저장
RedisChatMessageHistory 클래스 사용
메시지를 Redis에 저장/조회할 수 있음
세션별 대화를 영구(혹은 반영구) 저장할 때 사용
예시코드
from langchain.memory.chat_message_histories import RedisChatMessageHistory
redis_chat_history = RedisChatMessageHistory(
session_id="unique_session_id", # 세션 구분
url="redis://localhost:6379", # Redis URL
ttl=3600 # 선택: key 만료시간 (초 단위)
)
# 대화 추가
redis_chat_history.add_user_message("Redis에 메시지 저장 테스트")
redis_chat_history.add_ai_message("Redis에 잘 저장되었습니다!")
# 대화 확인
messages = redis_chat_history.messages
for msg in messages:
print(f"{msg.type}: {msg.content}")
1.3. 데이터베이스에 저장
PostgresChatMessageHistory, MongoDBChatMessageHistory, ElasticsearchChatMessageHistory, FileChatMessageHistory 등 매우 다양한 데이터베이스가 있다.
필요할 때 찾아서 쓰도록 하고 생략
2. 대화 이력 관리 (Memory)
저장된 메시지를 어떻게 가공해 LLM의 프롬프트로 넘길지 결정
2.1. ConversationBufferMemory
설명
- 단순히 모든 대화 히스토리를 그대로 저장
- 대화가 길어지면 토큰 수가 증가해 모델 호출이 비싸질 수 있음
기본 사용법
from langchain.memory import ConversationBufferMemory
# 객체 정의
memory = ConversationBufferMemory()
# 메모리 저장
memory.save_context(
inputs={"input": "안녕, GPT야!"},
outputs={"output": "안녕하세요! 무엇을 도와드릴까요?"}
)
# 메모리 불러오기
print(memory.load_memory_variables({}))
history와 연동
- 이 예시에서는 redis를 사용
- session_id를 기준으로 데이터를 조회해 온다. (즉 기존 대화이력을 가져옴)
from langchain.memory import ConversationBufferMemory
from langchain.memory.chat_message_histories import RedisChatMessageHistory
# 1) session_id를 기준으로 메시지를 저장/조회
redis_chat_history = RedisChatMessageHistory(
session_id="my_unique_session_id", # 세션 구분용 ID
url="redis://localhost:6379",
ttl=3600
)
# 2) Memory 객체 생성
memory = ConversationBufferMemory(
chat_memory=redis_chat_history,
return_messages=False # True면 List[BaseMessage], False면 문자열 형태 반환
)
2.2. ConversationBufferWindowMemory
설명
- 최근 k개의 대화만 저장
- 전체 대화 기록을 모두 LLM에 전달하기에 데이터가 클 때 사용
기본 사용법
from langchain.memory import ConversationBufferWindowMemory
# 최근 3개의 메시지만 기억하도록 설정
memory = ConversationBufferWindowMemory(k=3)
# 메모리 저장 (여러 번 호출해서 대화를 쌓아봅니다)
memory.save_context(
inputs={"input": "안녕, GPT야!"},
outputs={"output": "안녕하세요! 무엇을 도와드릴까요?"}
)
# 메모리 불러오기
print(memory.load_memory_variables({}))
history와 연동
- 이 예시에서는 redis를 사용
from langchain.memory import ConversationBufferWindowMemory
from langchain.memory.chat_message_histories import RedisChatMessageHistory
# 1) Redis로 메시지를 저장/조회 (session_id 로 구분)
redis_chat_history = RedisChatMessageHistory(
session_id="my_window_session",
url="redis://localhost:6379",
ttl=3600
)
# 2) ConversationBufferWindowMemory에 Redis 기반 History 결합
memory = ConversationBufferWindowMemory(
k=3,
chat_memory=redis_chat_history,
return_messages=False
)
2.3. ConversationSummaryMemory
설명
- 과거 대화들을 요약해 압축된 형태로 만듦
- llm을 이용해 요약함
기본 사용법 : 생략
history와 연동
- 실제 Redis에는 모든 메시지가 쌓임
- llm으로 요약을 실행함
- 이후 모델에 전달하는 ‘history’는 ConversationSummaryMemory가 요약한 결과만 포함
from langchain.memory import ConversationSummaryMemory
from langchain.memory.chat_message_histories import RedisChatMessageHistory
from langchain.llms import OpenAI
# 1) RedisChatMessageHistory로 메시지 저장
redis_chat_history = RedisChatMessageHistory(
session_id="my_summary_session",
url="redis://localhost:6379",
ttl=3600
)
llm = OpenAI(temperature=0, openai_api_key="YOUR_API_KEY")
# 2) ConversationSummaryMemory를 Redis 기반으로 생성
memory = ConversationSummaryMemory(
llm=llm,
chat_memory=redis_chat_history,
return_messages=False
)
2.4. ConversationKGMemory
설명
- 대화 내의 지식 그래프(Knowledge Graph)를 생성 및 업데이트해 메모리로 활용
- 대화가 길어져도, 핵심 정보(누가 누구인지, 무엇이 어떻게 연결되는지)를
그래프 구조로 쉽게 조회하고 이어서 활용 - llm을 이용해 그래프를 만듦
기본 사용법 : 생략
history와 연동
from langchain.memory import ConversationKGMemory
from langchain.memory.chat_message_histories import RedisChatMessageHistory
from langchain.llms import OpenAI
# 1) Redis에서 메시지를 저장하고 불러오기
redis_chat_history = RedisChatMessageHistory(
session_id="kg_session_example",
url="redis://localhost:6379"
)
# 2) LLM 준비
llm = OpenAI(temperature=0, openai_api_key="YOUR_API_KEY")
# 3) ConversationKGMemory 생성 (redis_chat_history를 사용)
kg_memory = ConversationKGMemory(
llm=llm,
chat_memory=redis_chat_history,
return_messages=False
)
2.5. CombinedMemory
위 여러 종류의 Memory를 결합해 사용
기본 사용법
- ConversationBufferMemory + ConversationSummaryMemory 형태
- 최근 대화는 버퍼로, 오래된 대화는 요약으로 관리
from langchain.memory import (
CombinedMemory,
ConversationBufferMemory,
ConversationSummaryMemory
)
from langchain.llms import OpenAI
# 1) 각각의 Memory 객체 생성
buffer_memory = ConversationBufferMemory(return_messages=False)
summary_memory = ConversationSummaryMemory(
llm=OpenAI(temperature=0, openai_api_key="YOUR_API_KEY"),
return_messages=False
)
# 2) CombinedMemory에 2개의 메모리를 합침
combined = CombinedMemory(
memories=[buffer_memory, summary_memory]
)
# 3) 대화 저장
combined.save_context(
inputs={"input": "안녕, GPT야! 요약도 하고, 버퍼도 쓰고 싶어."},
outputs={"output": "안녕하세요! 어떻게 도와드릴까요?"}
)
# 4) 메모리 불러오기
result = combined.load_memory_variables({})
print(result)
3. 체인(Chain) 에 적용
3.1. ConversationChain 을 이용해 LLM과 연결
ConversationChain() 에 memory에 담아서 사용한다.
predict() 를 하면 자동으로 memory에 input과 output이 저장된다.
실행 순서
- History의 내용은 prompt의 일부로 들어간다.
- prompt의 최종 결과물이 llm의 입력으로 들어간다
프롬프트 정의
from langchain import PromptTemplate
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain.llms import OpenAI
template = """
다음은 지금까지의 대화 이력입니다:
{history}
사용자가 한 말:
{input}
이에 대한 답변을 부드럽고 친절하게 작성해주세요.
"""
prompt = PromptTemplate(
input_variables=["history", "input"], # template에서 사용되는 변수
template=template
)
history + momory + LLM 연결
memory = ConversationBufferMemory()
# 3) ConversationChain 생성 시 prompt 전달
conversation = ConversationChain(
llm=OpenAI(temperature=0, openai_api_key="YOUR_API_KEY"),
memory=memory,
prompt=prompt,
verbose=True
)
# 대화
response1 = conversation.predict(input="안녕, GPT야! 오늘 기분이 어때?")
print(response1)
invoke() 를 사용해서 실행해도 괜찮다.
3.2. ConversationalRetrievalChain을 이용해 LLM과 연결
ConversationalRetrievalChain() 클래스를 이용하면 retriever도 추가할 수 있다.
이번 포스팅에선 retriever를 생성하는 것은 생략하겠다.
conversational_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=retriever,
memory=memory,
# condense_question_prompt 등 커스텀 프롬프트 지정 가능
verbose=True
)
3.3. LCEL을 활용한 | 연산자 방식 체인(Chain)
- ConversationBufferMemory 같은 메모리 클래스는 내부적으로 load_memory_variables() 메서드를 호출하면,
자동으로 {"history": "...대화 이력..."} 형태로 반환함 - 그래서 프롬프트에서 history 값을 키로 사용하면 됨
- invoke 할 때에는 prompt에 맞춰 input 키값을 넣어줌
from langchain.memory import ConversationBufferMemory
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
memory = ConversationBufferMemory()
llm = ChatOpenAI(temperature=0, openai_api_key="YOUR_API_KEY")
prompt = PromptTemplate.from_template("{history}\n사용자: {input}\nAI:")
# LCEL을 활용한 체인 구성
conversation_chain = memory | prompt | llm
# 체인 실행
response = conversation_chain.invoke({"input": "안녕, GPT야! 오늘 기분이 어때?"})
print(response)
4. 채팅 히스토리 백터 검색
설명
- VectorStoreRetrieverMemory 클래스 이용
- 채팅 히스토리에서 질문과 관련된 내용만 추출함
- 이번 에제에서는 redis vectorstore를 사용했다
- 채팅 히스토리는 redis로 들어감
- index_name으로 chat 구분
- redis 데이터 저장 시간은 expire() 메서드에 index이름을 직접 넣어줘서 설정한다.
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Redis as RedisVectorStore
from langchain.memory import VectorStoreRetrieverMemory
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
import redis
# 1. 임베딩 및 Redis VectorStore 설정
embeddings = OpenAIEmbeddings(openai_api_key="YOUR_OPENAI_API_KEY")
redis_vectorstore = RedisVectorStore(
redis_url="redis://localhost:6379",
index_name="my_vector_index", # 미리 구성한 인덱스 이름
embedding=embeddings
)
# 1-1. Redis 클라이언트로 TTL(예: 3600초 = 1시간) 설정
# "my_vector_index"에 저장된 모든 데이터가 1시간 후 자동 삭제되도록 expire 명령을 내립니다.
redis_client = redis.Redis(host="localhost", port=6379, decode_responses=True)
redis_client.expire("my_vector_index", 3600) # TTL을 3600초로 설정
# 2. Retriever 생성 및 VectorStoreRetrieverMemory 생성
retriever = redis_vectorstore.as_retriever(search_kwargs={"k": 1})
vector_memory = VectorStoreRetrieverMemory(
retriever=retriever,
memory_key="relevant_history" # 프롬프트에 삽입될 변수명 (기본값은 "history"입니다)
)
# 3. 프롬프트 템플릿과 LLM 설정
prompt = PromptTemplate.from_template(
"""아래는 현재 질문과 관련된 과거 대화 내용입니다:
{relevant_history}
사용자: {input}
AI:"""
)
llm = ChatOpenAI(temperature=0, openai_api_key="YOUR_OPENAI_API_KEY")
# 4. LCEL 방식으로 체인 구성
# VectorStoreRetrieverMemory의 출력은 {relevant_history}로, 사용자의 입력은 {input}으로 전달됩니다.
chain = vector_memory | prompt | llm
# 5. 체인 실행
# 새로운 질문이 들어오면 vector_memory가 내부 벡터 스토어에서 관련 대화 이력만 검색해 {relevant_history}에 채워주고,
# 프롬프트 템플릿과 결합되어 LLM에 전달됩니다.
input = "주말에 갈만한 곳 다시 추천해줘."
response = chain.invoke({"input": input})
print("AI 답변:", response)
신규 질문, 대답 벡터스토어에 저장
vector_memory.save_context(
inputs={"human": input},
outputs={"ai":response"}
)
'자연어처리 > Langchain' 카테고리의 다른 글
[Langchain] 임베딩(Embedding)과 유사도 검색 방법 for Retriever (0) | 2025.03.06 |
---|---|
[Langchain] Retriever 사용하기 (3) | 2025.03.05 |
[langchain] Message 다루기 (9) | 2024.10.04 |
[langchain] LangChain Expression Language(LCEL) (1) | 2024.09.30 |
[Langchain] Langchain v0.3 패치노트 (4) | 2024.09.28 |