본문 바로가기
AI

랭체인(LangChain)의 컴포넌트(Components) : Memory

by Dev_Mook 2025. 2. 19.
728x90

랭체인의 컴포넌트에는 Model I/ORetrievalCompositionMemoryCallbacks 등이 있어요.
이번에는 Memory에 대해서 학습해볼게요.
 
MemoryChains를 활용하는데 중요한 역할을 하는 Component에요.
Agnets에서도 메모리를 사용하는 방법에 대해 간단히 알아봤지만,
특히 대화형 AI를 만들 때 사용자와 AI 간의 대화 맥락을 기억해야
자연스러운 답변이 만들어지기 때문에 Memory는 매우 중요한 구성 요소랍니다!
 
 

# [Beta] Memory

 
대부분의 LLM 애플리케이션에는 대화형 인터페이스가 있어서 사용자와 대화를 할 수 있어요.
'대화'를 할 때 중요한건 뭘까요?
바로 '대화의 맥락'이에요!
 
상대방과 "오늘의 날씨"에 대해 대화를 하다가 갑자기
"AI가 일자리를 빼앗아 갈거야"라는 답변을 듣는다면?
그리고 계속 주제가 바뀌며 대화가 진행된다면?
 
그저 질문과 답변만 반복될 뿐, 더이상 대화가 아닌게 되어버리죠...
 
그럼 LLM 애플리케이션과 대화 형식으로 질문·답변을 하기 위해서는 무엇이 필요할까요?
바로, 앞서 대화했던 내용을 LLM 애플리케이션이 기억할 수 있도록 하는건데요!
Memory가 바로 이 역할을 해줘요.
사용자와 상호 작용을 통해 나눈 대화 정보를 저장하는 것!
 

Memory

 

간단한 LLM 애플리케이션은 최근 대화 내용의 일부만 참고하여도
어느정도 맥락에 맞는 답변을 할 수 있어요.
하지만 복잡한 시스템은 Entity와 사용자 사이의 관계에 대해 정보를
유지할 수 있도록 지속적으로 업데이트 해야되요.
메모리의 역할이 더 중요해진거죠!
이 때 사용되는 모델을 World Model이라고 한데요.
* World Model에 대해서는 더 찾아봐야겠어요...
 
LangChain은 시스템에 메모리를 추가하기 위한 많은 유틸리티를 제공하는데요.
이 유틸리티는 그 자체로 사용할수도 있고, Chain과 원활하게 통합할 수 있도록 사용되기도 해요.
 
LangChain의 메모리와 관련된 기능은 대부분 'Beta'로 표시되는데
그 이유는 두 가지가 있어요.

  1. 대부분의 기능(일부 예외 사항 있음)은 아직 프로덕션 준비가 되지 않았어요.
  2. 그리고 대부분의 기능(몇 가지 예외가 있음)은 새로운 LCEL 구문이 아닌 레거시 Chain에서만 작동합니다.

위에서 말한 예외 사항 중 대표적인게 ChatMessageHistory에요.
ChatMessageHistory의 기능은 대부분 프로덕션 준비가 외어 있고, LCEL과 통합할 수 있어요.
 
 

# Introduction

 
모든 Chains는 특정 입력에 대해 어떻게 동작해야 하는지 '핵심 실행 로직'을 정의해줘야 돼요.
이 때 입력은 사용자가 직접 전달한 값을 사용할 수도 있지만 메모리로부터 데이터를 읽어와서 사용할 수도 있어요.
그렇기 때문에 하나의 Chain이 실행될 때 메모리 시스템과 두 번 상호작용을 하게되요.
 

  1. 최초로 사용자의 입력을 받은 후 Chain은 로직을 실행하기 전에 메모리 시스템에서 데이터를 읽어와요.
  2. 그리고 사용자가 전달한 입력 값과 통합하여 입력 데이터를 보강한답니다.
  3. 예를 들어 이전 대화 내용을 메모리로부터 읽어와서 입력 값을 보강하는거죠.
  4. 기본 논리를 실행한 후 결과를 반환하기 전에 Chain은 입력 값과 출력값을 메모리에 저장해요.
    이후 다시 실행될 때 저장된 데이터를 참고한답니다.

이러한 이유로 Memory 시스템은 읽기와 쓰기를 모두 지원해야 되요.
 

Memory System Diagram

 

1. 시스템 메모리 구축하기

 
모든 메모리 시스템의 두 가지 핵심 설계 결정은 다음과 같아요.

  • 상태가 저장되는 방식
  • 상태가 쿼리되는 방식

 
Storing : 채팅 메시지 목록 저장
 
메모리 시스템은 모든 대화 기록을 저장해요.
실행될 때 저장된 내용을 참고하여 사용자와 자연스러운 대화를 하기 위함이죠.
그렇다고 저장된 모든 대화 기록을 사용하지는 않는다고 해요.
 
LangChain 메모리 모듈의 핵심 부분 중 하나는
이러한 채팅 메시지들을 저장하기 위해 다양한 통합 방식을 제공하는데,
메모리 내에 목록으로 저장하는 방법부터
데이터베이스에 저장하는 방법까지 다양한 방식을 제공해요.
 
 
Querying : 채팅 메시지에 기반한 데이터 구조와 알고리즘
 
채팅 메시지 목록을 유지하는 것은 매우 간단합니다.
 
간단하게 구현된 메모리 시스템은 실행할 때마다 가장 최근의 메시지만 반환할 수 있어요.
이보다 조금 더 복잡하게 시스템을 구현하게 되면 최근 N개의 메시지를 간략히 요약해서 반환할 수 있죠.
그리고 정교하게 시스템을 구현하면 메시지에서 Entity를 추출하고, 실행 결과에서 추출된 Entity와 
관련된 정보만 반환할 수도 있어요.
* 여기서 Entity는 특정 객체나 개념을 나타내는 용어입니다.
 
이러한 메모리 시스템을 구현하기 위해 데이터 구조와 알고리즘을 활용하게 되는데,
데이터 구조와 알고리즘은 채팅 메시지를 유용하게 볼 수 있도록 제공하기 때문에 복잡해요.
 
각 애플리케이션은 메모리를 조회(Query)하는 방식에 대해 서로 다른 요구 사항을 가질 수 있어요.
메모리 모듈은 간단한 메모리 시스템 구현으로 시작하고,
필요한 경우 Customizing하며 개선(고도화)하는 것이 효과적이에요.
 
 

# 시작하기(Get Start)

 
LangChain에서 Memory가 실제로 어떻게 생겼는지 살펴볼게요.
여기서는 임의의 메모리 클래스와 상호 작용하는 기본 사항에 대해 알아볼건데요.
 
ConversationBufferMemory 체인에서 사용하는 방법을 살펴볼게요.
ConversationBufferMemory는 채팅 메시지 목록을 버퍼에 보관하고,
이를 프롬프트 템플릿에 전달하는 매우 간단한 형태의 메모리에요.
 

1 from langchain.memory import ConversationBufferMemory
2
3 memory = ConversationBufferMemory()
4 memory.chat_memory.add_user_message("hi!")
5 memory.chat_memory.add_ai_message("what's up?")

 
Chain에서 메모리를 사용할 떄 이해해야 할 몇 가지 핵심 개념이 있어요.
여기서는 대부분의 메모리 타입에 관한 일반적인 개념을 다룰 예정이지만,
각 메모리 타입에는 꼭 이해해야 하는 매개변수와 개념이 있답니다.
 
 

1. 메모리에서 반환되는 변수는 무엇인가?

 
Chain에 들어가기 전에 다양한 변수가 메모리에서 읽어집니다.
이 변수들은 특정 이름을 가지며, Chain이 기대하는 변수들과 일치해야 해요.
어떤 변수들이 있는지 확인하려면 memory.load_memory_variables({})를 호출하면 되요.
여기서 빈 딕셔너리를 전달하는 이유실제 변수를 대신하는 자리라는 것을 표시하기 위함이에요.
사용 중인 메모리 유형이 입력 변수에 따라 달라진다면,
일부 변수를 직접 전달해야할 수도 있어요.
 

6 memory.load_memory_variables({})

# 결과
{'history': "Human: hi!\nAI: what's up?"}

 
load_memory_variables가 history라는 하나의 키를 반환하는 것을 볼 수 있어요.
이는 체인(및 프롬프트)에서 history라는 이름의 입력을 기대해야 한다는 의미입니다.
일반적으로 메모리 클래스의 매개변수를 통해 이 변수를 제어할 수 있습니다.
 
예를 들어, 메모리 변수가 chat_history라는 키로 반환되도록 하려면 다음과 같이 구현할 수 있어요.
 

7 memory = ConversationBufferMemory(memory_key="chat_history")
8 memory.chat_memory.add_user_message("hi!")
9 memory.chat_memory.add_ai_message("what's up?")

# 결과
{'chat_history': "Human: hi!\nAI: what's up?"}

 

키들을 제어하기 위한 매개변수의 이름은 메모리 타입에 따라 다를 수 있어요.

하지만 '(1) 입출력 키를 제어할 수 있다는 것'과 '(2) 어떻게 제어할 수 있는지 이해하는 것'이 중요해요!

 

 

2. 메모리가 문자열인지 아니면 목록인지...

 

가장 일반적인 메모리 타입 중 하나는 채팅 메시지 목록을 반환하는 거에요.

이 메시지들은 하나의 문자열로 모두 이어 붙여서 반환(LLM에 전달할 때 유용)될 수도 있고,

ChatMessages 목록으로 반환(ChatModels에 전달할 때 유용)될 수도 있어요.

 

기본적으로 메시지는 하나의 문자열로 반환하는데,

만약 메시지 목록으로 반환하고 싶으면 'return_messages=True'로 설정하면 되요!

 

10 memory = ConversationBufferMemory(return_messages=True)
11 memory.chat_memory.add_user_message("hi!")
12 memory.chat_memory.add_ai_message("what's up?")

# 결과
{'history': [HumanMessage(content='hi!', additional_kwargs={}, example=False),
  AIMessage(content='what's up?', additional_kwargs={}, example=False)]}

 

 

3. 어떤 Key들이 메모리에 저장되는가?

 

Chain은 여러 개의 입출력 키를 가져오거나 반환하는 경우가 있어요.

이 경우 채팅 메시지 기록에 저장할 키를 어떻게 알 수 있을까요?

 

일번적으로 메모리 타입은 input_keyoutput_key 매개변수로 키를 제어할 수 있어요.

기본 값은 None으로 설정되며, 입출력 키가 하나만 있는 경우 기본 값을 사용해요.

하지만 입출력 키가 여러 개인 경우 어떤 키를 사용할 것인지 이름을 지정해줘야 해요.

 

 

# End to end Example

 

마지막으로 Chain에서 이 기능을 사용하는 방법을 알아볼게요.

LLMChain을 사용하여 LLM과 ChatModel에 대해 어떻게 구현되는지 살펴봅시다.

 

[ LLM을 사용할 경우 ]

from langchain_openai import OpenAI
from langchain_core.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory


llm = OpenAI(temperature=0)
# Notice that "chat_history" is present in the prompt template
template = """You are a nice chatbot having a conversation with a human.

Previous conversation:
{chat_history}

New human question: {question}
Response:"""
prompt = PromptTemplate.from_template(template)
# Notice that we need to align the `memory_key`
memory = ConversationBufferMemory(memory_key="chat_history")
conversation = LLMChain(
    llm=llm,
    prompt=prompt,
    verbose=True,
    memory=memory
)

# 결과
# Notice that we just pass in the `question` variables - `chat_history` gets populated by memory
conversation({"question": "hi"})

 

[ ChatModel을 사용할 경우 ]

from langchain_openai import ChatOpenAI
from langchain_core.prompts import (
    ChatPromptTemplate,
    MessagesPlaceholder,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory


llm = ChatOpenAI()
prompt = ChatPromptTemplate(
    messages=[
        SystemMessagePromptTemplate.from_template(
            "You are a nice chatbot having a conversation with a human."
        ),
        # The `variable_name` here is what must align with memory
        MessagesPlaceholder(variable_name="chat_history"),
        HumanMessagePromptTemplate.from_template("{question}")
    ]
)
# Notice that we `return_messages=True` to fit into the MessagesPlaceholder
# Notice that `"chat_history"` aligns with the MessagesPlaceholder name.
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
conversation = LLMChain(
    llm=llm,
    prompt=prompt,
    verbose=True,
    memory=memory
)

# 결과
# Notice that we just pass in the `question` variables - `chat_history` gets populated by memory
conversation({"question": "hi"})

 

 

# 마무리

 

이번 포스팅에서는 Memory가 어떻게 동작하고,

사용자와 LLM 애플리케이션이 주고받는 메시지를 어떻게 관리할 수 있는지 알아봤어요.

 

AI와 자연스러운 대화를 하기 위해 메모리에 저장된 데이터를 읽어

입력 값을 보강한다는 것을 학습하며

우리가 사용하고 있는 ChatGPT나 미드저니와 같은

생성형 AI가 어떻게 동작하는지 조금은 알게 되었네요.

 

LLM을 활용한 애플리케이션을 구축할 때 Memory Component를 깊게 학습하며

효과적으로 사용해봐야겠어요.

 

이번 포스팅은 여기서 마무리!

 

글을 읽으면서 잘못된 점이 있으면 언제든지 지적해주세요.

더욱 더 공부를 하고 수정을 해나갈게요.

 

그리고 개념과 예제 소스코드는 랭체인 공식 문서에서 가져왔으니,

공식 문서도 참고하면서 봐주세요.

 

 

# 참고

 

 

How-to guides | 🦜️🔗 LangChain

Here you’ll find answers to “How do I….?” types of questions.

python.langchain.com

 

 

How to add memory to chatbots | 🦜️🔗 LangChain

A key feature of chatbots is their ability to use the content of previous conversational turns as context. This state management can take several forms, including:

python.langchain.com

728x90