[LangChain] RunnableWithMessageHistory, BaseChatMessageHistory를 사용해 메시지 히스토리 관리하기
- BaseChatMessageHistory
- RunnableWithMessageHistory
- session_id 바꿔서 테스트해보기
LLM과 대화를 나누기 위해서 이전의 대화를 SystemMessage, HumanMessage, AIMessage로 계속 기억해서 보내야했는데 이걸 더 편하게 관리해줄 수 있는걸 메시지 히스토리라고 한다.
- message와 chain 정의
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
chain = llm | parser
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
messages = [
SystemMessage(content="사용자의 질문에 2문장 이내로 짧게 대답해"),
HumanMessage(content="오늘은 피자를 먹어야지!"),
AIMessage(content="정말 좋은 생각이야. 음료는 무엇으로 할 거야?"),
HumanMessage(content="내일은 수영을 가야지!"),
AIMessage(content="수영이라니, 정말 좋은 운동이야. 수영장은 어디로 다녀?"),
HumanMessage(content="주말에는 영화를 보러 갈 거야!"),
AIMessage(content="주말이 벌써부터 기다려지겠는걸? 보려고 생각해둔 영화가 있어?"),
]
BaseChatMessageHistory는 메시지를 기록할 수 있는 추상클래스이다.
BaseChatMessageHistory Class hierarchy:
BaseChatMessageHistory # 추상 클래스(인터페이스 역할)
├── InMemoryChatMessageHistory # 메모리에 저장 (가장 간단, 세션 끝나면 사라짐)
├── FileChatMessageHistory # 로컬 파일에 저장
├── PostgresChatMessageHistory # PostgreSQL DB에 저장
├── RedisChatMessageHistory # Redis에 저장
├── CassandraChatMessageHistory # Cassandra에 저장
└── ... (다른 저장소 구현체들)
- RunnableWithMessageHistory 객체 만들고 메모리에 대화 저장하기
RunnableWithMessageHistory는 메시지기록을 관리해줄수 있는 wrapper객체이다. 매개변수로는 추상클래스인 BaseChatMessageHistory를 반환해주는 callable객체를 넣어줄 수 있다. > GetSessionHistoryCallable = Callable[..., BaseChatMessageHistory]
from langchain_core.chat_history import BaseChatMessageHistory, InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
store = {} # 특정 대화를 기록하기 위해 사용
def get_session_history(sessionId: str) -> BaseChatMessageHistory:
if sessionId not in store:
store[sessionId] = InMemoryChatMessageHistory()
return store[sessionId]
with_message_history = RunnableWithMessageHistory(chain, get_session_history)
config = {
"configurable": {
"session_id": "chat"
}
}
config에 dict형태로 configurable을 왜 넣는지?
class RunnableConfig(TypedDict, total=False):
configurable: dict[str, Any]
configurable = config.get("configurable", {})
위 형태로 RunnableConfig가 정의되어있음
session_id는 왜 필요할까?
사용자가 앱에 와서 LLM이랑 채팅을 한다고 했을 때, 여러명이 할텐데 각각의 대화내역이 분리돼야 하니까 sotre 딕셔너리는 a의 대화내역 b의 대화내역 이렇게 따로 관리를 한다.
그래서 이런 구조가 된 것. 그래서 sesstion_id를 호출해서 사용한다. 라고 이해하면 된다.
with_message_history.invoke(messages +
[HumanMessage(content="주말에 내가 뭐하러 간다고 했지?")],
config=config
)
=>'주말에 영화를 보러 간다고 했어! 어떤 영화 볼 계획이야?'
RunnableWithMessageHistory에 messages를 넣지 않아도 기억을 하고 있다.
with_message_history.invoke(
[HumanMessage(content="방금 내가 무슨 질문을 했지?")],
config=config
)
=> '주말에 네가 무엇을 하러 간다고 했는지 물어봤어.'
debug모드로 확인하기
import langchain
langchain.debug = True
with_message_history.invoke(
[HumanMessage(content="수영은 언제가기로 했지?")],
config=config
)
=> [chain/start] [chain:RunnableWithMessageHistory] Entering Chain run with input:
[inputs]
[chain/start] [chain:RunnableWithMessageHistory > chain:load_history] Entering Chain run with input:
[inputs]
[chain/end] [chain:RunnableWithMessageHistory > chain:load_history] [0ms] Exiting Chain run with output:
[outputs]
[chain/start] [chain:RunnableWithMessageHistory > chain:check_sync_or_async] Entering Chain run with input:
[inputs]
[chain/start] [chain:RunnableWithMessageHistory > chain:check_sync_or_async > chain:RunnableSequence] Entering Chain run with input:
[inputs]
[llm/start] [chain:RunnableWithMessageHistory > chain:check_sync_or_async > chain:RunnableSequence > llm:ChatOpenAI] Entering LLM run with input:
{
"prompts": [
"System: 사용자의 질문에 2문장 이내로 짧게 대답해\nHuman: 오늘은 피자를 먹어야지!\nAI: 정말 좋은 생각이야. 음료는 무엇으로 할 거야?\nHuman: 내일은 수영을 가야지!\nAI: 수영이라니, 정말 좋은 운동이야. 수영장은 어디로 다녀?\nHuman: 주말에는 영화를 보러 갈 거야!\nAI: 주말이 벌써부터 기다려지겠는걸? 보려고 생각해둔 영화가 있어?\nHuman: 주말에 내가 뭐하러 간다고 했지?\nAI: 주말에 영화를 보러 간다고 했어! 어떤 영화 볼 계획이야?\nHuman: 방금 내가 무슨 질문을 했지?\nAI: 주말에 네가 무엇을 하러 간다고 했는지 물어봤어.\nHuman: 수영은 언제가기로 했지?\nAI: 수영은 내일 가기로 했다고 했어. 잘 다녀와!\nHuman: 수영은 언제가기로 했지?"
]
}
[llm/end] [chain:RunnableWithMessageHistory > chain:check_sync_or_async > chain:RunnableSequence > llm:ChatOpenAI] [1.13s] Exiting LLM run with output:
{
"generations": [
[
{
"text": "내일 수영을 가기로 했다고 했어.",
"generation_info": {
"finish_reason": "stop",
"logprobs": null
...
[chain/end] [chain:RunnableWithMessageHistory] [1.14s] Exiting Chain run with output:
{
"output": "내일 수영을 가기로 했다고 했어."
}
session_id는 대화방번호같은 개념이다.
RunnableWithMessageHistory를 사용할 때 session_id을 키로 사용해 해당되는 대화를 저장하게 된다. 예시를 확인해보자.
config_01 = {
"configurable": {
"session_id": "chat_01"
}
}
with_message_history.invoke(
[HumanMessage(content="내일은 수영을가야지!")],
config=config_01
)
=>'좋은 계획이네요! 수영은 체력에도 좋고 기분 전환에도 도움이 됩니다. 어떤 수영 연습을 할 계획이신가요?'
with_message_history.invoke(
[HumanMessage(content="내일 뭐하러 간다고 했지?")],
config=config_01
)
=> '내일 수영을 가기로 했다고 하셨죠! 수영 즐겁게 하세요!'
대화방 config_02를 만들어주고 대화를 시도했지만 기억을 하지 못한다.
config_02= {
"configurable": {
"session_id": "chat_02"
}
}
with_message_history.invoke(
[HumanMessage(content="내일 뭐하러 간다고 했지?")],
config=config_02
)
=> '죄송하지만, 저는 개인적인 일정을 기억하거나 추적할 수 없습니다. 내일 계획에 대해 말해주시면 도와드릴 수 있는 부분이 있을지도 모릅니다!'
'Study > AI' 카테고리의 다른 글
[LangChain] debug모드로 메모리 관리 실습하기 (3) | 2025.08.10 |
---|---|
[LangChain] 메모리 관리 (0) | 2025.08.10 |
[LangChain] 메모리 (4) | 2025.08.10 |
[LangChain] Parser (5) | 2025.08.10 |
[LangChain] PromptTemplate (1) | 2025.08.10 |
댓글