LangChain의 메모리 관리

LangChain의 메모리 관리

랭체인에는 5가지 정도 종류의 메모리가 있는데 각자 저장방식도 다르고, 각자만의 장단점이 있다.

챗봇에 메모리를 추가하지 않으면 챗봇은 아무것도 기억할 수 잆다.

오픈 AI에서 제공하는 기본 API는 랭체인 없이 사용 사용할 수 있는데, 메모리를 지원하지 않는다 (stateless)

그렇기에, 오늘은 랭체인에서 제공하는 메모리 사용법에 대해 적어보고자 한다.

GPT는 어떻게 메모리 관리를 하는가

세션 메모리와 단기 컨텍스트 관리

  • GPT는 단기 컨텍스트만 유지. 이전 대화의 일부가 필요할 때 최근 메시지를 기반으로 적응하지만, 세션이 종료되면 맥락이 초기화 됨.

캐시와 세션 관리

  • Redis 또는 Memcached와 같은 인메모리 데이터베이스를 사용해, 실시간 대화 상태를 빠르게 유지하고 관리하는 것으로 보임.
  • 이러한 캐시는 짧은 시간동안만 상태를 유지하여 빠른 응답을 지원한다.
  • 대화가 끝나면 캐시된 데이터는 자동으로 소멸한다.

장기 데이터 저장소 (비활성화된 기능)

  • 만약 장기적 사용자 메모리 기능이 활성화된다면, 이를 위헤 데이터베이스(PostgreSQL, MongoDB)나 클라우드 오브젝트 스토리지 (AWS S3 등)를 사용할것으로 추정한다.

ChatGPT는 주로 Transformer 모델, 토큰화 시스템, 그리고 Redis와 같은 캐시를 활용해 실시간 대화의 상태를 유지하는것으로 보인다.
장기적인 메모리 기능은 아직 실험 단계에 있으며, 현재는 세션 단위의 단기 메모리만 지원한다.
OpenAI는 내부 시스템 구성에 대한 구체적인 기술 스택을 직접 공개하지 않고 있다.

LangChain의 메모리 관리

LangChain에서 크게 5가지 정도의 메모리를 사용할 수 있다.

  • ConversationBufferMemory
  • ConversationBufferWindow
  • ConversationSummaryMemory
  • ConversationSummaryBufferMemory
  • ConversationKGMemory
    Open AI에서 제공하는 기본 API는 LangChain 없이 사용할 수 있는데, 메모리를 지원하지 않는다 (stateless)

Conversational Buffer 메모리

  • 단순히 이전 대화 내용 전체를 저장하는 메모리
  • 단점 : 대화 내용이 길어질수록 메모리도 계속 커지니까 비효율적이다.
  • 모델 자체에는 메모리가 없다, 그래서 우리가 모델에게 요청을 보낼때, 이전 대화 기록 전체를 같이 보내야한다.
  • 유저와 대화가 길어질수록, 모델에게 매번 보내야 될 대화 기록이 길어진다. → 비효율적
  • 예시
1
2
3
4
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()
memory.save_context({"input": "Hi"}, {"output": "How are you?"})
memory.load_memory_variables({})
1
{'history': [HumanMessage(content='Hi!'), AIMessage(content='How are you?')]}
  • text completion 할 때 유용하다. (에측을 해야할 때, 텍스트를 자동완성하고 싶을때)
  • 그러나 만약 chat model과 작업을 하면 AI 메시지와 Human 메시지가 다 필요하다.
  • 메모리 종류와 무관하게 API는 다 똑같다.
    • 모든 메모리는 save_context, load_memory_variables라는 함수를 가지고 있다.
  • 이 메모리는 대화 내용 전체를 저장하는 메모리이다.
    • 대화가 길어질수록 메모리에 수많은 내용이 계속 쌓이게 되어 비효율적이다.

ConversationBufferWindow (대화 버퍼 윈도우)

대화의 특정 부분(가장 최근부분)만을 저장하는 메모리.

  • 예를들어 최근 5번째까지의 메시지를 저장한다고 했을 때, 6번째 메시지가 추가 됐을때 가장 오래된 메시지는 버려지는 방식.
  • 저장 범위는 직접 설정 가능
  • 메모리를 특정 크기로 유지할 수 있다는 것이 이 메모리의 큰 장점.
  • 단점 : 챗봇이 전체 대화가 아니라 최근 대화에만 집중.
  • 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
from langchain.memory import ConversationBufferWindowMemory
memory = ConversationBufferWindowMemory(
return_messages=True,
k=4
)
def add_message(input, output):
memory.save_context({"input": input}, {"output": output})
add_message('1', '1')
add_message('2', '2')
add_message('3', '3')
add_message('4', '4')

print(memory.load_memory_variables({}))
  • 최근에 일어난 대화에만 집중한다.

output

1
2
3
4
5
6
7
8
{'history': [HumanMessage(content='1'),
AIMessage(content='1'),
HumanMessage(content='2'),
AIMessage(content='2'),
HumanMessage(content='3'),
AIMessage(content='3'),
HumanMessage(content='4'),
AIMessage(content='4')]}

ConversationSummaryMemory

  • ConversationSummaryMemory는 llm을 사용한다.

  • ConversationSummaryMemory는 message를 그대로 저장하는 것이 아니라, conversation의 요약을 자체적으로 해준다.

  • 초반에는 이전의 메모리들보다 더 많은 토큰과 저장공간을 차지하게 된다.

  • 예시

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from langchain.memory import ConversationSummaryMemory
    from langchain.chat_models import ChatOpenAI
    llm = Chat OpenAI(temperature=0.1)
    memory = ConversationSummaryMemory(llm=llm)
    def add_memory(input, output):
    memory.save_context({"input": input}, {"output": output})
    def get_history():
    return memory.load_memory_variables({})
    add_message("Hi I'm hamin, I'm in Brooklyn.", "Wow that is so cool!")
    get_history()

ConversationKnowledge Graph Memory

  • LLM을 사용하는 memory class

  • 대화 중의 엔티티의 knowledge graph를 만든다.

  • 가장 중요한 것들만 뽑아내는 요약본 같은 것이다.

  • 요약을 하지만 대화에서 entity를 뽑아내는 것이다.

    • Entity를 뽑아낸다는 것은 사람, 장소, 날짜, 사건 등과 같은 의미 있는 개체를 추출하고 Knowledge graph를 구성한다는 것이다.
  • 용도

    • 맥락 유지
      • “그는 지금도 테슬라를 운영하고 있나요” → 그를 일론 머스크로 연결
    • 정보 간 탐색
      • “스페이스X와 테슬라 사이에는 어떤 관계가 있나요”
    • 대화에 따른 실시간 지식 업데이트
      • “테슬라는 최근 사이버트럭을 출시했어”
  • 예시

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from langchain.memory import ConversationKGMemory
    from langchain.chat_models import ChatOpenAI
    llm = Chat OpenAI(temperature=0.1)
    memory = ConversationKGMemory(
    llm=llm,
    return_messages=True,
    )
    def add_memory(input, output):
    memory.save_context({"input": input}, {"output": output})
    add_message("Hi I'm hamin, I'm in Brooklyn.", "Wow that is so cool!")
    memory.load_memory_variables({"input": "who is hamin"})
  • 단순 대화 이상의 구조화된 지식을 구성하고 더 나은 응답을 제공

  • 맥락 유지와 다중 개체간의 관계 탐색에 유용

LLM chain

off-the-shelf(일반적인 목적을 가진 chain을 의미) chain으로 빠르게 시작할 수 있게해서 좋지만, 프레임워크를 다루느라 머리 싸매거나 off-the-shelf chain을 커스텀하기보다 직접 만들고 싶을 때, langchain expression 언어를 활용해서 어플리케이션을 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from langchain.memory import ConversationSummaryBufferMemory
from langchain.chat_models import ChatOpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
llm = ChatOpenAI(temperature=0.1)
memory = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=120,
memory_key="chat_history"
)
template = """
You are a helpful AI talking to a human.
{chat_history}
Human:{question}
You:
"""
chain = LLMChain(
llm=llm,
memory=memory,
prompt=PromptTemplate.from_template(template),
verbose=True,
)
chain.predict(question="My name is hamin")
memory.load_memory_variables({})
chain.predict(question="I live in brooklyn")
chain.predict(question="What is my name?")
  • interaction 토큰 수가 120개보다 많으면 가장 오래된 interaction을 요약해줌
    • 최신 내용을 그대로 유지하고 대화 기록을 요약하기 시작함.
  • verbose를 이용하여 프롬프트 디버깅이 가능하다.

Chat based Memory

memory 클래스는 memory를 두가지 방식으로 출력할 수 있다.

  • 문자열 형태, message 형태
  • 대화기반의 채팅으로 바꾸고 싶다면 return_message=True를 넣어줘야한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     from langchain.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder
    memory = ConversationSummaryBufferMemory(
    llm=llm,
    max_token_limit=120,
    memory_key="chat_history",
    return_messages=True
    )
    prompt = ChatPromptTempate.from_messsages([
    ("system", "You are a helpful AI talking to a human"),
    MessagePlaceholder(variable_name="chat_history"),
    ("human", "{question}")
    ])
  • 이렇게 넣어주면 문자열기반 템플릿 대신 ChatPromptTemplate을 불러올것이다.
  • ConversationBufferMemory로부터 요약본을 받아올 때, 시스템 메시지도 추가된다.
  • MessagesPlaceholder

Caching

캐싱을 사용하면 LM(언어모델)의 응답을 저장할 수 있다.

  • 예) 채팅 봇이 있고 그 채팅 봇이 항상 똑같은 질문을 받는다면 계속 답변을 생성하지 않고, 이미 답변한 답을 캐싱을 이용하여 재사용한다.
  • InMemoryCache
    1
    2
    3
    4
    5
    6
    from langchain.globals import set_llm_cache, set_debug
    from langchain.cache import InMemoryCache
    set_llm_cache(InMemoryCache())
    set_debug(True)
    chat.predict("How do you make italian pasta")
    chat.predict("How do you make italian pasta")
    • 모든 response가 메모리에 저장된다.
    • 첫 predict와 두번째 predict의 응답속도가 다르다.
    • set_debug는 프롬프트로 표시되고 있는 모든 것들을 보여준다.
  • 데이터베이스 캐싱
    1
    2
    3
    from langchain.globals import set_llm_cache, set_debug
    from langchain.cache import SQLiteCache
    set_llm_cache(SQLiteCache("cache.db"))
    • 실행시키면 자동으로 sqlite 데이터베이스가 생성된다.
    • How to cache LLM responses | 🦜️🔗 LangChain
    • ChatOpenAI의 streaming=True

데이터베이스에 히스토리 저장하기

langchain docs의 integration을 보면 Memory를 백업할 수 있는 다양한 DB들을 확인할 수 있다.
Message histories | 🦜️🔗 LangChain
ex) MongoDB, Redis, PostgresQL

Redis Chat Message History

RedisChatMessageHistory를 이용하여 read/write에 대한 low-latency로 chat message를 저장하고 관리할 수 있다.

Setup

1
% pip install -qU langchain-redis langchain-openai redis
1
docker run -d -p 6379:6379 redis:latest

Importing Required Libraries

1
2
3
4
5
6
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain_redis import RedisChatMessageHistory

RedisChatMessageHistory 사용 예시

1
2
3
4
5
6
7
8
9
# RedisChatMessageHistory 초기화
history = RedisChatMessageHistory(session_id="user_123", redis_url=REDIS_URL)
# Add messages to the history
history.add_user_message("Hello, AI assistant!")
history.add_ai_message("Hello! How can I assist you today?")
# Retrieve messages
print("Chat History:")
for message in history.messages:
print(f"{type(message).__name__}: {message.content}")

output

1
2
3
Chat History:
HumanMessage: Hello, AI assistant!
AIMessage: Hello! How can I assist you today?

Redis Chat Message History | 🦜️🔗 LangChain

MongoDB Chat Message History

1
pip install -U --quiet langchain-mongodb
1
2
# os.environ["LANGCHAIN_TRACING_V2"] = "true"
# os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()
1
2
3
4
5
6
7
8
9
from langchain_mongodb.chat_message_histories import MongoDBChatMessageHistory
chat_message_history = MongoDBChatMessageHistory(
session_id="test_session",
connection_string="mongodb://mongo_user:password123@mongo:27017",
database_name="my_db",
collection_name="chat_histories",
)
chat_message_history.add_user_message("Hello")
chat_message_history.add_ai_message("Hi")
1
chat_message_history.messages
1
[HumanMessage(content='Hello'), AIMessage(content='Hi')]
Author

hamin

Posted on

2024-10-22

Updated on

2024-10-24

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.
You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.