Study/AI

[LangChain] RunnableWithMessageHistory, BaseChatMessageHistory를 사용해 메시지 히스토리 관리하기

hongeeii 2025. 8. 11.
728x90
반응형
 
  • 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는 메시지를 기록할 수 있는 추상클래스이다.

BaseChatMessageHistory Class hierarchy:

BaseChatMessageHistory   # 추상 클래스(인터페이스 역할)
   ├── InMemoryChatMessageHistory   # 메모리에 저장 (가장 간단, 세션 끝나면 사라짐)
   ├── FileChatMessageHistory       # 로컬 파일에 저장
   ├── PostgresChatMessageHistory   # PostgreSQL DB에 저장
   ├── RedisChatMessageHistory      # Redis에 저장
   ├── CassandraChatMessageHistory  # Cassandra에 저장
   └── ... (다른 저장소 구현체들)
 

RunnableWithMessageHistory

  • 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를 바꿔가며 대화해보기

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
)

=> '죄송하지만, 저는 개인적인 일정을 기억하거나 추적할 수 없습니다. 내일 계획에 대해 말해주시면 도와드릴 수 있는 부분이 있을지도 모릅니다!'
 
728x90
반응형

'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

추천 글