카테고리 없음

[노마드코더] 내돈내산강의_풀스택 GPT 과정_Memory

개발자 배찌 2025. 7. 3. 17:48
728x90

오늘도 힘내봅시다..!  memory에 대해 배우기

 

오픈AI에서 제공하는 기본API는 memory를 지원하지 않는다.

즉, 모델에게 어떤 말을 건네도 모든 대화내용을 까먹어서 대화가 매끄럽지가 않다..

 

반면에 실제 chat gpt에는 메모리가 탑재되어있어서 실제로 사람과 이야기 하고있다는 느낌을 들게 하지..

이것은 챗봇이 이전 대화내용이나 질문을 기억하고 있다는 것!

 

이제 memory 종류와 차이점, langchain에 memory를 탑재시키는 방법을 배울 예정

(langchain에는 5가지 정도 종류의 메모리가있고, 각각 저장방식도 다르고 장단점이 있다)

 

#0. ConversationBufferMemory

이 메모리는 엄청 단순하다. 그냥 단순히 이전 대화 내용 전체를 저장하는것

단점 :  내용이 길어질 수록 메모리도 계속 커지니까 비효율적임요. 

유저와 ai의 대화가 길어질 수록 우리가 모델에게 매번 보내야될 대화기록이 길어지고 상당히 비효율적이며 비용도 많이 나감.

 

그래도 한번 연습해보자! ㅎ이해하기 쉬운 메모리니까ㅎ

이 메모리는 text completion할 때, 예측을 해야할 때, 텍스트를 자동완성하고싶을 때 유용하다

from operator import itemgetter
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain.schema.runnable import RunnablePassthrough,RunnableLambda
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

model = ChatOpenAI()

#prompt template을 3부분으로 구성
#시스템메시지, 이전 대화들이 들어갈 위치, 사용자입력메시지
prompt = ChatPromptTemplate.from_messages(
[
("system","당신은 도움을 주는 챗봇입니다."),
MessagesPlaceholder(variable_name="history"),
("human","{message}")

]
)

#ConversationBufferMemory : 대화 기록을 버퍼에 저장
memory = ConversationBufferMemory(return_messages=True)

#저장된 대화 기록을 불러와서 history키로 반환
def load_memory(_):
x = memory.load_memory_variables({})
return {"history":x["history"]}

# RunnablePassthrough : 입력을 그대로 전달하면서 추가 처리를 할 수 있는 클래스
chain = RunnablePassthrough.assign(history=load_memory) | prompt | model

inputs = {"message":"안녕 나는 배디옹이야."}
response = chain.invoke(inputs)
response
>>응답결과
# response 객체의 내용 (AIMessage 형태)
AIMessage(
    content="안녕하세요, 배디옹님! 만나서 반가워요. 저는 도움을 드리는 챗봇입니다. 무엇을 도와드릴까요?",
    response_metadata={
        'token_usage': {
            'completion_tokens': 35,
            'prompt_tokens': 25,
            'total_tokens': 60
        },
        'model_name': 'gpt-3.5-turbo',
        'system_fingerprint': 'fp_xxx',
        'finish_reason': 'stop'
    }
)
>>실제 출력결과
안녕하세요, 배디옹님! 만나서 반가워요. 저는 도움을 드리는 챗봇입니다. 무엇을 도와드릴까요?

 

#1. ConversationBufferWindowMemory

특정부분만을 저장하는 메모리

- 예를들어 최근5개의 메시지만 저장한다 라고 범위를 지정했을 때, 6번째 메시지가 추가되면 가장 오래된 메시지는 버려지는 방식

- 제일 최근 부문만 저장한다는 특성을 가지고 있음.

- 저장범위는 결정할 수 있음

- 장점 : 메모리를 특정크기로 유지할 수 있다. 전체 대화내용을 저장하지 않아도 된다.

- 단점 : 챗봇이 전체 대화가 아닌 최근 대화에만 집중한다.

from langchain.memory import ConversationBufferWindowMemory

memory = ConversationBufferMemory(return_message=True, k=4)

 

 

#2. ConversationSummaryMemory

- lllm사용한 메모리방식

- message 그대로 저장하는 것이 아니라 conversation의 요약을 자체적으로 저장하는것

- 초반에는 conversationSummaryMemory는 더 많은 토큰과 저장공간을 차지하게 된다.

- 그러나 conversation버퍼 메모리를 사용하고 있어서 대화가 진행될 수록 저장된 모든 메시지가 매우 많아지면서 연결되겠지.

- conversation 메시지가 많아질 수록 conversationSummaryMessage의 도움을 받아 요약하는 것이 토큰의 양도 줄어들면서 훨씬 나은 방법이 됨!

from langchain.memory import ConversationSummaryMemory
from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(temperature=0.1)
memory = ConversationSummaryMemory(llm = llm)

def add_message(input, output):
memory.save_context({"input": input},{"output":output})

def get_history():
return memory.load_memory_variables({})

add_message("안녕? 나는 배디옹이야. 나는 한국에 살고있어.", "우와 정말 멋지구나!")

add_message("한국은 정말 아름다워","나도 가보고싶다!")

get_history()


 

#ConversationSummaryBufferMemory

- conversation summary memory, conversation buffer memory 의 결합

- 우리가 llm에 다다른 순간에, 그냥 무슨일이 일어났는지 잊어버리는 것 대신 오래된 메시지들을 요약한다. 이것이 의미하는 바는 가장 최근의 상호작용을 계속 추적한다는 것!

- 가장 최근 및 가장 오래전에 주고 받은 메시지 모두 잊혀지지 않고 요약되는 것

- buffer memory와 summary memory가 결합된 메모리는 매우매우 강력하다!!

from langchain.memory import ConversationSummaryBufferMemory
from langchain.chat_models import ChatOpenAI

llm = ChagOpenAI(temperature = 0.1)

memory = ConversationSummaryBufferMemory(
llm = llm,
max_token_limit=150,
return_messages=True,
)

def add_message(input,output):
memory.save_context({"input":input}, {"output":output})

def get_history():
return memory.load_memory_variables({})

add_message("안녕! 나는 배디옹이야. 나는 서울에 살고있어요", "와우 정말 멋지네요")
add_message("한국은 정말 예뻐요", "우와! 저도 가보고싶어요")
add_message("아르헨티나에서 한국까지 얼마나 걸리나요?", "저도 몰라요!")
add_message("브라질에서 아르헨티나끼자 일머나 걸리나요?","저도 몰라요!")

결과값은.. 돈이 없어서 실행은 못해보았지만,,

이전 대화 내역은 요약해서 들고있는것으로 알고있으면 될 것 같다 (슬픔..ㅎㅎ)

 

 

#ConversationKGMemory

Conversation Knowledge Graph Memory

이것도 llm을 가용하는 memory class 인데, 이건 대화중의 엔터티의 knowledge graph를 만든다.

가장 중요한 것들만 뽑아내는 요약본 같은것 ! 

결국 요약을 하지만, 대화에서 entity를 뽑아내는 것이라고 볼 수 있다.

from langchain.memory import ConversationKGMemory
from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(temperature =0.1)

memory = ConversationKGMemory(
llm = llm,
return_messages=True,
)

def add_message(input, output):
memory.save_context({"input":input}, {"output":output})

add_message("안녕 나는 배디옹이야. 나는 한국에 살아", "우와 진짜 멋지다")
memory.load_memory_variables({"input":"배디옹은 누구에요?"})

add_message("배디옹은 피자를 좋아해요","우와 참 멋지네요")
memory.load_memory_variables({"input":"배디옹이 좋아하는 음식은 뭐에요?"})



코드로는 파악할 수 있는게 없지만.. 실행을 하게되면

얘네들은 엔터티의 관계를 저장한다 라는 정도만 기억 하면 될듯!

 

>>추가설명 (cloade에게 물어보기 ㅎㅎ)

일반 메모리 vs KG 메모리

  • 일반 메모리: 대화를 순차적 텍스트로 저장
  • KG 메모리: 대화에서 엔티티(개체)와 관계를 추출하여 그래프 구조로 저장

적합한 경우

  • 장기간 대화: 사용자와 오랜 기간 대화하는 시스템
  • 복잡한 관계: 여러 엔티티 간 관계가 중요한 경우
  • 개인화 서비스: 사용자 특성을 기억해야 하는 서비스

부적합한 경우

  • 단순한 Q&A: 일회성 질문-답변
  • 비용 민감: LLM 호출 비용을 최소화해야 하는 경우
  • 실시간 처리: 빠른 응답이 중요한 시스템

ConversationKGMemory는 단순한 대화 기록을 넘어서 의미적 관계를 이해하고 활용할 수 있는 고급 메모리 시스템으로, 복잡한 대화 시나리오에서 매우 유용합니다.

 

 

 

#5. Memory on LLMChain

llm chain에 memory를 꽂아서 사용하는 방법 학습하기

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 = """
당신은 인간을 상대하는 AI봇입니다.
{chat_history}
Human:{question}
You:
"""

chain = LLMChain(
llm = llm,
memory = memory,
prompt = PromptTemplate.from_template(template),
verbose = True
)

chain.predict(question="내 이름은 배디옹이에요")
chain.predict(question="나는 서울에 살고 있어요")
chain.predict(question="내 이름이 뭐에요?")

 

llm chain은 off-the shelf chain인데 일반적인 목적을 가진 chain을 의미하고, langchain에 아주 많고 아주 유용!

하지만 우리가 스스로를 무언가 만들어 볼 때, off the shelf chain보다는 우리가 직접 커스텀해서 만든 chain을 활용하기를 선호한다.

 

off the shelf chain은 빠르게 시작할 수 있게 해서 좋긴하지만, 복잡한 개발에는 어울리지 않음!

프레임워크를 다루느라 머리 싸매거나 그럴 필요 없이,  off the shelf를 langchain expresstion 언어를 활용해서 우리의 것을 만들 수 있음

 

LCEL 방식을 사용하지, 이방법을 사용하진 않음,.. LCEL ? 이후에 학습 과정에 있음! 참고!

 

#5. Chat Based Memory

from langchain.memory import ConversationSummaryBufferMemory
from langchain.chat_models import ChatOpenAI
from langchain.chains import LLMChain
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

llm = ChatOpenAI(temperature=0.1)

memory = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=120,
memory_key = "chat_history",
return_message = True
)

prompt = ChatPromptTemplate.from_messages(
[
("System","당신은 사람과 이야기를 하는 AI 챗봇입니다."),
MessagesPlaceholder(variable_name="chat_history"),
("human","{question}")
]
)

chain = LLMChain(
llm = llm,
memory = memory,
prompt = prompt,
verbose=True
)

chain.predict(question="내 이름은 배디옹입니다.")

#ai가 답변이 온다
#예) 디옹이야 반가워! 요즘 어디에 머물러있어?

chain.predict(question="나는 서울에 살아요")

#ai가 답변이 온다.
#예) 그것 참 멋지다 디옹아! 서울은 바쁜도시이고 히스토리와 역사가 깊어. 서울에 대해서 더 궁금한게 있어? 내가 설명해줄게

chain.predict(question="내 이름이 뭐라구요?")

#ai가 답변이 온다.
#예) 너는 디옹이야.

대화 기반 메시지의 memory는 아주 쉽다!

기억해야할 것은, memory 클래스가 memory를 두가지 방식으로 출력할 수 있다는 것을 알아야한다.

문자열 형태일 수도 있고, message 형태 일 수도 있음.

여기서 우리가 어떤 설정도 바꾸지 않았기 때문에  텍스트 기반인데, 대화 기반의 채팅으로 사용하고 싶다면 이걸 바꿔줘야함

return message = true

이건 문자열로 바꾸지 말고 실제 메시지로 바꿔달라는 것을 의미한다.

사람 메시지든, ai메시지든, 시스템 메시지든 실제 message 클래스로 바꿔달라는 것.

 

여기에서 messagePlaceholder 사용!

누가 보냈는지 알 수 없는, 예측하기 어려운 메시지의 양과 제한없는 양의 메시지를 가질 수 있어요.

 

우린 그게 시스템메시지인지, 사람메시지인지 혹인 ai메시지인지 알 수 없잖아.

그러면 우리의 대화 memory는 그 기록들에서 메시지들을 가져와서 모든 메시지 기록들로 여기 messagePlaceholder를 채운다.

이건 아주 간단하고 고상한 방법이다.

 

conversation summary buffer memory가 ai, 사람, 시스템 메시지를 줄거고

우리는 그게 얼마나 많은지 알 수 없기에 message place holder를 붙혔다. 

 

얘네가 하는 역할은 우리는 메시지가 얼마나 많고 누구에게로부터 있는지 모르지만 얘네가 이 memory class로 대체 됨.

 

약간 말이 좀 어렵지만.. 도와줘요 클로드!! 

message place holder 핵심 장점:

  • 대화 맥락을 자동으로 유지
  • 메모리와 프롬프트를 쉽게 연결
  • 동적으로 대화 기록이 업데이트됨

즉, 이전 대화 내용을 현재 프롬프트에 "끼워넣어주는" 역할을 합니다.

 

다음 파트에서 배울 내용은!

수동으로 만들어진 chain에 langchain expression언어를 활용해서 추가하는 법

이건 또 무슨말일지 잔뜩 기대 중 ..

 

#7. LCEL Based Memory

LCEL 이 뭔데요?

**LCEL (LangChain Expression Language)**는 LangChain에서 제공하는 선언적 체인 구성 언어

라고 합니다..

이 | (파이프) 연산자가 바로 LCEL의 핵심..

 

langchain expression 언어를 이용하여 생성된 체인에 메모리를 추가하는 것은 어렵지 않고, 

실제로 변경작업을 할 때 권장되는 방법이다.

현재 langchain expression언어에서 동작하는 방법이다.

 

현재로서는 아주 수동적인 과정임

하지만 배우는 과정이니 하나하나 천천히 해보기!

from langchain.memory import ConversationSummaryBufferMemory
from langchain.chat_models import ChatOpenAI
from langchain.schema.runnable import RunnablePassthrough
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

llm = ChatOpenAI(temperature=0.1)

memory = ConversationSummaryBufferMemory(
llm = llm,
max_token_limit=120,
return_messages=True,
)

prompot = ChatPromptTemplate.from_messages(
[
("system","당신은 사람과 소통하는 ai입니다."),
MessagesPlaceholder(variable_name="history"),
("human","{question}")
]
)

def load_memory(_):
return memory.load_memory_variables({})["history"]

chain = RunnablePassthrough.assign(history=load_memory) | prompt | llm

def invoke_chain(question):
result = chain.invoke({"question":question})
memory.save_context(
{"input":question},
{"output": result.content},
)
print(result)

invoke_chain("내 이름은 배디옹입니다.")
invoke_chain("내 이름이 뭐에요?")

 

답변에서 "LLMChain 과 LCEL을 동시에 사용하는것이 더 편하게 느껴졌습니다" 라는 댓글이 있길래

LLMChain?? LCEL?? 동시에 사용? 이게무슨말이야!!! 난 역시 아직 멀었어 ㅠ흑흑..

그래도 클로드의 도움을 받아 다시 이해하기 !

LLMChain vs LCEL

LLMChain (레거시 방식)

from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

prompt = PromptTemplate(template="질문: {question}")
chain = LLMChain(llm=llm, prompt=prompt)
result = chain.run(question="안녕하세요")

 

LCEL (현대적 방식)

from langchain.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_template("질문: {question}")
chain = prompt | llm
result = chain.invoke({"question": "안녕하세요"})

결론

LLMChain: 간단한 프로토타입, 학습용 LCEL: 실제 프로덕션, 복잡한 애플리케이션

현재 LangChain은 LCEL을 표준으로 밀고 있어서, 새로 배우신다면 LCEL에 집중하는 것이 좋습니다!

 

이렇게 우여곡절 끝에 memory 공부도 마쳤다..

빨리 다른거 더 배우고싶은 생각 뿜뿜!!

복습만이 살 길...!