{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# How to call tools using ToolNode\n",
    "\n",
    "This guide covers how to use LangGraph's prebuilt [`ToolNode`](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode) for tool calling.\n",
    "\n",
    "`ToolNode` is a LangChain Runnable that takes graph state (with a list of messages) as input and outputs state update with the result of tool calls. It is designed to work well out-of-box with LangGraph's prebuilt [ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent/), but can also work with any `StateGraph` as long as its state has a `messages` key with an appropriate reducer (see [`MessagesState`](https://github.com/langchain-ai/langgraph/blob/e3ef9adac7395e5c0943c22bbc8a4a856b103aa3/libs/langgraph/langgraph/graph/message.py#L150))."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install --quiet -U langgraph langchain_anthropic"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "ANTHROPIC_API_KEY:  ········\n"
     ]
    }
   ],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "\n",
    "def _set_env(var: str):\n",
    "    if not os.environ.get(var):\n",
    "        os.environ[var] = getpass.getpass(f\"{var}: \")\n",
    "\n",
    "\n",
    "_set_env(\"ANTHROPIC_API_KEY\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Define tools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import AIMessage\n",
    "from langchain_core.tools import tool\n",
    "\n",
    "from langgraph.prebuilt import ToolNode"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "@tool\n",
    "def get_weather(location: str):\n",
    "    \"\"\"Call to get the current weather.\"\"\"\n",
    "    if location.lower() in [\"sf\", \"san francisco\"]:\n",
    "        return \"It's 60 degrees and foggy.\"\n",
    "    else:\n",
    "        return \"It's 90 degrees and sunny.\"\n",
    "\n",
    "\n",
    "@tool\n",
    "def get_coolest_cities():\n",
    "    \"\"\"Get a list of coolest cities\"\"\"\n",
    "    return \"nyc, sf\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [get_weather, get_coolest_cities]\n",
    "tool_node = ToolNode(tools)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Manually call `ToolNode`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`ToolNode` operates on graph state with a list of messages. It expects the last message in the list to be an `AIMessage` with `tool_calls` parameter. \n",
    "\n",
    "Let's first see how to invoke the tool node manually:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [ToolMessage(content=\"It's 60 degrees and foggy.\", name='get_weather', tool_call_id='tool_call_id')]}"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "message_with_single_tool_call = AIMessage(\n",
    "    content=\"\",\n",
    "    tool_calls=[\n",
    "        {\n",
    "            \"name\": \"get_weather\",\n",
    "            \"args\": {\"location\": \"sf\"},\n",
    "            \"id\": \"tool_call_id\",\n",
    "            \"type\": \"tool_call\",\n",
    "        }\n",
    "    ],\n",
    ")\n",
    "\n",
    "tool_node.invoke({\"messages\": [message_with_single_tool_call]})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that typically you don't need to create `AIMessage` manually, and it will be automatically generated by any LangChain chat model that supports tool calling.\n",
    "\n",
    "You can also do parallel tool calling using `ToolNode` if you pass multiple tool calls to `AIMessage`'s `tool_calls` parameter:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [ToolMessage(content='nyc, sf', name='get_coolest_cities', tool_call_id='tool_call_id_1'),\n",
       "  ToolMessage(content=\"It's 60 degrees and foggy.\", name='get_weather', tool_call_id='tool_call_id_2')]}"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "message_with_multiple_tool_calls = AIMessage(\n",
    "    content=\"\",\n",
    "    tool_calls=[\n",
    "        {\n",
    "            \"name\": \"get_coolest_cities\",\n",
    "            \"args\": {},\n",
    "            \"id\": \"tool_call_id_1\",\n",
    "            \"type\": \"tool_call\",\n",
    "        },\n",
    "        {\n",
    "            \"name\": \"get_weather\",\n",
    "            \"args\": {\"location\": \"sf\"},\n",
    "            \"id\": \"tool_call_id_2\",\n",
    "            \"type\": \"tool_call\",\n",
    "        },\n",
    "    ],\n",
    ")\n",
    "\n",
    "tool_node.invoke({\"messages\": [message_with_multiple_tool_calls]})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using with chat models"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll be using a small chat model from Anthropic in our example. To use chat models with tool calling, we need to first ensure that the model is aware of the available tools. We do this by calling `.bind_tools` method on `ChatAnthropic` moodel"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Literal\n",
    "\n",
    "from langchain_anthropic import ChatAnthropic\n",
    "from langgraph.graph import StateGraph, MessagesState\n",
    "from langgraph.prebuilt import ToolNode\n",
    "\n",
    "\n",
    "model_with_tools = ChatAnthropic(\n",
    "    model=\"claude-3-haiku-20240307\", temperature=0\n",
    ").bind_tools(tools)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'name': 'get_weather',\n",
       "  'args': {'location': 'San Francisco'},\n",
       "  'id': 'toolu_01Fwm7dg1mcJU43Fkx2pqgm8',\n",
       "  'type': 'tool_call'}]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model_with_tools.invoke(\"what's the weather in sf?\").tool_calls"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, the AI message generated by the chat model already has `tool_calls` populated, so we can just pass it directly to `ToolNode`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [ToolMessage(content=\"It's 60 degrees and foggy.\", name='get_weather', tool_call_id='toolu_01LFvAVT3xJMeZS6kbWwBGZK')]}"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tool_node.invoke({\"messages\": [model_with_tools.invoke(\"what's the weather in sf?\")]})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ReAct Agent"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, let's see how to use `ToolNode` inside a LangGraph graph. Let's set up a graph implementation of the [ReAct agent](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/#react-agent). This agent takes some query as input, then repeatedly call tools until it has enough information to resolve the query. We'll be using `ToolNode` and the Anthropic model with tools we just defined"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Literal\n",
    "\n",
    "from langgraph.graph import StateGraph, MessagesState\n",
    "\n",
    "\n",
    "def should_continue(state: MessagesState) -> Literal[\"tools\", \"__end__\"]:\n",
    "    messages = state[\"messages\"]\n",
    "    last_message = messages[-1]\n",
    "    if last_message.tool_calls:\n",
    "        return \"tools\"\n",
    "    return \"__end__\"\n",
    "\n",
    "\n",
    "def call_model(state: MessagesState):\n",
    "    messages = state[\"messages\"]\n",
    "    response = model_with_tools.invoke(messages)\n",
    "    return {\"messages\": [response]}\n",
    "\n",
    "\n",
    "workflow = StateGraph(MessagesState)\n",
    "\n",
    "# Define the two nodes we will cycle between\n",
    "workflow.add_node(\"agent\", call_model)\n",
    "workflow.add_node(\"tools\", tool_node)\n",
    "\n",
    "workflow.add_edge(\"__start__\", \"agent\")\n",
    "workflow.add_conditional_edges(\n",
    "    \"agent\",\n",
    "    should_continue,\n",
    ")\n",
    "workflow.add_edge(\"tools\", \"agent\")\n",
    "\n",
    "app = workflow.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "[encoded data, 10320 characters]",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "try:\n",
    "    display(Image(app.get_graph().draw_mermaid_png()))\n",
    "except Exception:\n",
    "    # This requires some extra dependencies and is optional\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's try it out!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "what's the weather in sf?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "[{'text': \"Okay, let's check the weather in San Francisco:\", 'type': 'text'}, {'id': 'toolu_01LdmBXYeccWKdPrhZSwFCDX', 'input': {'location': 'San Francisco'}, 'name': 'get_weather', 'type': 'tool_use'}]\n",
      "Tool Calls:\n",
      "  get_weather (toolu_01LdmBXYeccWKdPrhZSwFCDX)\n",
      " Call ID: toolu_01LdmBXYeccWKdPrhZSwFCDX\n",
      "  Args:\n",
      "    location: San Francisco\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: get_weather\n",
      "\n",
      "It's 60 degrees and foggy.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "The weather in San Francisco is currently 60 degrees with foggy conditions.\n"
     ]
    }
   ],
   "source": [
    "# example with a single tool call\n",
    "for chunk in app.stream(\n",
    "    {\"messages\": [(\"human\", \"what's the weather in sf?\")]}, stream_mode=\"values\"\n",
    "):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "what's the weather in the coolest cities?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "[{'text': \"Okay, let's find out the weather in the coolest cities:\", 'type': 'text'}, {'id': 'toolu_01LFZUWTccyveBdaSAisMi95', 'input': {}, 'name': 'get_coolest_cities', 'type': 'tool_use'}]\n",
      "Tool Calls:\n",
      "  get_coolest_cities (toolu_01LFZUWTccyveBdaSAisMi95)\n",
      " Call ID: toolu_01LFZUWTccyveBdaSAisMi95\n",
      "  Args:\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: get_coolest_cities\n",
      "\n",
      "nyc, sf\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "[{'text': \"Now let's get the weather for those cities:\", 'type': 'text'}, {'id': 'toolu_01RHPQBhT1u6eDnPqqkGUpsV', 'input': {'location': 'nyc'}, 'name': 'get_weather', 'type': 'tool_use'}]\n",
      "Tool Calls:\n",
      "  get_weather (toolu_01RHPQBhT1u6eDnPqqkGUpsV)\n",
      " Call ID: toolu_01RHPQBhT1u6eDnPqqkGUpsV\n",
      "  Args:\n",
      "    location: nyc\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: get_weather\n",
      "\n",
      "It's 90 degrees and sunny.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "[{'id': 'toolu_01W5sFGF8PfgYzdY4CqT5c6e', 'input': {'location': 'sf'}, 'name': 'get_weather', 'type': 'tool_use'}]\n",
      "Tool Calls:\n",
      "  get_weather (toolu_01W5sFGF8PfgYzdY4CqT5c6e)\n",
      " Call ID: toolu_01W5sFGF8PfgYzdY4CqT5c6e\n",
      "  Args:\n",
      "    location: sf\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: get_weather\n",
      "\n",
      "It's 60 degrees and foggy.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Based on the results, it looks like the weather in the coolest cities is:\n",
      "- New York City: 90 degrees and sunny\n",
      "- San Francisco: 60 degrees and foggy\n",
      "\n",
      "So the weather in the coolest cities is a mix of warm and cool temperatures, with some sunny and some foggy conditions.\n"
     ]
    }
   ],
   "source": [
    "# example with a multiple tool calls in succession\n",
    "\n",
    "for chunk in app.stream(\n",
    "    {\"messages\": [(\"human\", \"what's the weather in the coolest cities?\")]},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`ToolNode` can also handle errors during tool execution. You can enable / disable this by setting `handle_tool_errors=True` (enabled by default). See our guide on handling errors in `ToolNode` [here](https://langchain-ai.github.io/langgraph/how-tos/tool-calling-errors/)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langgraph",
   "language": "python",
   "name": "langgraph"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
