langgraph / / 2024. 11. 29. 17:28

[langgraph] 대화 이력을 요약하는 방법

LangGraph 공식문서를 번역한 내용입니다. 필요한 경우 부연 설명을 추가하였고 이해하기 쉽게 예제를 일부 변경하였습니다. 문제가 되면 삭제하겠습니다.

https://langchain-ai.github.io/langgraph/how-tos/memory/add-summary-conversation-history/

영속성의 가장 일반적인 사용 사례 중 하나는 대화 기록을 추적하는 것이다. 즉, 대화를 쉽게 이어갈 수 있게 한다. 그러나 대화가 길어지면 대화 기록이 쌓여서 컨텍스트 공간을 점점 더 많이 차지하게 된다. 이는 간혹 의도하지 않게 동작하며, LLM에 대한 호출이 더 비싸지고 길어지며, 오류가 발생할 수도 있다. 이를 해결하는 방법 중 하나는 지금까지의 대화 요약을 작성하고, 그것을 지난 N개의 메시지와 함께 사용하는 것이다. 이 가이드에서는 그 방법에 대한 예시를 다룰 것이다.

이 과정은 몇 가지 단계로 이루어진다.

  • 대화가 너무 긴지 확인 (메시지 수 또는 메시지 길이를 확인하여 할 수 있음)
  • 만일 너무 길다면, 요약을 생성 (이를 위한 프롬프트가 필요)
  • 그런 다음 마지막 N개의 메시지를 제외한 모든 메시지 삭제

이 과정에서 중요한 부분은 오래된 메시지를 삭제하는 것이다. 이를 수행하는 방법에 대한 자세한 가이드는 이 가이드를 참고하라.

준비

필요한 패키지를 설치하자.

pip install langgraph langchain_openai

챗봇 생성

이제 챗봇을 만들어보자.

from typing import Literal

from dotenv import load_dotenv
from langchain_core.messages import SystemMessage, RemoveMessage, HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import MessagesState, StateGraph, START, END

load_dotenv()

memory = MemorySaver()


# summary 속성을 추가한다. (MessagesState가 이미 가지고 있는 `messages` 키에 추가로)
class State(MessagesState):
    summary: str


model = ChatOpenAI(model_name="gpt-4o-mini")


# 모델을 호출하는 로직을 정의한다.
def call_model(state: State):
    # 요약이 있다면, 시스템 메시지로 추가한다.
    summary = state.get("summary", "")
    if summary:
        system_message = f"Summary of conversation earlier: {summary}"
        messages = [SystemMessage(content=system_message)] + state["messages"]
    else:
        messages = state["messages"]
    response = model.invoke(messages)

    return {"messages": [response]}


# 대화를 요약할지 끝낼지를 결정하는 로직을 정의한다.
def should_continue(state: State) -> Literal["summarize_conversation", END]:
    """Return the next node to execute."""
    messages = state["messages"]
    # 6개 이상의 메시지가 있다면, 대화를 요약한다.
    if len(messages) > 6:
        return "summarize_conversation"
    # 그렇지 않다면, 끝낸다.
    return END


def summarize_conversation(state: State):
    # 우선, 대화를 요약한다.
    summary = state.get("summary", "")
    if summary:
        # 이미 요약이 있다면, 요약하기 위해 다른 시스템 프롬프트를 사용한다.
        summary_message = (
            f"This is summary of the conversation to date: {summary}\n\n"
            "Extend the summary by taking into account the new messages above:"
        )
    else:
        summary_message = "Create a summary of the conversation above in Korean:"

    messages = state["messages"] + [HumanMessage(content=summary_message)]
    response = model.invoke(messages)
    # 더 이상 표시하고 싶지 않은 메시지를 삭제해야 한다.
    # 지난 두 메시지를 제외한 모든 메시지를 삭제할 것이다. 하지만 변경할 수도 있다.
    delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]]
    return {"summary": response.content, "messages": delete_messages}


workflow = StateGraph(State)

# 대화 노드와 요약 노드를 정의한다.
workflow.add_node("conversation", call_model)
workflow.add_node(summarize_conversation)

# 대화를 시작하는 노드로 설정한다.
workflow.add_edge(START, "conversation")

# 조건부 엣지를 추가한다.
workflow.add_conditional_edges(
    # 우선, 시작 노드를 정의한다. 'conversation'을 사용한다.
    # 'conversation' 노드가 호출된 후에 호출되는 엣지를 의미한다.
    "conversation",
    # 다음으로, 다음에 호출될 노드를 결정할 함수를 전달한다.
    should_continue,
)

# summarize_conversation에서 END로의 일반 엣지를 추가한다.
# 이것은 summarize_conversation이 호출된 후에 끝난다는 것을 의미한다.
workflow.add_edge("summarize_conversation", END)

app = workflow.compile(checkpointer=memory)

그래프 사용

def print_update(update):
    for k, v in update.items():
        for m in v["messages"]:
            m.pretty_print()
        if "summary" in v:
            print(v["summary"])
from langchain_core.messages import HumanMessage

config = {"configurable": {"thread_id": "4"}}
input_message = HumanMessage(content="안녕! 내 이름은 홍길동이야")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
    print_update(event)

input_message = HumanMessage(content="내 이름이 뭐야?")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
    print_update(event)

input_message = HumanMessage(content="나는 토트넘을 좋아해")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
    print_update(event)
================================ Human Message =================================

안녕! 내 이름은 홍길동이야
================================== Ai Message ==================================

안녕하세요, 홍길동님! 만나서 반갑습니다. 어떻게 도와드릴까요?
================================ Human Message =================================

내 이름이 뭐야?
================================== Ai Message ==================================

당신의 이름은 홍길동입니다. 맞나요?
================================ Human Message =================================

나는 토트넘을 좋아해
================================== Ai Message ==================================

토트넘 홋스퍼를 좋아하시는군요! 팀의 어떤 점이 가장 마음에 드세요? 선수들, 경기 스타일, 아니면 팬 문화 같은 것들이요?

지금까지 요약이 발생하지 않은 이유는 목록에 메시지가 6개뿐이기 때문이다.

values = app.get_state(config).values
print(values)
{
  'messages': [
    HumanMessage(content=
    '안녕! 내 이름은 홍길동이야',
    ...)
    HumanMessage(content=
    '내 이름이 뭐야?',
    ...),
    HumanMessage(content=
    '나는 토트넘을 좋아해',
    ...),
    AIMessage(content=
    '토트넘 홋스퍼를 좋아하시군요! 팀에 대해 어떤 점이 가장 마음에 드세요? 선수나 경기, 아니면 팬 문화에 대해 이야기해 볼까요?',
    ...)
  ]
}

이제 메시지를 하나 더 보내자.

input_message = HumanMessage(content="나는 그들이 경기를 이기는 것을 좋아해")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
    print_update(event)
================================ Human Message =================================

나는 그들이 경기를 이기는 것을 좋아해
================================== Ai Message ==================================

그렇죠! 경기를 이길 때의 짜릿함과 기쁨은 정말 특별하죠. 토트넘은 언제나 팬들을 즐겁게 해주는 멋진 경기를 보여주려고 노력하죠. 최근에 기억에 남는 경기나 선수는 누구인가요?
================================ Remove Message ================================


================================ Remove Message ================================


================================ Remove Message ================================


================================ Remove Message ================================


================================ Remove Message ================================


================================ Remove Message ================================


대화 요약:
사용자는 자신의 이름이 홍길동이라고 소개하며, 토트넘 핫스퍼를 좋아한다고 말했습니다. 그들은 토트넘이 경기를 이기는 것을 좋아한다고 표현했습니다. 이어서 대화 상대는 토트넘의 매력과 팬으로서의 기쁨에 대해 이야기하며, 최근 기억에 남는 경기나 선수를 물어보았습니다.

지금 상태를 확인하면, 대화의 요약과 함께 마지막 두 개의 메시지가 있음을 알 수 있다.

values = app.get_state(config).values
print(values)
{
  'messages': [
    HumanMessage(content=
    '나는 그들이 경기를 이기는 것을 좋아해',
    ...),
    AIMessage(content=
    '그렇죠! 경기를 이길 때의 짜릿함과 기쁨은 정말 특별하죠. 토트넘은 언제나 팬들을 즐겁게 해주는 멋진 경기를 보여주려고 노력하죠. 최근에 기억에 남는 경기나 선수는 누구인가요?',
    ...)
  ],
  'summary': '대화 요약:\n사용자는 자신의 이름이 홍길동이라고 소개하며, 토트넘 핫스퍼를 좋아한다고 말했습니다. 그들은 토트넘이 경기를 이기는 것을 좋아한다고 표현했습니다. 이어서 대화 상대는 토트넘의 매력과 팬으로서의 기쁨에 대해 이야기하며, 최근 기억에 남는 경기나 선수를 물어보았습니다.'
}

이제 대화를 계속할 수 있다. 마지막 두 개의 메시지만 있어도, 이전 대화에서 언급된 내용에 대해 여전히 질문할 수 있다 (왜냐하면 그것들을 요약했기 때문이다).

input_message = HumanMessage(content="내 이름이 뭐야?")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
    print_update(event)
================================ Human Message =================================

내 이름이 뭐야?
================================== Ai Message ==================================

당신의 이름은 홍길동입니다! 계속해서 이야기하고 싶은 내용이 있으신가요?
input_message = HumanMessage(content="내가 어느 야구팀을 좋아한다고 생각하니?")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
    print_update(event)
================================ Human Message =================================

내가 어느 야구팀을 좋아한다고 생각하니?
================================== Ai Message ==================================

야구팀에 대한 언급이 없어서 정확히 알 수는 없지만, 혹시 좋아하는 팀이 있나요? 그 팀에 대해 이야기해주시면 좋을 것 같아요!
input_message = HumanMessage(content="나는 삼성 라이온즈를 좋아해")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
    print_update(event)
================================ Human Message =================================

나는 삼성 라이온즈를 좋아해
================================== Ai Message ==================================

삼성 라이온즈를 좋아하시는군요! 삼성 라이온즈는 한국 프로야구에서 매우 인기 있는 팀이죠. 어떤 점이 그 팀을 좋아하게 만들었나요? 특히 기억에 남는 경기나 선수도 있나요?
================================ Remove Message ================================


================================ Remove Message ================================


================================ Remove Message ================================


================================ Remove Message ================================


================================ Remove Message ================================


================================ Remove Message ================================


대화 요약: 사용자(홍길동)는 삼성 라이온즈를 좋아한다고 밝혔으며, 그들이 경기를 이기는 것을 좋아한다고 언급했다. AI는 사용자에게 어떤 점이 마음에 드는지, 기억에 남는 경기나 특별히 좋아하는 선수에 대해 질문했다. 사용자는 삼성 라이온즈에 대한 관심을 표현하며, 추가적인 이야기나 세부 정보를 나누지 않았다.
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유