인공지능 개발자 수다(유튜브 바로가기) 자세히보기

자연어처리/Langchain

[langchain] 채팅 히스토리와 메모리 (History, Memory)

Suda_777 2025. 2. 5. 03:50
반응형

 

 

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"}
)

 

 

 

 

반응형