3. μ±—λ΄‡μ˜ κΈ°μ–΅λ ₯, Memory

🎯 이 μ±•ν„°μ—μ„œ 배울 것

  • 챗봇이 λŒ€ν™”λ₯Ό κΈ°μ–΅ν•˜κ²Œ λ§Œλ“œλŠ” β€˜Memoryβ€™μ˜ μ€‘μš”μ„± μ΄ν•΄ν•˜κΈ°
  • ConversationBufferMemory: λͺ¨λ“  λŒ€ν™”λ₯Ό κΈ°μ–΅ν•˜λŠ” κ°€μž₯ 기본적인 λ©”λͺ¨λ¦¬
  • ConversationBufferWindowMemory: 졜근 λŒ€ν™”λ§Œ κΈ°μ–΅ν•˜μ—¬ 토큰을 μ ˆμ•½ν•˜λŠ” λ©”λͺ¨λ¦¬
  • ConversationSummaryMemory: κΈ΄ λŒ€ν™”λ₯Ό μš”μ•½ν•˜μ—¬ ν•΅μ‹¬λ§Œ κΈ°μ–΅ν•˜λŠ” λ©”λͺ¨λ¦¬
  • ConversationSummaryBufferMemory: 졜근 λŒ€ν™”λŠ” κ·ΈλŒ€λ‘œ, 였래된 λŒ€ν™”λŠ” μš”μ•½ν•˜λŠ” ν•˜μ΄λΈŒλ¦¬λ“œ λ©”λͺ¨λ¦¬
  • ConversationKGMemory: λŒ€ν™”μ—μ„œ 지식 κ·Έλž˜ν”„(Knowledge Graph)λ₯Ό κ΅¬μΆ•ν•˜λŠ” κ³ κΈ‰ λ©”λͺ¨λ¦¬
  • LLMChain 및 LCELκ³Ό ν•¨κ»˜ Memoryλ₯Ό ν†΅ν•©ν•˜λŠ” 방법

ConversationBuffer(Window)Memory

🎯 이번 λ‹¨κ³„μ—μ„œ 배울 것

  • ConversationBufferMemoryλ₯Ό μ‚¬μš©ν•˜μ—¬ 전체 λŒ€ν™” 기둝을 μ €μž₯ν•˜λŠ” 방법
  • ConversationBufferWindowMemoryλ₯Ό μ‚¬μš©ν•˜μ—¬ 졜근 K개의 λŒ€ν™”λ§Œ μ €μž₯ν•˜λŠ” 방법

πŸ“ 1단계: λŒ€ν™” κΈ°μ–΅ν•˜κΈ°

전체 μ½”λ“œ (notebook.ipynb):

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
27
28
29
30
from langchain.memory import ConversationBufferMemory, ConversationBufferWindowMemory
from langchain.chat_models import ChatOpenAI
from langchain.schema.runnable import RunnablePassthrough
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

# 1. κ°€μž₯ 기본적인 λ©”λͺ¨λ¦¬: λͺ¨λ“  λŒ€ν™”λ₯Ό μ €μž₯
# memory = ConversationBufferMemory(return_messages=True)

# 2. 졜근 K개의 λŒ€ν™”λ§Œ μ €μž₯ν•˜λŠ” λ©”λͺ¨λ¦¬
memory = ConversationBufferWindowMemory(
return_messages=True,
k=4, # 졜근 4개의 λŒ€ν™”(Human+AI)λ₯Ό κΈ°μ–΅
)

model = ChatOpenAI(temperature=0.1)

prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a helpful chatbot"),
MessagesPlaceholder(variable_name="history"), # λ©”λͺ¨λ¦¬κ°€ 여기에 듀어감
("human", "{message}"),
]
)

chain = RunnablePassthrough.assign(history=lambda x: memory.load_memory_variables(x)["history"]) | prompt | model

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

πŸ” μ½”λ“œ 상세 μ„€λͺ…

1. Memoryλž€?
챗봇은 기본적으둜 β€œμƒνƒœκ°€ μ—†λŠ”(stateless)” νŠΉμ„±μ„ κ°€μ§‘λ‹ˆλ‹€. 즉, 방금 λ‚˜λˆˆ λŒ€ν™”λ„ λ°”λ‘œ μžŠμ–΄λ²„λ¦½λ‹ˆλ‹€. MemoryλŠ” 챗봇이 이전 λŒ€ν™” λ‚΄μš©μ„ κΈ°μ–΅ν•˜κ³  λ‹€μŒ 닡변에 ν™œμš©ν•  수 μžˆλ„λ‘ λŒ€ν™” 기둝을 μ €μž₯ν•˜κ³  κ΄€λ¦¬ν•˜λŠ” ꡬ성 μš”μ†Œμž…λ‹ˆλ‹€.

2. ConversationBufferMemory

  • λͺ¨λ“  λŒ€ν™” λ‚΄μš©μ„ μˆœμ„œλŒ€λ‘œ 버퍼에 μ €μž₯ν•©λ‹ˆλ‹€.
  • κ°„λ‹¨ν•˜μ§€λ§Œ, λŒ€ν™”κ°€ κΈΈμ–΄μ§€λ©΄ ν”„λ‘¬ν”„νŠΈμ— ν¬ν•¨λ˜λŠ” 토큰 양이 λ¬΄ν•œμ • λŠ˜μ–΄λ‚˜ λΉ„μš©κ³Ό μ„±λŠ₯ λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

3. ConversationBufferWindowMemory

  • k νŒŒλΌλ―Έν„°λ‘œ μ§€μ •λœ 개수만큼의 졜근 λŒ€ν™”λ§Œ μ €μž₯ν•©λ‹ˆλ‹€.
  • 토큰 μ‚¬μš©λŸ‰μ„ μ œμ–΄ν•˜λ©΄μ„œλ„ 졜근 λŒ€ν™”μ˜ λ§₯락은 μœ μ§€ν•  수 μžˆλŠ” 효율적인 λ°©λ²•μž…λ‹ˆλ‹€.

4. MessagesPlaceholder
ChatPromptTemplate λ‚΄μ—μ„œ λ©”λͺ¨λ¦¬κ°€ λ™μ μœΌλ‘œ μ‚½μž…λ  μœ„μΉ˜λ₯Ό μ§€μ •ν•˜λŠ” ν”Œλ ˆμ΄μŠ€ν™€λ”μž…λ‹ˆλ‹€. variable_name은 memory의 memory_key와 μΌμΉ˜ν•΄μ•Ό ν•©λ‹ˆλ‹€.

βœ… 체크리슀트

  • ConversationBufferWindowMemoryλ₯Ό μ΄ˆκΈ°ν™”ν•˜κ³  k 값을 μ„€μ •ν–ˆλ‚˜μš”?
  • MessagesPlaceholderλ₯Ό ν”„λ‘¬ν”„νŠΈμ— μΆ”κ°€ν–ˆλ‚˜μš”?
  • 체인 μ‹€ν–‰ ν›„ memory.save_contextλ₯Ό ν˜ΈμΆœν•˜μ—¬ λŒ€ν™”λ₯Ό μ €μž₯ν–ˆλ‚˜μš”?

ConversationSummary(Buffer)Memory

🎯 이번 λ‹¨κ³„μ—μ„œ 배울 것

  • ConversationSummaryMemory: 전체 λŒ€ν™”λ₯Ό LLM을 μ‚¬μš©ν•΄ μš”μ•½ν•˜μ—¬ μ €μž₯ν•˜λŠ” 방법
  • ConversationSummaryBufferMemory: μš”μ•½κ³Ό 버퍼링을 κ²°ν•©ν•œ ν•˜μ΄λΈŒλ¦¬λ“œ λ©”λͺ¨λ¦¬

πŸ“ 1단계: λŒ€ν™” μš”μ•½ν•˜μ—¬ κΈ°μ–΅ν•˜κΈ°

전체 μ½”λ“œ (notebook.ipynb):

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
27
from langchain.memory import ConversationSummaryMemory, ConversationSummaryBufferMemory
from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(temperature=0.1)

# 1. 전체 λŒ€ν™”λ₯Ό μš”μ•½ν•˜λŠ” λ©”λͺ¨λ¦¬
# memory = ConversationSummaryMemory(llm=llm)

# 2. ν•˜μ΄λΈŒλ¦¬λ“œ λ©”λͺ¨λ¦¬
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("Hi I'm Nicolas, I live in South Korea", "Wow that is so cool!")
add_message("South Korea is so pretty", "I wish I could go!!!")

# max_token_limit을 λ„˜λŠ” κΈ΄ λŒ€ν™”λ₯Ό μΆ”κ°€ν•˜λ©΄...
add_message("How far is Brazil from Argentina?", "I don't know! Super far!")
# ... get_history()λ₯Ό ν˜ΈμΆœν•˜λ©΄ 였래된 λŒ€ν™”κ°€ μš”μ•½λœ 것을 λ³Ό 수 있음

πŸ” μ½”λ“œ 상세 μ„€λͺ…

1. ConversationSummaryMemory

  • λŒ€ν™”κ°€ 좔가될 λ•Œλ§ˆλ‹€ LLM을 ν˜ΈμΆœν•˜μ—¬ 전체 λŒ€ν™” λ‚΄μš©μ„ μš”μ•½ν•©λ‹ˆλ‹€.
  • λŒ€ν™”κ°€ 아무리 길어져도 ν”„λ‘¬ν”„νŠΈμ— ν¬ν•¨λ˜λŠ” 토큰 양을 μΌμ •ν•˜κ²Œ μœ μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • 단점: λŒ€ν™”κ°€ 좔가될 λ•Œλ§ˆλ‹€ μš”μ•½μ„ μœ„ν•΄ LLM을 ν˜ΈμΆœν•˜λ―€λ‘œ λΉ„μš©μ΄ λ°œμƒν•˜κ³  μ•½κ°„μ˜ 지연이 생길 수 μžˆμŠ΅λ‹ˆλ‹€.

2. ConversationSummaryBufferMemory

  • ConversationBufferMemory와 ConversationSummaryMemory의 μž₯점을 κ²°ν•©ν•œ κ²ƒμž…λ‹ˆλ‹€.
  • ν‰μ†Œμ—λŠ” λŒ€ν™”λ₯Ό 버퍼에 κ·ΈλŒ€λ‘œ μ €μž₯ν•˜λ‹€κ°€, max_token_limit으둜 μ§€μ •λœ 토큰 양을 μ΄ˆκ³Όν•˜λ©΄ κ°€μž₯ 였래된 λŒ€ν™”λΆ€ν„° μš”μ•½ν•˜μ—¬ λ²„νΌμ˜ 크기λ₯Ό μ€„μž…λ‹ˆλ‹€.
  • νš¨μœ¨μ„±κ³Ό μ •ν™•μ„± μ‚¬μ΄μ˜ κ· ν˜•μ„ 맞좘 맀우 μ‹€μš©μ μΈ λ©”λͺ¨λ¦¬μž…λ‹ˆλ‹€.

λ™μž‘ 흐름 (SummaryBuffer):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. λŒ€ν™” 1, 2, 3 μΆ”κ°€ -> 버퍼에 κ·ΈλŒ€λ‘œ μ €μž₯
[H: μ•ˆλ…•, λ‚œ λ‹ˆμ½”μ•Ό, ...]
[A: 와 λ©‹μ§€λ‹€!]
[H: ν•œκ΅­μ€ 예뻐, ...]
[A: κ°€κ³  μ‹Άλ‹€!]

2. λŒ€ν™” 4 μΆ”κ°€ (max_token_limit 초과)
|
V
3. κ°€μž₯ 였래된 λŒ€ν™”(1)λ₯Ό μš”μ•½ν•˜μ—¬ SystemMessage둜 λ§Œλ“¦
[S: μ‚¬μš©μžλŠ” λ‹ˆμ½”μ΄κ³  ν•œκ΅­μ— μ‚°λ‹€κ³  μ†Œκ°œν–ˆλ‹€.]
[H: ν•œκ΅­μ€ 예뻐, ...]
[A: κ°€κ³  μ‹Άλ‹€!]
[H: λΈŒλΌμ§ˆμ€ μ–Όλ§ˆλ‚˜ λ©€μ–΄?]
[A: μ—„μ²­ λ©€μ–΄!]

βœ… 체크리슀트

  • ConversationSummaryBufferMemoryλ₯Ό llmκ³Ό max_token_limitκ³Ό ν•¨κ»˜ μ΄ˆκΈ°ν™”ν–ˆλ‚˜μš”?
  • μ—¬λŸ¬ 번의 λŒ€ν™”λ₯Ό μΆ”κ°€ν•œ ν›„, get_history()λ₯Ό 톡해 였래된 λŒ€ν™”κ°€ μš”μ•½λ˜λŠ” 것을 ν™•μΈν–ˆλ‚˜μš”?

ConversationKGMemory

🎯 이번 λ‹¨κ³„μ—μ„œ 배울 것

  • ConversationKGMemoryλ₯Ό μ‚¬μš©ν•˜μ—¬ λŒ€ν™”μ—μ„œ 지식 κ·Έλž˜ν”„(Knowledge Graph)λ₯Ό μΆ”μΆœν•˜κ³  ν™œμš©ν•˜λŠ” 방법

πŸ“ 1단계: 지식 κ·Έλž˜ν”„λ‘œ λŒ€ν™” κΈ°μ–΅ν•˜κΈ°

전체 μ½”λ“œ (notebook.ipynb):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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("Hi I'm Nicolas, I live in South Korea", "Wow that is so cool!")
add_message("Nicolas likes kimchi", "Wow that is so cool!")

# "λ‹ˆμ½”κ°€ 무엇을 μ’‹μ•„ν•˜λ‹ˆ?" 와 같은 μ§ˆλ¬Έμ— λŒ€ν•œ λ§₯락을 제곡
memory.load_memory_variables({"inputs": "what does nicolas like"})
# 좜λ ₯: {'history': [SystemMessage(content='On Nicolas: Nicolas lives in South Korea. Nicolas likes kimchi.')]}

πŸ” μ½”λ“œ 상세 μ„€λͺ…

1. ConversationKGMemory (Knowledge Graph)

  • λŒ€ν™”μ—μ„œ μ€‘μš”ν•œ 개체(Entity, 예: μ‚¬λžŒ, μž₯μ†Œ)와 κ·Έλ“€ μ‚¬μ΄μ˜ 관계λ₯Ό μΆ”μΆœν•˜μ—¬ 지식 κ·Έλž˜ν”„ ν˜•νƒœλ‘œ μ €μž₯ν•©λ‹ˆλ‹€.
  • λ‹¨μˆœν•œ λŒ€ν™” 기둝이 μ•„λ‹Œ, κ΅¬μ‘°ν™”λœ 정보λ₯Ό κΈ°μ–΅ν•˜λ―€λ‘œ νŠΉμ • κ°œμ²΄μ— λŒ€ν•œ μ§ˆλ¬Έμ— 더 μ •ν™•ν•œ λ§₯락을 μ œκ³΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ–΄λ–»κ²Œ μž‘λ™ν•˜λŠ”κ°€?: λ‚΄λΆ€μ μœΌλ‘œ LLM을 μ‚¬μš©ν•˜μ—¬ λŒ€ν™”μ—μ„œ β€œ(μ£Όμ–΄, μ„œμˆ μ–΄, λͺ©μ μ–΄)” ν˜•νƒœμ˜ νŠΈλ¦¬ν”Œ(triple)을 μΆ”μΆœν•©λ‹ˆλ‹€. (예: (Nicolas, lives in, South Korea), (Nicolas, likes, kimchi))
  • load_memory_variablesκ°€ 호좜될 λ•Œ, ν˜„μž¬ 질문과 κ΄€λ ¨λœ 지식듀을 μš”μ•½ν•˜μ—¬ SystemMessage둜 μ œκ³΅ν•©λ‹ˆλ‹€.

βœ… 체크리슀트

  • ConversationKGMemoryλ₯Ό μ΄ˆκΈ°ν™”ν–ˆλ‚˜μš”?
  • μ—¬λŸ¬ 정보λ₯Ό 담은 λŒ€ν™”λ₯Ό μΆ”κ°€ν–ˆλ‚˜μš”?
  • νŠΉμ • κ°œμ²΄μ— λŒ€ν•œ μ§ˆλ¬Έμ„ load_memory_variables에 μ „λ‹¬ν•˜μ—¬, κ΄€λ ¨λœ 정보가 μš”μ•½λ˜μ–΄ λ°˜ν™˜λ˜λŠ” 것을 ν™•μΈν–ˆλ‚˜μš”?

Memory와 Chain의 톡합

🎯 이번 λ‹¨κ³„μ—μ„œ 배울 것

  • λ ˆκ±°μ‹œ LLMChain에 λ©”λͺ¨λ¦¬λ₯Ό 직접 ν†΅ν•©ν•˜λŠ” 방법
  • μ΅œμ‹  LCEL(LangChain Expression Language)을 μ‚¬μš©ν•˜μ—¬ λ©”λͺ¨λ¦¬λ₯Ό 체인에 ν†΅ν•©ν•˜λŠ” 방법

πŸ“ 1단계: LCEL둜 λ©”λͺ¨λ¦¬ ν†΅ν•©ν•˜κΈ° (μ΅œμ‹  방식)

전체 μ½”λ“œ (notebook.ipynb):

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
27
28
29
30
31
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,
)

prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a helpful AI talking to a human"),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
]
)

# LCEL 체인 ꡬ성
chain = RunnablePassthrough.assign(history=lambda x: memory.load_memory_variables(x)["history"]) | prompt | llm

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

invoke_chain("My name is nico")
invoke_chain("What is my name?") # -> "Your name is Nico."

πŸ” μ½”λ“œ 상세 μ„€λͺ…

1. LCEL λ°©μ‹μ˜ λ©”λͺ¨λ¦¬ 톡합
이것이 ν˜„μž¬ LangChainμ—μ„œ ꢌμž₯ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€. λ©”λͺ¨λ¦¬λ₯Ό 체인의 μΌλΆ€λ‘œ λͺ…μ‹œμ μœΌλ‘œ μ—°κ²°ν•˜μ—¬ 데이터 흐름을 더 λͺ…ν™•ν•˜κ²Œ λ§Œλ“­λ‹ˆλ‹€.

  • RunnablePassthrough.assign(history=...): 체인의 μ‹œμž‘ 뢀뢄에 μƒˆλ‘œμš΄ history ν‚€λ₯Ό μΆ”κ°€ν•˜λŠ” λ‹¨κ³„μž…λ‹ˆλ‹€.
  • lambda x: memory.load_memory_variables(x)["history"]: invokeκ°€ 호좜될 λ•Œ memoryμ—μ„œ λŒ€ν™” 기둝을 λ‘œλ“œν•˜λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€. 이 ν•¨μˆ˜λŠ” 체인이 싀행될 λ•Œλ§ˆλ‹€ ν˜ΈμΆœλ˜μ–΄ 항상 μ΅œμ‹  λŒ€ν™” 기둝을 κ°€μ Έμ˜΅λ‹ˆλ‹€.
  • memory.save_context(...): 체인 싀행이 λλ‚œ ν›„, μ‚¬μš©μžμ˜ 질문과 λͺ¨λΈμ˜ 닡변을 μˆ˜λ™μœΌλ‘œ λ©”λͺ¨λ¦¬μ— μ €μž₯ν•˜μ—¬ λ‹€μŒ λŒ€ν™”λ₯Ό μ€€λΉ„ν•©λ‹ˆλ‹€.

2. λ ˆκ±°μ‹œ LLMChain 방식 (μ°Έκ³ )

1
2
3
4
5
6
7
8
9
10
11
# μ˜ˆμ „ 방식
from langchain.chains import LLMChain

chain = LLMChain(
llm=llm,
memory=memory, # memoryλ₯Ό νŒŒλΌλ―Έν„°λ‘œ 직접 전달
prompt=prompt,
verbose=True,
)

chain.predict(question="My name is Nico")
  • LLMChain은 λ©”λͺ¨λ¦¬ 관리λ₯Ό λ‚΄λΆ€μ μœΌλ‘œ μžλ™ μ²˜λ¦¬ν•΄μ€˜μ„œ νŽΈλ¦¬ν–ˆμ§€λ§Œ, λ³΅μž‘ν•œ 체인을 ꡬ성할 λ•Œ 데이터 흐름이 λΆˆλΆ„λͺ…ν•΄μ§€λŠ” 단점이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. LCEL 방식은 이λ₯Ό κ°œμ„ ν•˜μ—¬ 더 λͺ…μ‹œμ μ΄κ³  μœ μ—°ν•œ 체인 ꡬ성을 κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.

βœ… 체크리슀트

  • LCEL을 μ‚¬μš©ν•˜μ—¬ λ©”λͺ¨λ¦¬ λ‘œλ“œ, ν”„λ‘¬ν”„νŠΈ, λͺ¨λΈμ„ μˆœμ„œλŒ€λ‘œ μ—°κ²°ν–ˆλ‚˜μš”?
  • RunnablePassthrough.assign을 μ‚¬μš©ν•˜μ—¬ 체인에 historyλ₯Ό μ£Όμž…ν–ˆλ‚˜μš”?
  • 체인 호좜 ν›„ save_contextλ₯Ό μ‚¬μš©ν•˜μ—¬ λŒ€ν™” λ‚΄μš©μ„ μˆ˜λ™μœΌλ‘œ μ €μž₯ν–ˆλ‚˜μš”?
  • 챗봇이 이전 λŒ€ν™” λ‚΄μš©μ„ κΈ°μ–΅ν•˜κ³  λ‹΅λ³€ν•˜λŠ” 것을 ν™•μΈν–ˆλ‚˜μš”?

좜처 : https://nomadcoders.co/fullstack-gpt