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

자연어처리/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"}
    )

     

     

     

     

    반응형