๐ฉ๐ป ChatGPT๋ฅผ ์ฌ์ฉํ๋ฉด ์ด์ ๋ํ๋ฅผ ๊ณ์ ๊ธฐ์ตํด ๋๊ฐ๋ฉด์ ๋ํ๋ฅผ ์ด์ด๋๊ฐ๋ ๊ฒ์ ์ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ์ ์ฒด ๋ํ์ ๋งฅ๋ฝ์ ์ฝ๊ณ ๋ํ๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๊ฒ์ ๋ฉํฐํด (Multi-turn) ์ด๋ผ๊ณ ํฉ๋๋ค. ์ด์ ๋ค๋ฅด๊ฒ ๋ฐ๋ก ์ง์ ์ ์ง๋ฌธ์๋ง ๋ตํ๋ ๊ฒ์ ์ฑ๊ธํด (single-turn) ์ด๋ผ๊ณ ํฉ๋๋ค. ์ค๋์ Langchain์ ํตํด ๋ฉํฐํด์ ์ด๋ป๊ฒ ๊ตฌํํ ์ ์์ ์ง๋ฅผ ์์๋ณด๋ ค ํฉ๋๋ค.
์ ๊ฐ ์ฐธ๊ณ ํ ๋ฌธ์๋ ์๋ Langchain ํ์ด์ฌ ๊ณต์๋ฌธ์ ์ ๋๋ค. ์ฑ๋ด์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์ ๋ํด ๊ธฐ์ ํ ์๋ฃ์ด๋ฉฐ, ํด๋น ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ค๋ช ๋๋ฆฌ๋ฉฐ, ๋ง์ง๋ง์ผ๋ก ์ด๋ฅผ ์์ฉํ์ฌ ๋๋ง์๊ธฐ ์ฑ๋ด์ ๊ตฌํํด๋ณด์์ผ๋ ๋ค์ํ ํ์ฉ์ ๋์์ด ๋์์ผ๋ฉด ์ข๊ฒ ์ต๋๋ค!
https://python.langchain.com/v0.2/docs/how_to/chatbots_memory/
1. Message Passing
๋ฉํฐํด์ ๊ตฌํํ๋ค๋ ๊ฒ์ ๊ธฐ๋ณธ์ ์ธ ๋ต๋ณ ๊ธฐ๋ฅ์ ์ด์ ๋ํ ๊ธฐ๋ก ์ ์ฅ ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋๋ ๊ฒ์ ๋๋ค.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
chat = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful assistant. Answer all questions to the best of your ability. YOU MUST ANSWER IN KOREAN.",
),
("placeholder", "{messages}"),
]
)
์ง๋ฌธ์ ๋ต๋ณ์ ํ๊ธฐ ์ํ ํ๋กฌํํธ๋ก ๋ณดํต ์๋์ ๊ฐ์ ํ๋กฌํํธ๋ฅผ ์ฌ์ฉํ๊ฒ ๋ ํ ๋ฐ์.
"You are a helpful assistant. Answer all questions to the best of your ability. YOU MUST ANSWER IN KOREAN."
์ฌ๊ธฐ์ ์ถ๊ฐ๋ก ์ด์ ๋ํ ๋ด์ฉ์ ์ ๋ ฅํ placeholder๋ฅผ ์ถ๊ฐํ์ต๋๋ค. placeholder ๋ ์ํ๋ ์์น์ ๋์ ์ผ๋ก ์ฝ์ ๋ ๋ฉ์์ง๋ฅผ ์ถ๊ฐํ๊ธฐ ์ํ ๊ฒ์ผ๋ก, ์ฌ๊ธฐ์๋ ์ด์ ๋ํ ํ์คํ ๋ฆฌ๋ฅผ ์ํ ๊ฒ์ ๋๋ค. ๋ค์ ๋งํด, ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฅ์ํ ๋ต๋ณ์ ํ๊ธฐ ์ํ ๋ชจ๋ธ์์, ์ด์ ๋ํ๋ฅผ ํจ๊ป ๋๊ฒจ์ค์ผ๋ก์จ ๋ํ ๋งฅ๋ฝ์ ์ดํดํ๋ ๋ชจ๋ธ์ด ๊ตฌ์ฑ๋ ๊ฒ์ ๋๋ค.
chain = prompt | chat
์์์ ๋ง๋ค์ด์ค ์ฑํ ๋ชจ๋ธ(chat)๊ณผ ํ๋กฌํํธ(prompt)๋ฅผ ๋ฒํฐ์ปฌ๋ฐ(|) ๋ก ์ฐ๊ฒฐํด์ฃผ์ด, ๊ฐ ๋จ๊ณ๊ฐ ์ด์ ๋จ๊ณ์ ๊ฒฐ๊ณผ๋ฅผ ์ด์ด๋ฐ์ ์ฒ๋ฆฌํ๋๋ก ํ๋ฉด ๋์ ๋๋ค. ์ฌ๊ธฐ์๋ (1) ํ๋กฌํํธ๋ฅผ ํตํด ์ด์ ๋ํ๊ธฐ๋ก์ด ๋ฐ์๋, ๋ชจ๋ธ์ ์ ๋ ฅ๋ ๋ฉ์์ง๊ฐ ์์ฑ๋์๋ค๋ฉด (2)๊ทธ๊ฒ์ ์ฑํ ๋ชจ๋ธ์ ๋๊ฒจ์ฃผ๋ ๊ตฌ์ฑ์ธ ๊ฒ์ ๋๋ค.
ai_msg = chain.invoke(
{
"messages": [
(
"human",
"์ผ๋ณธ์ด๋ก '์ ๋จน๊ฒ ์ต๋๋ค.' ์ด๋ป๊ฒ ๋งํด? ํ๊ธ ๋ฐ์๋ ํจ๊ป ๋งํด์ค.",
),
("ai", "ใใใ ใใพใ (์ดํ๋คํค๋ง์ค)"),
("human", "๋ด๊ฐ ๋ฐฉ๊ธ ๋ญ๋ผ๊ณ ํ์ง?"),
],
}
)
print(ai_msg.content)
output :
๋น์ ์ ์ผ๋ณธ์ด๋ก '์ ๋จน๊ฒ ์ต๋๋ค.'๋ผ๋ ํํ์ ๋ฌผ์ด๋ณด์ จ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด์ ๋ํ ๋ต์ผ๋ก ์ผ๋ณธ์ด๋ก๋ "ใใใ ใใพใ" (์ดํ๋คํค๋ง์ค)๋ผ๊ณ ๋งํ๋ค๊ณ ์๋ ค๋๋ ธ์ต๋๋ค.
Chain์ Runnableํ๋ฏ๋ก invoke ํ ์ ์์ต๋๋ค. ํ๋ผ๋ฏธํฐ์๋ ์์์ ์ค๊ดํธ๋ก ๋น์นธ ๋ซ์ด๋์๋ messages ๋ถ๋ถ์ ๋ค์ด๊ฐ ๋ํ ๋ด์ฉ์ด ์์ฑ๋์๋๋ฐ์. ์์๋ก ์ผ๋ณธ์ด๋ก ์ ๋จน๊ฒ ์ต๋๋ค์ ๋ํ ์ง๋ฌธ๊ณผ ๋ต๋ณ์ ํ์๋ค๊ณ ๊ฐ์ ํ์ต๋๋ค. ๋ง์ง๋ง์ user(human)๊ฐ "๋ด๊ฐ ๋ฐฉ๊ธ ๋ญ๋ผ๊ณ ํ์ง?" ๋ผ๊ณ ๋๋ฌผ์์ผ๋ฉฐ, ๋ชจ๋ธ์ ์ด์ ๋ํ ๋ด์ฉ์ ํจ๊ป ๋๊ฒจ๋ฐ์๊ธฐ ๋๋ฌธ์ ์ output ๊ฒฐ๊ณผ์ ๊ฐ์ด ์ด์ ๋ํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ๋ต๋ณํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์์ ๊ฒฝ์ฐ๋, ๋ํ ๋ด์ฉ์ด ์ด๋ฏธ ์์ธ ๊ฒฝ์ฐ๋ผ๊ณ ๊ฐ์ ํ์ฌ, ์ ์ฒด ๋ํ ๊ธฐ๋ก์ ํํ๋ก ์ฐ๊ฒฐํ์ฌ ํ๋ฒ์ ๋๊ฒจ์ฃผ๋ ์์์๋ค๋ฉด, ์ค์ ๋ก๋ ๋ํ๊ฐ ํ๋์ฉ ์์ด๋ ๊ฒฝ์ฐ์ผ ๊ฒ์ ๋๋ค. ๊ทธ ์์๋ฅผ ์๋ ๊ตฌํํด๋์์ต๋๋ค.
history_list = []
while(True):
user_input = input()
if user_input == "์ข
๋ฃ": break
history_list.append(
(
"human",
user_input,
)
)
print("## CHAT_HISTORY ##")
print(history_list, "\n")
ai_msg = chain.invoke(
{
"messages": history_list,
}
)
print("AI Says : ",ai_msg.content)
history_list.append(
(
"ai",
ai_msg.content,
)
)
(1) while๋ฌธ์ ๋๋ฉด์ ์ฌ์ฉ์ ์ง๋ฌธ์ ๋ฐ์ผ๋ฉด, ๊ทธ ์ง๋ฌธ์ ์ฐ์ history_list์ append ๋ฉ๋๋ค.
(2) ์ฌ์ฉ์ ์ง๋ฌธ์ด ์ถ๊ฐ๋ history_list๋ฅผ ํฌํจํด invoke ํ์ฌ ๋ชจ๋ธ์ ๋ต๋ณ์ ๋ฐ์ต๋๋ค.
(3) ๋ชจ๋ธ์ ๋ต๋ณ ๋ํ history_list์ append ๋ฉ๋๋ค.
(4) ์ ๊ณผ์ ์ ๋ฐ๋ณตํฉ๋๋ค.
์คํ ๊ฒฐ๊ณผ, ์๋ ์ฒ๋ผ chat_history๊ฐ ์ฑ์์ง๊ณ , ์ด์ ๋ํ๋ฅผ ๊ธฐ์ตํ๊ณ ๋ต๋ณํด๋ด๋ ๋ชจ์ต์ ํ์ธํ ์ ์์ ๊ฒ์ ๋๋ค.
[์ฒซ๋ฒ์งธ ๋ํ]
์๋
## CHAT_HISTORY ##
[('human', '์๋ ')]
AI Says : ์๋ ํ์ธ์! ์ด๋ป๊ฒ ๋์๋๋ฆด๊น์?
[๋๋ฒ์งธ ๋ํ]
๋ ๋์ฐ์ด๋ผ๊ณ ํด.
## CHAT_HISTORY ##
[('human', '์๋ '), ('ai', '์๋ ํ์ธ์! ์ด๋ป๊ฒ ๋์๋๋ฆด๊น์?'), ('human', '๋ ๋์ฐ์ด๋ผ๊ณ ํด.')]
AI Says : ์๋ ํ์ธ์, ๋์ฐ๋! ๋ง๋์ ๋ฐ๊ฐ์ต๋๋ค. ์ค๋ ์ด๋ป๊ฒ ๋์๋๋ฆด๊น์?
...
[๋ง์ง๋ง ๋ํ]
๋ด ์ด๋ฆ์ด ๋ญ์๊ฒ?
## CHAT_HISTORY ##
[('human', '์๋ '), ('ai', '์๋ ํ์ธ์! ์ด๋ป๊ฒ ๋์๋๋ฆด๊น์?'), ('human', '๋ ๋์ฐ์ด๋ผ๊ณ ํด.'), ('ai', '์๋ ํ์ธ์, ๋์ฐ๋! ๋ง๋์ ๋ฐ๊ฐ์ต๋๋ค. ์ค๋ ์ด๋ป๊ฒ ๋์๋๋ฆด๊น์?'), ('human', '๊ทธ๋ฆฌ๊ณ ๋ง์ฐจ๋ฅผ ์ข์ํ์ง'), ('ai', '๋ง์ฐจ๋ฅผ ์ข์ํ์๋๊ตฐ์! ๋ง์ฐจ๋ ๋ ํนํ ๋ง๊ณผ ํฅ์ด ์์ด์ ๋ง์ ์ฌ๋๋ค์ด ์ข์ํ์ฃ . ๋ง์ฐจ๋ฅผ ์์ฃผ ๋์๋์, ์๋๋ฉด ํน๋ณํ ๋์๋ง ๋์๋์?'), ('human', '๋ด ์ด๋ฆ์ด ๋ญ์๊ฒ?')]
AI Says : ๋์ฐ๋์ด์์ฃ ? ์์์ ๋ง์ํด ์ฃผ์ จ์ต๋๋ค. ๐
2. Chat History
๋ค์์ ChatMessageHistory() ํด๋์ค๋ฅผ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ์ฒซ๋ฒ์งธ ๋ฐฉ๋ฒ์ ๋ํ ๊ธฐ๋ก์ ์ง์ ํํ๊ณผ ๋ฆฌ์คํธ๋ก ์์ฑํ์ฌ ๋๊ฒจ์ฃผ์๋ค๋ฉด, ์ด๋ฒ์๋ ์ข ๋ ํจ์จ์ ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ ์ฅํ ์ ์๋๋ก ํ์คํ ๋ฆฌ๋ฅผ ์ ์ฅํ๊ณ ๋ก๋ํ๋ ํด๋์ค์ ๋์์ ๋ฐ๋ ๊ฒ์ ๋๋ค.
from langchain_community.chat_message_histories import ChatMessageHistory
chat_history = ChatMessageHistory()
chat_history.add_user_message(
"์ผ๋ณธ์ด๋ก '์ ๋จน๊ฒ ์ต๋๋ค.' ์ด๋ป๊ฒ ๋งํด? ํ๊ธ ๋ฐ์๋ ํจ๊ป ๋งํด์ค."
)
chat_history.add_ai_message("ใใใ ใใพใ (์ดํ๋คํค๋ง์ค)")
chat_history.messages
# chat_history.clear()
add_user_message๋ฅผ ํตํด ์ฌ์ฉ์์ ์ง๋ฌธ์ ์ ์ฅํ ์ ์์ต๋๋ค. ์์์ "human" ์ด๋ผ๋ ์ญํ ํค๋ก ํํ์ ์ ์ฅํ๋ ๊ฒ๊ณผ ๋์ผํ ๋จ๊ณ๋ผ๊ณ ๋ณผ ์ ์์ต๋๋ค.
add_ai_message๋ฅผ ํตํด ๋ชจ๋ธ(AI)์ ๋ต๋ณ์ ์ ์ฅํ ์ ์์ต๋๋ค. ์์์ "ai"๋ผ๋ ์ญํ ํค๋ก ํํ์ ์ ์ฅํ๋ ๊ฒ๊ณผ ๋์ผํ ๋จ๊ณ๋ผ๊ณ ๋ณผ ์ ์์ต๋๋ค.
์ ์ฅ๋ ๋ํ ํ์คํ ๋ฆฌ๋ฅผ ํ์ธํ๋ ค๋ฉด .messages๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค. ์ ์ฅ๋ ๋ํ ํ์คํ ๋ฆฌ๋ฅผ ๋ชจ๋ ์ญ์ ํ๋ ค๋ฉด .clear() ํด์ฃผ๋ฉด ๋ฉ๋๋ค.
chat_history = ChatMessageHistory()
while(True):
user_input = input()
if user_input == "์ข
๋ฃ": break
chat_history.add_user_message(user_input)
response = chain.invoke(
{
"messages": chat_history.messages,
}
)
chat_history.add_ai_message(response)
print(response.content)
์ด ๋ฐฉ๋ฒ์์๋ ๋์ผํ๊ฒ ๋ํ๊ฐ ์์ผ ๋๋ง๋ค ํ์คํ ๋ฆฌ๋ฅผ ์ ์ฅํ ์ ์์ต๋๋ค. ์์์ ์ง์ history_list๋ฅผ ๋ง๋ค์ด append ํ๋ ๊ฒ ๋์ add_user_message, add_ai_message ๋ฅผ ํธ์ถํด ์ฌ์ฉํ์์ต๋๋ค. ๋ํ "messages" ์ ๊ฐ์ผ๋ก chat_history.messages ๋ง ํด์ฃผ๋ฉด ๋๋ ์ฝ๋๊ฐ ํจ์ฌ ๊ฐ๊ฒฐํด์ก์ต๋๋ค.
3. Automatic history management
๋ค์์ ์๋์ผ๋ก ํ์คํ ๋ฆฌ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ธ๋ฐ์. ์์์ ์ฌ์ฉํด๋ณธ ํ์คํ ๋ฆฌ๋ฅผ ์ ์ฅํ๊ณ ๋ก๋ํ๋ ChatMessageHistory() ๋ฅผ ํ์ฉํด ์ค๊ฒ๋๋ค. ๋์ RunnableWithMessageHistory() ํด๋์ค๋ฅผ ์ฌ์ฉํด runnable ํ ๊ฐ์ฒด๋ก ๋ฐ๋ก ์ฌ์ฉํด์ค ์ ์๋๋ก ํฉ๋๋ค. ๋ค์ ๋งํด, ์์์๋ ํ์คํ ๋ฆฌ ์ ์ฅ์ ๋ฐ๋ก, chain์ invoke ๋ฐ๋ก ์งํํ์๋ค๋ฉด, ์ด๋ฒ์๋ Runnable ํ ๋ชจ๋ธ ํ๋์ ํ์คํ ๋ฆฌ ์ ์ฅ์๋ ํฉ์ณ์ฃผ์ด chain์ด invoke ๋ ๋๋ง๋ค ์๋์ผ๋ก ํ์คํ ๋ฆฌ๊ฐ ์ ๋ฐ์ดํธ ๋ ์ ์๋๋ก ํ๋ ๊ฒ๋๋ค.
chat = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful assistant. Answer all questions to the best of your ability. YOU MUST ANSWER IN KOREAN.",
),
("placeholder", "{chat_history}"),
("human", "{input}"),
]
)
chain = prompt | chat
์์์ ์ฌ์ฉํด์ค ๋ต๋ณ์ ์ํ chain์ ๋์ผํ๊ฒ ์ฌ์ฉํด์ค๋๋ค.
from langchain_core.runnables.history import RunnableWithMessageHistory
chat_history_for_chain = ChatMessageHistory()
chain_with_message_history = RunnableWithMessageHistory(
chain, # ์คํํ Runnable ๊ฐ์ฒด
lambda session_id: chat_history_for_chain, # ์ธ์
๊ธฐ๋ก์ ๊ฐ์ ธ์ค๋ ํจ์
input_messages_key="input", # ์
๋ ฅ ๋ฉ์์ง์ Key
history_messages_key="chat_history", # ๋ํ ํ์คํ ๋ฆฌ ๋ฉ์์ง์ Key
)
RunnableWithMessageHistory ํด๋์ค์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
- chain : ์คํํ Runnable ๊ฐ์ฒด
- session_id : ์ธ์
๊ธฐ๋ก์ ๊ฐ์ ธ์ค๋ ํจ์, ํ์คํ ๋ฆฌ ์ ์ฅ๊ณต๊ฐ
- ๊ฐ์ session_id๋ฅผ ์ ๋ ฅํ๋ฉด ์ด์ด์ ๋ํ ๊ฐ๋ฅ
- input_messages_key : ์ ๋ ฅ ๋ฉ์์ง์ Key, ํ๋กฌํํธ์ ์ฌ์ฉ์ ์ ๋ ฅ์ ํด๋น ({input})
- history_messages_key : ๋ํ ํ์คํ ๋ฆฌ ๋ฉ์์ง์ Key, ํ๋กฌํํธ์ ๋ํ ํ์คํ ๋ฆฌ์ ํด๋น ({chat_history})
์์์ ๋งํ๋ฏ ํด๋น ๊ฐ์ฒด๋ Runnable ํ๊ธฐ ๋๋ฌธ์ ํ๋ผ๋ฏธํฐ๋ก ์์ฑํด์ค chain์ด ๋ฐ๋ก ์คํ๋๋๋ก invoke ๊ฐ ๊ฐ๋ฅํฉ๋๋ค. ๋จ, session_id์ chat_history_for_chain๊ณผ ๊ฐ์ด ๋ํ ํ์คํ ๋ฆฌ๋ฅผ ์ ๋ฐ์ดํธ ํด์ค ์ ์ฅ์๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ ฅํ์์ผ๋ฏ๋ก, invoke ์ ์๋์ผ๋ก ๋ํ ํ์คํ ๋ฆฌ๊ฐ ์ ์ฅ๋๋ฉฐ, history_messages_key ์ธ "chat_history" ๋ฅผ ํตํด ํ๋ผ๋ฏธํฐ ๋ด placeholder ๋ถ๋ถ์ผ๋ก ๋ํ ํ์คํ ๋ฆฌ๊ฐ ๋๊ฒจ์ง๋๋ค. ์ถ๊ฐ๋ก, ์ฌ๊ธฐ์๋ ์ฌ์ฉ์ ์ง๋ฌธ์ "input" ํค๋ก ๋ฐ๋ก ๋ฐ์ ํ๋กฌํํธ ๋ฐ ์ฑํ ๋ชจ๋ธ์ ์ ๋ ฅ๋๋๋ก ํฉ๋๋ค.
chain_with_message_history.invoke(
{"input": "์ผ๋ณธ์ด๋ก '์ ๋จน๊ฒ ์ต๋๋ค.' ์ด๋ป๊ฒ ๋งํด? ํ๊ธ ๋ฐ์๋ ํจ๊ป ๋งํด์ค."},
{"configurable": {"session_id": "unused"}},
)
output :
AIMessage(content='์ผ๋ณธ์ด๋ก \'์ ๋จน๊ฒ ์ต๋๋ค.\'๋ "ใใใ ใใพใ"๋ผ๊ณ ๋งํฉ๋๋ค. ํ๊ธ ๋ฐ์์ "์ดํ๋คํค๋ง์ค"์ ๋๋ค.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 34, 'prompt_tokens': 59, 'total_tokens': 93, 'completion_tokens_details': {'audio_tokens': None, 'reasoning_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_e5e4913e83', 'finish_reason': 'stop', 'logprobs': None}, id='run-6ea25a7f-eaa5-4809-81eb-57d41ad37427-0', usage_metadata={'input_tokens': 59, 'output_tokens': 34, 'total_tokens': 93, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})
chain_with_message_history.invoke(
{"input": "๋ด๊ฐ ๋ญ๋ผ๊ณ ๋ฌผ์ด๋ดค์ง?"}, {"configurable": {"session_id": "unused"}}
)
output :
AIMessage(content='๋น์ ์ "\'์ ๋จน๊ฒ ์ต๋๋ค.\'๋ฅผ ์ผ๋ณธ์ด๋ก ์ด๋ป๊ฒ ๋งํ๋์ง" ๋ฌผ์ด๋ณด์ จ์ต๋๋ค.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 23, 'prompt_tokens': 110, 'total_tokens': 133, 'completion_tokens_details': {'audio_tokens': None, 'reasoning_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_e5e4913e83', 'finish_reason': 'stop', 'logprobs': None}, id='run-50037728-4fa6-4506-ba54-a5d97a92e832-0', usage_metadata={'input_tokens': 110, 'output_tokens': 23, 'total_tokens': 133, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})
๋ณธ chain์ invoke ํ๋ฉด ์์ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์ผ๋ฉฐ, .content ๋ฅผ ํตํด ๋ชจ๋ธ ๋ต๋ณ์ ํ์ธํ ์ ์์ต๋๋ค. ๋ค์ ์ ๋ฆฌํ๋ฉด, ์์์ ๊ตฌํํ๋ ํ์คํ ๋ฆฌ๋ฅผ ์ ์ฅํ๋ ํจ์ ๋ฑ์ ๋ฐ๋ก ์ผ์ผํ ํธ์ถํ ํ์์์ด, invoke ์คํ๋ง์ผ๋ก ๋ํ๊ธฐ๋ก์ด ์๋์ผ๋ก ์ ์ฅ๋์์ต๋๋ค.
4. Modifying chat history
๋ง์ง๋ง์ผ๋ก, 3๋ฒ์งธ ๋ฐฉ๋ฒ์์ ์ฌ์ฉํ RunnableWithMessageHistory ํด๋์ค๋ฅผ ์ฌ์ฉํ๋, ๋ํ ํ์คํ ๋ฆฌ๋ฅผ ๊ทธ๋๋ก ๋๊ฒจ์ฃผ๋ ๊ฒ์ด ์๋, ๋ถํ์ํ๊ฑฐ๋ ๋๋ฌด ๋ง์ ํ ์คํธ๊ฐ ๋ต๋ณ์ ๋ฐฉํดํ์ง ์๋๋ก ์์ฝ์ ํ๋ฒ ํด์ฃผ๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํด๋ณด๊ฒ ์ต๋๋ค.
chat_history = ChatMessageHistory()
chat_history.add_user_message("์๋
ํ์ธ์. ์ ์ด๋ฆ์ ๋์ฐ์
๋๋ค.")
chat_history.add_ai_message("์๋
ํ์ธ์, ๋์ฐ๋! ๋ฌด์์ ๋์๋๋ฆด๊น์?")
chat_history.add_user_message("๋ ์จ ์ข์ ๋ ๋ค์๋ง ํ ๋
ธ๋ ์ถ์ฒํด์ฃผ์ธ์.")
chat_history.add_ai_message("Carpenters - Close to you ๋ฅผ ์ถ์ฒํด์.")
chat_history.messages
# chat_history.clear()
์์๋ฅผ ์ํด ๋ ธ๋ ์ถ์ฒ์ ๊ดํ ๋ํ๋ฅผ ๋๋์๋ค๊ณ ๊ฐ์ ํ์ต๋๋ค.
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful assistant. Answer all questions to the best of your ability. The provided chat history includes facts about the user you are speaking with. YOU MUST ANSWER IN KOREAN.",
),
("placeholder", "{chat_history}"),
("user", "{input}"),
]
)
chain = prompt | chat
chain_with_message_history = RunnableWithMessageHistory(
chain,
lambda session_id: chat_history,
input_messages_key="input",
history_messages_key="chat_history",
)
ํ๋กฌํํธ์ Chat๋ชจ๋ธ, chain์ผ๋ก ์ ์ํ RunnableWithMessageHistory ๊น์ง๋ ๋์ผํฉ๋๋ค.
+ ๋ํ ๋ด์ฉ ์์ฝ์ ์ํ Chain
์ฌ๊ธฐ๊น์ง๊ฐ ๋ต๋ณ์ ์ํ ์ฒด์ธ์ด์๋ค๋ฉด, ์ฌ๊ธฐ์ ๋ํ ํ์คํ ๋ฆฌ ๋ด์ฉ์ ์์ถํ๋ ์ฒด์ธ์ ์ถ๊ฐํ ๊ฒ์ ๋๋ค. ์๋๋ ์ด๊ฑธ ์ ๋ฆฌํ ๊ทธ๋ฆผ์ ๋๋ค.
from langchain_core.runnables import RunnablePassthrough
def summarize_messages(chain_input):
stored_messages = chat_history.messages
if len(stored_messages) == 0:
return False
summarization_prompt = ChatPromptTemplate.from_messages(
[
("placeholder", "{chat_history}"),
(
"user",
"Distill the above chat messages into a single summary message. Include as many specific details as you can.",
),
]
)
summarization_chain = summarization_prompt | chat
# chat_history ์ ์ ์ฅ๋ ๋ํ ๊ธฐ๋ก์ ์์ฝํ๋กฌํํธ์ ์
๋ ฅ & ๊ฒฐ๊ณผ ์ ์ฅ
summary_message = summarization_chain.invoke({"chat_history": stored_messages})
# chat_history ์ ์ ์ฅ๋์ด์๋ ๊ธฐ๋ก ์ง์ฐ๊ธฐ
chat_history.clear()
# ์์ฑ๋ ์๋ก์ด ์์ฝ๋ด์ฉ์ผ๋ก ๊ธฐ๋ก ์ฑ์ฐ๊ธฐ
chat_history.add_message(summary_message)
return True
๋ํ ๋ด์ฉ์ด ์ ์ฅ๋์ด ์๋ chat_history์ messages๋ฅผ ๊ฐ์ ธ์ ์์ฝ์ ๋ด๋นํ๋ ๋ชจ๋ธ์ ๋ฃ์ด ์์ฝ ๊ฒฐ๊ณผ๋ฅผ ์์ฑ๋ฐ๊ณ , ๊ธฐ์กด chat_history ๊ธฐ๋ก์ ์ญ์ ํ ์๋ก์ด ์์ฝ๋ด์ฉ์ผ๋ก ์ฑ์๋ฃ๋ ํจ์๊ฐ ๋ฐ๋ก summrize_messages ์ ๋๋ค. ์ด ๋ ๋ํ๊ธฐ๋ก์ด ์์ง ์๋ค๋ฉด ์คํ๋์ง ์์ต๋๋ค. (if๋ฌธ ์ฐธ๊ณ )
์ด ํจ์ ๋ํ chain์ผ๋ก ์ฐ๊ฒฐ์์ผ runnable ํ๊ฒ ๋ง๋ค์ด ์ค ๊ฑด๋ฐ์!
chain_with_summarization = (
RunnablePassthrough.assign(messages_summarized=summarize_messages)
| chain_with_message_history
)
๊ธฐ์กด์ ๋ต๋ณ์ ์ํ chain์ด์๋ chain_with_message_history ์คํ ์ , ์์ฝ ํจ์๋ฅผ ์คํํ๊ธฐ ์ํด RunnablePassthrough ๋ก ํด๋น ํจ์๋ฅผ Runnable ํ๊ฒ ๋ง๋ค์ด ์ค ํ chain์ผ๋ก ์ฐ๊ฒฐํ์ต๋๋ค.
๐คธโ๏ธ ์ดํด๋ฅผ ์ํ ๋ถ๊ฐ ์ค๋ช !
> summarize_messages ํจ์์ ์คํ ๊ฒฐ๊ณผ(True / False)๋ฅผ messages_summarized ๋ผ๋ ์ด๋ฆ์ Key๋ก ์ ์ฅํ๋๋ก ๋์ด์๋๋ฐ์. ์ค์ ๋ก ์ด Key๋ ์ ์๋์ด ์์ง ์์ผ๋ฉฐ, ์ด๋ summarize_messages ํจ์๋ฅผ ์คํํ๊ธฐ ์ํ ๋ฌด์ ๊ฐ๋ฅํ Key๋ก ์ดํดํ์๋ฉด ๋ฉ๋๋ค. ์ฆ, RunnablePassthough ๋ก summarize_messages ๊ฐ runnable ํ๊ฒ ์คํ๋์ด chat_history์ ์์ฝ๋ ํ์คํ ๋ฆฌ๊ฐ ์ ์ฅ๋์๋ค๋ฉด, ์ด ํ chain_with_message_history ๋ก ์ฐ๊ฒฐ๋ฉ๋๋ค.
chain_with_summarization.invoke(
{"input": "์ ์ด๋ฆ์ ๊ธฐ์ตํ๊ณ ์๋์?"},
{"configurable": {"session_id": "unused"}},
)
output :
AIMessage(content='๋ค, ๋์ฐ๋์ด๋ผ๊ณ ์๊ณ ์์ต๋๋ค. ๋ง๋์?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 12, 'prompt_tokens': 94, 'total_tokens': 106, 'completion_tokens_details': {'audio_tokens': None, 'reasoning_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_2f406b9113', 'finish_reason': 'stop', 'logprobs': None}, id='run-0e55a4c3-cc75-4b51-afc6-41fc57f30d60-0', usage_metadata={'input_tokens': 94, 'output_tokens': 12, 'total_tokens': 106, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})
chain_with_summarization.invoke(
{"input": "๊ทธ ๊ฐ์๋ ๋จ์์ธ๊ฐ์ ์ฌ์์ธ๊ฐ์?"},
{"configurable": {"session_id": "unused"}},
)
output :
AIMessage(content='Carpenters๋ ๋จ๋งค ๋์ค๋ก, ๋ฆฌ์ฒ๋ ์นดํํฐ์ ์นด๋ ์นดํํฐ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. ์นด๋ ์นดํํฐ๋ ์ฌ์ฑ ๋ณด์ปฌ๋ฆฌ์คํธ์ ๋๋ค.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 44, 'prompt_tokens': 109, 'total_tokens': 153, 'completion_tokens_details': {'audio_tokens': None, 'reasoning_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_e5e4913e83', 'finish_reason': 'stop', 'logprobs': None}, id='run-20fa421b-37ee-49f2-8af2-dbc6ea408fd6-0', usage_metadata={'input_tokens': 109, 'output_tokens': 44, 'total_tokens': 153, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})
chain ์คํ ๊ฒฐ๊ณผ, ์์ฝ๋ ๋ํ ํ์คํ ๋ฆฌ๋ก๋ ์ ๋ต๋ณ๋๋ ๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค!
๋ ์์ธํ ์ค๋ช ๊ณผ ๋ค์ํ ์์ , ๊ทธ๋ฆฌ๊ณ 4๋ฒ์งธ ๋ฐฉ๋ฒ์ ํ์ฉํ ๋๋ง์๊ธฐ ๊ฒ์ ์ฝ๋๋ ์๋ ์ ํ๋ธ์ ์ฝ๋ฉ ๋งํฌ๋ฅผ ํ์ธํด์ฃผ์ธ์!
https://colab.research.google.com/drive/1nqxKHz-YOJWBFnvSLvwm17oAI2oV5LkP?usp=sharing