langgraph / / 2024. 12. 2. 06:50

[langgraph] ToolNode를 사용하여 도구(tool)를 호출하는 방법

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

https://langchain-ai.github.io/langgraph/how-tos/tool-calling/

이 가이드는 LangGraph의 사전 구축된 ToolNode를 도구 호출에 사용하는 방법을 다룬다.

ToolNode는 그래프 상태(메시지 목록 포함)를 입력으로 받아 도구 호출 결과를 포함한 상태 업데이트를 출력하는 LangChain Runnable이다. 이 노드는 LangGraph의 사전 구축된 ReAct 에이전트와 잘 작동하도록 설계되었지만, 상태에 적절한 리듀서(예: MessagesState)가 있는 메시지 키가 포함된 모든 StateGraph와도 함께 사용할 수 있다.

준비

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

pip install langgraph langchain_openai

도구 정의

from langchain_core.messages import AIMessage
from langchain_core.tools import tool

from langgraph.prebuilt import ToolNode
@tool
def get_weather(location: str):
    """Call to get the current weather."""
    if location in ["서울", "인천"]:
        return "현재 기온은 20도이고 구름이 많아."
    else:
        return "현재 기온은 30도이며 맑아"


@tool
def get_coolest_cities():
    """Get a list of coolest cities"""
    return "서울, 인천"
tools = [get_weather, get_coolest_cities]
tool_node = ToolNode(tools)

수동으로 ToolNode 호출

ToolNode는 메시지 목록을 포함한 그래프 상태에서 작동한다. 이 노드는 목록의 마지막 메시지가 tool_calls 파라미터를 가진 AIMessage일 것으로 기대한다.

먼저, 도구 노드를 수동으로 호출하는 방법을 살펴보자.

message_with_single_tool_call = AIMessage(
    content="",
    tool_calls=[
        {
            "name": "get_weather",
            "args": {"location": "서울"},
            "id": "tool_call_id",
            "type": "tool_call",
        }
    ],
)

result = tool_node.invoke({"messages": [message_with_single_tool_call]})
print(result)
{'messages': [ToolMessage(content='현재 기온은 20도이고 구름이 많아.', name='get_weather', tool_call_id='tool_call_id')]}

일반적으로 AIMessage를 수동으로 생성할 필요는 없으며, 도구 호출을 지원하는 LangChain의 채팅 모델이 이를 자동으로 생성한다.

또한 AIMessagetool_calls 파라미터에 여러 도구 호출을 전달하면 ToolNode를 사용하여 병렬 도구 호출을 할 수 있다.

message_with_multiple_tool_calls = AIMessage(
    content="",
    tool_calls=[
        {
            "name": "get_coolest_cities",
            "args": {},
            "id": "tool_call_id_1",
            "type": "tool_call",
        },
        {
            "name": "get_weather",
            "args": {"location": "서울"},
            "id": "tool_call_id_2",
            "type": "tool_call",
        },
    ],
)

result = tool_node.invoke({"messages": [message_with_multiple_tool_calls]})
print(result)
{'messages': [ToolMessage(content='서울, 인천', name='get_coolest_cities', tool_call_id='tool_call_id_1'), ToolMessage(content='현재 기온은 20도이고 구름이 많아.', name='get_weather', tool_call_id='tool_call_id_2')]}

챗 모델 사용

예제에서는 OpenAI의 채팅 모델을 사용한다. 도구 호출이 가능한 채팅 모델을 사용하려면 먼저 모델이 사용 가능한 도구를 인식하도록 해야 한다. 이를 위해 ChatOpenAI 모델에서 .bind_tools 메서드를 호출한다.

from dotenv import load_dotenv
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import ToolNode

load_dotenv()


@tool
def get_weather(location: str):
    """Call to get the current weather."""
    if location in ["서울", "인천"]:
        return "현재 기온은 20도이고 구름이 많아."
    else:
        return "현재 기온은 30도이며 맑아"


@tool
def get_coolest_cities():
    """Get a list of coolest cities"""
    return "서울, 인천"


tools = [get_weather, get_coolest_cities]
tool_node = ToolNode(tools)

model_with_tools = ChatOpenAI(model="gpt-4o-mini", temperature=0).bind_tools(tools)
result = model_with_tools.invoke("서울 날씨 어때?").tool_calls
print(result)
[{'name': 'get_weather', 'args': {'location': '서울'}, 'id': 'call_Ri016cJtayiot5WbA6SfKgRU', 'type': 'tool_call'}]

보는바와 같이, 채팅 모델에서 생성된 AI 메시지는 이미 tool_calls가 채워져 있으므로 이를 그대로 ToolNode에 전달할 수 있다.

result = tool_node.invoke({"messages": [model_with_tools.invoke("서울 날씨 어때?")]})
print(result)
{'messages': [ToolMessage(content='현재 기온은 20도이고 구름이 많아.', name='get_weather', tool_call_id='call_fXsYbdTIQmi9sCpL9FUZe1x3')]}

ReAct Agent

다음으로, LangGraph 그래프 내에서 ToolNode를 사용하는 방법을 살펴보자. 먼저 ReAct 에이전트의 그래프 구현을 설정해 보자. 이 에이전트는 일부 쿼리를 입력받아 충분한 정보를 얻을 때까지 도구를 반복적으로 호출하여 쿼리를 해결한다. 우리는 방금 정의한 도구들과 함께 ToolNode와 OpenAI 모델을 사용할 것이다.

from dotenv import load_dotenv
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode

load_dotenv()


@tool
def get_weather(location: str):
    """Call to get the current weather."""
    if location in ["서울", "인천"]:
        return "현재 기온은 20도이고 구름이 많아."
    else:
        return "현재 기온은 30도이며 맑아"


@tool
def get_coolest_cities():
    """Get a list of coolest cities"""
    return "서울, 인천"


tools = [get_weather, get_coolest_cities]
tool_node = ToolNode(tools)
model_with_tools = ChatOpenAI(model="gpt-4o-mini", temperature=0).bind_tools(tools)


def should_continue(state: MessagesState):
    messages = state["messages"]
    last_message = messages[-1]
    if last_message.tool_calls:
        return "tools"
    return END


def call_model(state: MessagesState):
    messages = state["messages"]
    response = model_with_tools.invoke(messages)
    return {"messages": [response]}


workflow = StateGraph(MessagesState)

workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)

workflow.add_edge(START, "agent")
workflow.add_conditional_edges("agent", should_continue, ["tools", END])
workflow.add_edge("tools", "agent")

app = workflow.compile()
from IPython.display import Image, display

try:
    display(
        Image(
            app.get_graph().draw_mermaid_png(
                output_file_path="how-to-call-tools-using-toolnode-with-chatmodel.png"
            )
        )
    )
except Exception:
    pass

시작해보자.

for chunk in app.stream(
    {"messages": [("human", "서울 날씨 어때?")]}, stream_mode="values"
):
    chunk["messages"][-1].pretty_print()
================================ Human Message =================================

서울 날씨 어때?
================================== Ai Message ==================================
Tool Calls:
  get_weather (call_94wPLRZA6ekh2tYM30VFJMTY)
 Call ID: call_94wPLRZA6ekh2tYM30VFJMTY
  Args:
    location: 서울
================================= Tool Message =================================
Name: get_weather

현재 기온은 20도이고 구름이 많아.
================================== Ai Message ==================================

현재 서울의 날씨는 기온 20도이며, 구름이 많습니다.
for chunk in app.stream(
    {"messages": [("human", "가장 추운 도시 날씨 어때?")]},
    stream_mode="values",
):
    chunk["messages"][-1].pretty_print()
================================ Human Message =================================

가장 추운 도시 날씨 어때?
================================== Ai Message ==================================
Tool Calls:
  get_coolest_cities (call_N6AYNe0j9SRwfVZDVnPuUhq6)
 Call ID: call_N6AYNe0j9SRwfVZDVnPuUhq6
  Args:
================================= Tool Message =================================
Name: get_coolest_cities

서울, 인천
================================== Ai Message ==================================
Tool Calls:
  get_weather (call_xSXsCQowPtOKcHNk2n5keQrH)
 Call ID: call_xSXsCQowPtOKcHNk2n5keQrH
  Args:
    location: 서울
  get_weather (call_rJcfhDHX7N8yNZB0T7f37mll)
 Call ID: call_rJcfhDHX7N8yNZB0T7f37mll
  Args:
    location: 인천
================================= Tool Message =================================
Name: get_weather

현재 기온은 20도이고 구름이 많아.
================================== Ai Message ==================================

가장 추운 도시인 서울과 인천의 날씨는 현재 기온이 20도입니다. 날씨가 따뜻하네요!

ToolNode는 도구 실행 중 발생할 수 있는 오류를 처리할 수도 있다. 이를 활성화하거나 비활성화하려면 handle_tool_errors=True로 설정하면 되며, 기본값은 활성화되어 있다. 오류 처리에 대한 자세한 내용은 여기에서 확인할 수 있다.

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