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

[langgraph] 대화 이력을 관리하는 방법

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

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

영속성의 가장 일반적인 사용 사례 중 하나는 대화 기록을 추적하는 것이다. 즉, 대화를 쉽게 이어갈 수 있게 한다. 그러나 대화가 길어지면 대화 기록이 쌓여서 컨텍스트 공간을 점점 더 많이 차지하게 된다. 간혹 의도하지 않게 동작하며, LLM에 대한 호출이 더 비싸지고 길어지며, 오류가 발생할 수도 있다. 이런 일이 발생하지 않도록 하려면 대화 기록을 적절하게 관리해야 한다.

참고: 이 가이드는 LangGraph에서 이를 어떻게 처리할 수 있는지에 대해 설명한다. LangGraph에서는 완전히 커스트마이징할 수 있다. 만약 즉시 사용할 수 있는 방법을 원한다면, LangChain에서 제공하는 기능을 살펴볼 수 있다.

준비

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

pip install langgraph langchain_openai

agent 생성

간단한 ReAct 스타일 에이전트를 만들자.

from dotenv import load_dotenv
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import MessagesState, StateGraph, START, END
from langgraph.prebuilt import ToolNode

load_dotenv()

memory = MemorySaver()


@tool
def search(query: str):
    """Call to surf the web."""
    return "It's sunny in San Francisco, but you better look out if you're a Gemini 😈."


tools = [search]
tool_node = ToolNode(tools)
model = ChatOpenAI(model_name="gpt-4o-mini")
bound_model = model.bind_tools(tools)


def should_continue(state: MessagesState):
    """Return the next node to execute."""
    last_message = state["messages"][-1]
    # 만일 함수 호출이 없다면, 끝낸다.
    if not last_message.tool_calls:
        return END
    # 만일 함수 호출이 있다면, 계속한다.
    return "action"


# 모델을 호출하는 함수를 정의한다.
def call_model(state: MessagesState):
    response = bound_model.invoke(state["messages"])
    # 리스트를 반환한다. 기존 리스트에 추가될 것이기 때문이다.
    return {"messages": response}


workflow = StateGraph(MessagesState)

# 두 개의 노드가 서로를 순환하도록 정의한다.
workflow.add_node("agent", call_model)
workflow.add_node("action", tool_node)

# 진입점을 'agent'로 설정한다.
# 이것은 이 노드가 처음으로 호출되는 것을 의미한다.
workflow.add_edge(START, "agent")

# 조건부 엣지를 추가한다.
workflow.add_conditional_edges(
    # 우선, 시작 노드를 정의한다. 'agent'를 사용한다.
    # 이것은 'agent' 노드가 호출된 후에 호출되는 엣지를 의미한다.
    "agent",
    # 다음으로, 다음에 호출될 노드를 결정할 함수를 전달한다.
    should_continue,
    # 다음으로, 경로 맵을 전달한다. 이 엣지가 갈 수 있는 모든 노드들이다.
    ["action", END],
)

# tools에서 agent로의 일반 엣지를 추가한다.
# 이것은 tools가 호출된 후에 agent 노드가 호출된다는 것을 의미한다.
workflow.add_edge("action", "agent")

# 마지막으로, 컴파일한다!
# 이것은 LangChain Runnable로 컴파일된다.
# 이것은 다른 Runnable처럼 사용할 수 있다는 것을 의미한다.
app = workflow.compile(checkpointer=memory)
from langchain_core.messages import HumanMessage

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


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

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

안녕하세요, 홍길동님! 어떻게 도와드릴까요?
================================ Human Message =================================

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

당신의 이름은 홍길동입니다!

메시지 필터링

대화 기록이 지나치게 커지는 것을 방지하기 위한 간단한 방법은 LLM에 전달되기 전에 메시지 목록을 필터링하는 것이다. 이는 두 가지 부분으로 나눌 수 있다. 메시지를 필터링하는 함수 정의와 이를 그래프에 추가하는 것이다. 아래 예시에서는 매우 간단한 filter_messages 함수를 정의하고 이를 사용하는 방법을 보여준다.

from dotenv import load_dotenv
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import MessagesState, StateGraph, START, END
from langgraph.prebuilt import ToolNode

load_dotenv()

memory = MemorySaver()


@tool
def search(query: str):
    """Call to surf the web."""
    return "It's sunny in San Francisco, but you better look out if you're a Gemini 😈."


tools = [search]
tool_node = ToolNode(tools)
model = ChatOpenAI(model_name="gpt-4o-mini")
bound_model = model.bind_tools(tools)


def should_continue(state: MessagesState):
    """Return the next node to execute."""
    last_message = state["messages"][-1]
    # 만일 함수 호출이 없다면, 끝낸다.
    if not last_message.tool_calls:
        return END
    # 만일 함수 호출이 있다면, 계속한다.
    return "action"


def filter_messages(messages: list):
    # 마지막 메시지만 사용하는 매우 간단한 도우미 함수이다.
    return messages[-1:]


# 모델을 호출하는 함수를 정의한다.
def call_model(state: MessagesState):
    messages = filter_messages(state["messages"])
    response = bound_model.invoke(messages)
    # 리스트를 반환한다. 기존 리스트에 추가될 것이기 때문이다.
    return {"messages": response}


workflow = StateGraph(MessagesState)

# 두 개의 노드가 서로를 순환하도록 정의한다.
workflow.add_node("agent", call_model)
workflow.add_node("action", tool_node)

# 진입점을 'agent'로 설정한다.
# 이것은 이 노드가 처음으로 호출되는 것을 의미한다.
workflow.add_edge(START, "agent")

# 조건부 엣지를 추가한다.
workflow.add_conditional_edges(
    # 우선, 시작 노드를 정의한다. 'agent'를 사용한다.
    # 이것은 'agent' 노드가 호출된 후에 호출되는 엣지를 의미한다.
    "agent",
    # 다음으로, 다음에 호출될 노드를 결정할 함수를 전달한다.
    should_continue,
    # 다음으로, 경로 맵을 전달한다. 이 엣지가 갈 수 있는 모든 노드들이다.
    ["action", END],
)

# tools에서 agent로의 일반 엣지를 추가한다.
# 이것은 tools가 호출된 후에 agent 노드가 호출된다는 것을 의미한다.
workflow.add_edge("action", "agent")

# 마지막으로, 컴파일한다!
# 이것은 LangChain Runnable로 컴파일된다.
# 이것은 다른 Runnable처럼 사용할 수 있다는 것을 의미한다.
app = workflow.compile(checkpointer=memory)
from langchain_core.messages import HumanMessage

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


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

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

안녕하세요, 홍길동님! 어떻게 도와드릴까요?
================================ Human Message =================================

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

죄송하지만, 당신의 이름을 알 수 없습니다. 어떻게 도와드릴까요?

위 예시에서는 filter_messages 함수를 직접 정의했다. 또한 LangChain에서는 메시지를 자르고 필터링하는 즉시 사용할 수 있는 방법을 제공한다.

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유