Deep Learning (TF, Keras, PyTorch)/Natural Language Processing

[LangChain] Redis를 사용하여 이전 대화 기억을 가진 LLM 챗봇 만들기 (Adding Message History, Memory by Redis)

Rfriend 2023. 12. 24. 21:44

이번 포스팅에서는 인메모리 키-값 저장소인 Redis 와 LangChain을 사용하여 이전 대화 내용에 대한 기억(memory, messages history)을 사용하여 답변을 할 수 있는 LLM 모델 체인을 만들어보겠습니다. 

 

1. Redis란 무엇인가? (What is Redis?)

2. LangChain에서 Redis를 사용한 메시지 기록 (Use of Redis for Message History in LangChain)

3. LangChain, Redis를 이용하여 기억(Memory)을 가진 LLM 모델 체인 만들기

 

 

1. Redis란 무엇인가? (What is Redis?)

 

Redis는 고성능, 유연성 및 다양한 기능으로 알려진 오픈 소스, 인 메모리 키-값 저장소(in-memory key-value store)입니다. 일반적으로 데이터베이스, 캐시 및 메시지 브로커로 사용됩니다. 여기에서 Redis와 LangChain 애플리케이션에서의 메시지 기록 사용에 대해 간략히 설명하겠습니다. 


(1) 인 메모리 저장 (In-Memory Storage): Redis는 데이터를 메모리에 저장하여 매우 빠른 읽기 및 쓰기 작업을 가능하게 합니다. 이는 속도가 중요한 시나리오에 이상적입니다. 

(2) 키-값 저장소 (Key-Value Store): 키와 그 값이 쌍을 이루는 키-값 저장 모델로 작동합니다. 이 값들은 문자열, 해시, 리스트, 세트 등이 될 수 있습니다. 

(3) 지속성 (Persistence): 메모리 기반임에도 불구하고, Redis는 디스크에 데이터를 지속시킬 수 있는 옵션을 제공하여, 재시작 시 데이터 손실을 방지합니다. 이는 스냅샷과/또는 추가 전용 파일(Append Only Files, AOF)을 사용하여 수행됩니다. 

(4) 확장성 및 고가용성 (Scalability and High Availability): Redis는 마스터-슬레이브 복제를 지원하여 확장성을 높이고 가용성을 증가시킵니다. Redis Sentinel은 모니터링 및 장애 조치를 통해 고가용성을 제공합니다. 

(5) 데이터 구조 (Data Structure): Redis는 문자열, 해시, 리스트, 세트, 정렬된 세트, 비트맵, 하이퍼로그로그, 지오스페이셜 인덱스 등 다양한 데이터 구조를 지원합니다. 

(6) Pub/Sub 기능: 내장된 Pub/Sub(게시/구독) 메시징 패러다임을 지원하여, 메시지 지향 미들웨어에 적합합니다.

 



2. LangChain에서 Redis를 사용한 메시지 기록 (Use of Redis for Message History in LangChain)

위의 Redis란 무엇인가라는 소개글에서 언급한 특성이 LangChain과 언어 모델을 사용하여 애플리케이션을 구축하기 위한 프레임워크에도 그대로 적용됩니다. Redis는 채팅 메시지 기록을 관리하는 데 효과적으로 사용될 수 있습니다. 

(1) 대화 저장 (Storing Conversations): Redis는 사용자와 챗봇 또는 언어 모델 간에 교환된 각 메시지를 추적하여 대화 기록을 저장할 수 있습니다. 이는 지속적인 대화에서 맥락을 유지하는 데 중요합니다. 

(2) 빠른 접근 (Fast Access): 인 메모리 특성 덕분에, Redis는 대화 기록에 대한 신속한 접근을 제공합니다. 이는 응답 시간이 중요한 실시간 애플리케이션에 필수적입니다. 

(3) 확장성 (Scalability): LangChain 애플리케이션이 성장함에 따라, Redis는 증가된 데이터 부하와 더 많은 동시 대화를 수용할 수 있도록 확장할 수 있습니다. 

(4) 지속성 (Persistence): 대화 기록이 항상 장기 저장이 필요한 것은 아니지만, Redis의 지속성 메커니즘은 시스템 재시작이나 실패 시 진행 중인 대화가 손실되지 않도록 보장합니다. 

(5) 간편한 통합 (Easy Integration): Redis는 다양한 프로그래밍 언어와 프레임워크, LangChain을 포함하여 간편한 통합을 제공합니다. 이는 개발자에게 접근하기 쉬운 선택입니다. 

요약하자면, Redis는 채봇과 상호작용에서 맥락과 연속성을 유지하는 데 필수적인 대화 데이터의 신속한 검색 및 조작을 보장함으로써, LangChain 애플리케이션에서 메시지 기록을 저장하고 관리하기 위한 고성능 데이터베이스 솔루션으로서 역할을 합니다.

 

 

대부분의 LLM 애플리케이션은 대화형 인터페이스를 가지고 있습니다. 대화의 필수적인 구성 요소 중 하나는 대화 중에 이전에 도입된 정보를 참조할 수 있는 능력입니다. 기본적으로 대화 시스템은 과거 메시지의 일부 창을 직접 접근할 수 있어야 합니다. 더 복잡한 시스템은 지속적으로 업데이트되는 세계 모델을 가질 필요가 있으며, 이를 통해 엔터티와 그 관계에 대한 정보를 유지하는 등의 작업을 수행할 수 있습니다. 

우리는 과거 상호 작용에 대한 정보를 저장하는 이 능력을 '메모리(Memory)'라고 부릅니다. LangChain은 시스템에 메모리를 추가하기 위한 많은 유틸리티를 제공하며, Redis 가 대화 내역을 기록하는 메모리 (Memory) 역할을 해줄 수 있습니다. 이러한 유틸리티는 단독으로 사용되거나 체인에 원활하게 통합될 수 있습니다. 

메모리 시스템은 두 가지 기본 작업, 즉 읽기와 쓰기(Read and Write)를 지원해야 합니다. 모든 체인이 특정 입력을 기대하는 핵심 실행 논리를 정의한다는 것을 기억하세요. 이러한 입력 중 일부는 사용자로부터 직접 옵니다만, 일부 입력은 메모리에서 올 수  있습니다. 체인은 주어진 실행에서 두 번 메모리 시스템과 상호 작용합니다. 


1. 초기 사용자 입력을 받은 후(AFTER), 핵심 논리를 실행하기 전(BEFORE)에, 체인은 메모리 시스템에서 읽어 사용자 입력을 보완합니다. 


2. 핵심 논리를 실행한 후(AFTER), 답변을 반환하기 전(BEFORE)에, 체인은 현재 실행의 입력 및 출력을 메모리에 기록하여, 미래의 실행에서 참조할 수 있도록 합니다. 

 

LangChain - Redis 를 사용해서 이전 대화 내용을 기억으로 추가하기

 

 

3. LangChain, Redis를 이용하여 기억(Memory)을 가진 LLM 모델 체인 만들기

 

먼저, openai, langchain, redis 모듈이 설치되어 있지 않다면 pip install을 사용해서 설치하도록 합니다. 

 

! pip install openai langchain redis

 

 

다음으로, Redis 데이터베이스를 만들어야 하는데요, 간단한 방법 중의 하나로 UpStash 에서 Redis database API 를 만들어보겠습니다. UpStash 사이트에서 회원가입하고 Create Redis Database 를 선택해서 'memory' 라는 이름을 부여해주면 됩니다. 사용량 만큼 과금하는 서비스인데요, 초반 무료 trial 시도해볼 수 있으므로 학습 용도로 사용해보기에 좋습니다. 

 

(* 참고: Docker로 Redis 를 Local에 설치해서 사용하기 ==> https://hub.docker.com/_/redis )

 

아래 화면 캡쳐에서 하단의 "Connect to your database - redis-cli" 란에서 redis-cli -u 다음 부분부터 있는 URL을 복사해서 가져옵니다. 

 

Serverless Database with Redis API by UpStash

 

 

Redis 를 사용하기 위한 URL을 가져왔습니다. 

 

# Redis URL for access
REDIS_URL = 'redis://default:xxxxx...@apn1-precious-lab-35237.upstash.io:35237'

 

 

이전 대화에 대한 기억(Memory)을 가지고 있지 않은 ChatModel은 이전 대화에 기반한 연속적인 대화가 불가능합니다. 아래의 예처럼 "My name is neo." 라고 이미 말을 했는데, 다음번에 "What's my name?" 이라고 다시 물어보면 ChatModel은 모른다고 말합니다. 

 

import os
from langchain.chat_models import ChatOpenAI

os.environ["OPENAI_API_KEY"] = "sk-xxxxx..."

## ChatModel doesn't have memory! 
model = ChatOpenAI(model_name="gpt-4")
model.invoke("Hi, my name is Lion King.")

# Out[16]:
# AIMessage(content='Hello Lion King! How can I assist you today?')


## -- No memory of previous conversations! --
model.invoke("What's my name?")

# Out[17]:
# AIMessage(content="I'm sorry, 
# but I don't have access to personal information about individuals 
# unless it has been shared with me in the course of our conversation.")

 

 

이제 LangChain과 Redis를 사용해서 ChatModel에게 이전 대화에 대한 기억(Memory)을 추가해보겠습니다. 

 

LangChain의 RunnableWithMessageHistory 클래스는 대화 기록 (Message History)를 추가할 수 있도록 해줍니다. 그리고 Langchain.memory.chat_message_histories 에서 RedisChatMessageHistory 메소드를 가져와서 Redis 에 대화 내역을 쓰고 Redis에서 읽어오기를 하도록 하겠습니다. 

 

아래에 Prompt 에서 MessagePlaceholder(variable_name="history") 의 "history" key에는 과거 대화 기록이 인풋으로 들어가게 됩니다. 

 

ChatModel 은 "gpt-4"로 설정했으며, '|' 으로 Prompt와 ChatModel을 chaining 하여 chain 이라는 이름으로 객체를 만들었습니다. 

 

import os
from langchain.chat_models import ChatOpenAI
from langchain.memory.chat_message_histories import RedisChatMessageHistory
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

os.environ["OPENAI_API_KEY"] = "sk-xxxxx..."

template = ChatPromptTemplate.from_messages(
    [
        ("system", "You're a trustworthy AI assistant. Answer the question below. \
                    If your don't know, just say you don't know it."), 
        MessagesPlaceholder(variable_name="history"),
        ("human", "{question}"),
    ])

model = ChatOpenAI(model_name="gpt-4")
chain = template | model

 

 

 

다음으로, RunnableWithMessageHistory() 메소드에 앞에서 정의한 chain, 그리고 이전 대화를 쓰고 읽을 인메모리 키-값 저장소인 Redis를 사용하기 위해 RedisChatMessageHistory(session_id, url=REDIS_URL) 을 lambda 함수로 정의해주었습니다. 그리고

   - input_messages_key="question",

   - history_messages_key="history"

로 지정해서 사용자의 질문("question")과 이전 대화 기억 ("history") 을 읽어와서 인풋으로 사용할 수 있도록 해주었습니다. 

 

## Chain with History by using Redis
chain_with_history = RunnableWithMessageHistory(
    chain, 
    lambda session_id: RedisChatMessageHistory(session_id, url=REDIS_URL), 
    input_messages_key="question", 
    history_messages_key="history",
)

 

 

 

이제 다시 한번 이름을 알려주고, 다시 한번 이름이 뭔지 물어보겠습니다. 

(session_id 는 사용자별로 혹은 대화 주제별로 unique 한 값으로 아무 값이나 넣어줘도 되며, 이후 동일한 사용자나 동일 대화 주제에 대해 이전 대화이 맥락을 고려한 연속적인 대화를 원할 때 동일한 session_id 를 넣어주면 됩니다.)

 

chain_with_history.invoke(
    {"question": "Hi, my name is Lion King."}, 
    config={"configurable": {"session_id": "123"}},
)

# AIMessage(content='Hello, Lion King! How can I assist you today?')

## This time, ChatModel has a memory of previous conversations!!
chain_with_history.invoke(
    {"question": "What's my name? Do you remember it?"},
    config={"configurable": {"session_id": "123"}}
)

# AIMessage(content='Yes, your name is Lion King.')

 

 

네, 이번엔 잘 기억하고 제대로 대답을 해주네요! 

 

 

UpStash 사이트에 들어가서 "memory" 데이터베이스의 "Data Browser" 탭에 들어가서 보면 이전의 대화 기록 (Message Histories) 을 조회해서 확인해 볼 수 있습니다. (Redis에 이전 대화를 이처럼 쓰고, 읽기를 합니다.)

 

UpStash - Redis - Data Browser

 

 

위에 화면 캡쳐에 가려져서 잘 안보이는데요, 하나씩 풀어서 보면 아래와 같습니다. 

 

Redis - Memory (Message History)

 

 

[ Reference ]

 

1. LangChain Documents - Add message history (memory):
https://python.langchain.com/docs/expression_language/how_to/message_history

2. LangChain Documents - Memory:
https://python.langchain.com/docs/modules/memory/

3. Redis by UpStash - Serverless Database with Redis API:
https://www.upstash.com/

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요.  :-)

 

728x90
반응형