{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# How to handle tool calling errors\n",
    "\n",
    "<div class=\"admonition tip\">\n",
    "    <p class=\"admonition-title\">Prerequisites</p>\n",
    "    <p>\n",
    "        This guide assumes familiarity with the following:\n",
    "        <ul>\n",
    "            <li>\n",
    "                <a href=\"https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/#tool-calling\">\n",
    "                    Tool calling\n",
    "                </a>\n",
    "            </li>\n",
    "            <li>\n",
    "                <a href=\"https://langchain-ai.github.io/langgraph/how-tos/memory/delete-messages/\">\n",
    "                    Deleting messages\n",
    "                </a>\n",
    "            </li>\n",
    "        </ul>\n",
    "    </p>\n",
    "</div> \n",
    "\n",
    "LLMs aren't perfect at calling tools. The model may try to call a tool that doesn't exist or fail to return arguments that match the requested schema. Strategies like keeping schemas simple, reducing the number of tools you pass at once, and having good names and descriptions can help mitigate this risk, but aren't foolproof.\n",
    "\n",
    "This guide covers some ways to build error handling into your graphs to mitigate these failure modes."
   ]
  },
  {
   "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": [
    "## Using the prebuilt `ToolNode`\n",
    "\n",
    "To start, define a mock weather tool that has some hidden restrictions on input queries. The intent here is to simulate a real-world case where a model fails to call a tool correctly:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.tools import tool\n",
    "\n",
    "\n",
    "@tool\n",
    "def get_weather(location: str):\n",
    "    \"\"\"Call to get the current weather.\"\"\"\n",
    "    if location == \"san francisco\":\n",
    "        raise ValueError(\"Input queries must be proper nouns\")\n",
    "    elif location == \"San Francisco\":\n",
    "        return \"It's 60 degrees and foggy.\"\n",
    "    else:\n",
    "        raise ValueError(\"Invalid input.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, 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 use the prebuilt [`ToolNode`](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode) to execute called tools, and a small, fast model powered by Anthropic:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "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",
    "tool_node = ToolNode([get_weather])\n",
    "\n",
    "model_with_tools = ChatAnthropic(\n",
    "    model=\"claude-3-haiku-20240307\", temperature=0\n",
    ").bind_tools([get_weather])\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": 5,
   "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": [
    "When you try to call the tool, you can see that the model calls the tool with a bad input, causing the tool to throw an error. The prebuilt `ToolNode` that executes the tool has some built-in error handling that captures the error and passes it back to the model so that it can try again:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "HUMAN: what is the weather in san francisco?\n",
      "\n",
      "AI: [{'id': 'toolu_01UagsLm5GKtdtJ6nZdADFSa', 'input': {'location': 'san francisco'}, 'name': 'get_weather', 'type': 'tool_use'}]\n",
      "\n",
      "TOOL: Error: ValueError('Input queries must be proper nouns')\n",
      " Please fix your mistakes.\n",
      "\n",
      "AI: [{'text': 'Apologies, it looks like there was an issue with the weather lookup. Let me try that again with the proper format:', 'type': 'text'}, {'id': 'toolu_01PwRKYxhbgW8pHnWbyubp94', 'input': {'location': 'San Francisco'}, 'name': 'get_weather', 'type': 'tool_use'}]\n",
      "\n",
      "TOOL: It's 60 degrees and foggy.\n",
      "\n",
      "AI: The current weather in San Francisco is 60 degrees and foggy.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "response = app.invoke(\n",
    "    {\"messages\": [(\"human\", \"what is the weather in san francisco?\")]},\n",
    ")\n",
    "\n",
    "for message in response[\"messages\"]:\n",
    "    string_representation = f\"{message.type.upper()}: {message.content}\\n\"\n",
    "    print(string_representation)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Custom strategies\n",
    "\n",
    "This is a fine default in many cases, but there are cases where custom fallbacks may be better.\n",
    "\n",
    "For example, the below tool requires as input a list of elements of a specific length - tricky for a small model! We'll also intentionally avoid pluralizing `topic` to trick the model into thinking it should pass a string:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "HUMAN: Write me an incredible haiku about water.\n",
      "\n",
      "AI: [{'text': 'Here is a haiku about water:', 'type': 'text'}, {'id': 'toolu_018KUdKbJEiprjJdBGxDs4Zq', 'input': {'topic': ['water']}, 'name': 'master_haiku_generator', 'type': 'tool_use'}]\n",
      "\n",
      "TOOL: Error: ValidationError(model='master_haiku_generatorSchema', errors=[{'loc': ('request',), 'msg': 'field required', 'type': 'value_error.missing'}])\n",
      " Please fix your mistakes.\n",
      "\n",
      "AI: [{'text': 'Oops, let me try that again with the required parameters:', 'type': 'text'}, {'id': 'toolu_015ENNFtemedbmJdvkzT7PU1', 'input': {'request': {'topic': ['water']}}, 'name': 'master_haiku_generator', 'type': 'tool_use'}]\n",
      "\n",
      "TOOL: Error: ValidationError(model='master_haiku_generatorSchema', errors=[{'loc': ('request', 'topic'), 'msg': 'ensure this value has at least 3 items', 'type': 'value_error.list.min_items', 'ctx': {'limit_value': 3}}])\n",
      " Please fix your mistakes.\n",
      "\n",
      "AI: [{'text': 'Hmm, it looks like the haiku generator requires at least 3 topics. Let me provide 3 related topics:', 'type': 'text'}, {'id': 'toolu_015vKAc67QwAgoJxigueiyLi', 'input': {'request': {'topic': ['water', 'ocean', 'waves']}}, 'name': 'master_haiku_generator', 'type': 'tool_use'}]\n",
      "\n",
      "TOOL: Here is a haiku about water, ocean, and waves:\n",
      "\n",
      "Vast ocean's embrace,\n",
      "Waves crash upon the shoreline,\n",
      "Water's eternal dance.\n",
      "\n",
      "AI: I hope you enjoy this haiku about the beauty and power of water! Let me know if you would like me to generate another one.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain.pydantic_v1 import BaseModel, conlist\n",
    "\n",
    "\n",
    "class HaikuRequest(BaseModel):\n",
    "    topic: conlist(str, min_items=3, max_items=3)\n",
    "\n",
    "\n",
    "@tool\n",
    "def master_haiku_generator(request: HaikuRequest):\n",
    "    \"\"\"Generates a haiku based on the provided topics.\"\"\"\n",
    "    model = ChatAnthropic(model=\"claude-3-haiku-20240307\", temperature=0)\n",
    "    chain = model | StrOutputParser()\n",
    "    topics = \", \".join(request.topic)\n",
    "    haiku = chain.invoke(f\"Write a haiku about {topics}\")\n",
    "    return haiku\n",
    "\n",
    "\n",
    "tool_node = ToolNode([master_haiku_generator])\n",
    "\n",
    "model = ChatAnthropic(model=\"claude-3-haiku-20240307\", temperature=0)\n",
    "model_with_tools = model.bind_tools([master_haiku_generator])\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()\n",
    "\n",
    "response = app.invoke(\n",
    "    {\"messages\": [(\"human\", \"Write me an incredible haiku about water.\")]},\n",
    "    {\"recursion_limit\": 10},\n",
    ")\n",
    "\n",
    "for message in response[\"messages\"]:\n",
    "    string_representation = f\"{message.type.upper()}: {message.content}\\n\"\n",
    "    print(string_representation)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that the model takes two tries to get the input correct.\n",
    "\n",
    "A better strategy might be to trim the failed attempt to reduce distraction, then fall back to a more advanced model. Here's an example. We also use a custom-built node to call our tools instead of the prebuilt `ToolNode`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "from langchain_core.messages import AIMessage, ToolMessage\n",
    "from langchain_core.messages.modifier import RemoveMessage\n",
    "\n",
    "\n",
    "class HaikuRequest(BaseModel):\n",
    "    topic: conlist(str, min_items=3, max_items=3)\n",
    "\n",
    "\n",
    "@tool\n",
    "def master_haiku_generator(request: HaikuRequest):\n",
    "    \"\"\"Generates a haiku based on the provided topics.\"\"\"\n",
    "    model = ChatAnthropic(model=\"claude-3-haiku-20240307\", temperature=0)\n",
    "    chain = model | StrOutputParser()\n",
    "    topics = \", \".join(request.topic)\n",
    "    haiku = chain.invoke(f\"Write a haiku about {topics}\")\n",
    "    return haiku\n",
    "\n",
    "\n",
    "def call_tool(state: MessagesState):\n",
    "    tools_by_name = {master_haiku_generator.name: master_haiku_generator}\n",
    "    messages = state[\"messages\"]\n",
    "    last_message = messages[-1]\n",
    "    output_messages = []\n",
    "    for tool_call in last_message.tool_calls:\n",
    "        try:\n",
    "            tool_result = tools_by_name[tool_call[\"name\"]].invoke(tool_call[\"args\"])\n",
    "            output_messages.append(\n",
    "                ToolMessage(\n",
    "                    content=json.dumps(tool_result),\n",
    "                    name=tool_call[\"name\"],\n",
    "                    tool_call_id=tool_call[\"id\"],\n",
    "                )\n",
    "            )\n",
    "        except Exception as e:\n",
    "            # Return the error if the tool call fails\n",
    "            output_messages.append(\n",
    "                ToolMessage(\n",
    "                    content=\"\",\n",
    "                    name=tool_call[\"name\"],\n",
    "                    tool_call_id=tool_call[\"id\"],\n",
    "                    additional_kwargs={\"error\": e},\n",
    "                )\n",
    "            )\n",
    "    return {\"messages\": output_messages}\n",
    "\n",
    "\n",
    "model = ChatAnthropic(model=\"claude-3-haiku-20240307\", temperature=0)\n",
    "model_with_tools = model.bind_tools([master_haiku_generator])\n",
    "\n",
    "better_model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\", temperature=0)\n",
    "better_model_with_tools = better_model.bind_tools([master_haiku_generator])\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 should_fallback(\n",
    "    state: MessagesState,\n",
    ") -> Literal[\"agent\", \"remove_failed_tool_call_attempt\"]:\n",
    "    messages = state[\"messages\"]\n",
    "    failed_tool_messages = [\n",
    "        msg\n",
    "        for msg in messages\n",
    "        if isinstance(msg, ToolMessage)\n",
    "        and msg.additional_kwargs.get(\"error\") is not None\n",
    "    ]\n",
    "    if failed_tool_messages:\n",
    "        return \"remove_failed_tool_call_attempt\"\n",
    "    return \"agent\"\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",
    "def remove_failed_tool_call_attempt(state: MessagesState):\n",
    "    messages = state[\"messages\"]\n",
    "    # Remove all messages from the most recent\n",
    "    # instance of AIMessage onwards.\n",
    "    last_ai_message_index = next(\n",
    "        i\n",
    "        for i, msg in reversed(list(enumerate(messages)))\n",
    "        if isinstance(msg, AIMessage)\n",
    "    )\n",
    "    messages_to_remove = messages[last_ai_message_index:]\n",
    "    return {\"messages\": [RemoveMessage(id=m.id) for m in messages_to_remove]}\n",
    "\n",
    "\n",
    "# Fallback to a better model if a tool call fails\n",
    "def call_fallback_model(state: MessagesState):\n",
    "    messages = state[\"messages\"]\n",
    "    response = better_model_with_tools.invoke(messages)\n",
    "    return {\"messages\": [response]}\n",
    "\n",
    "\n",
    "workflow = StateGraph(MessagesState)\n",
    "\n",
    "workflow.add_node(\"agent\", call_model)\n",
    "workflow.add_node(\"tools\", call_tool)\n",
    "workflow.add_node(\"remove_failed_tool_call_attempt\", remove_failed_tool_call_attempt)\n",
    "workflow.add_node(\"fallback_agent\", call_fallback_model)\n",
    "\n",
    "workflow.add_edge(\"__start__\", \"agent\")\n",
    "workflow.add_conditional_edges(\n",
    "    \"agent\",\n",
    "    should_continue,\n",
    ")\n",
    "workflow.add_conditional_edges(\"tools\", should_fallback)\n",
    "workflow.add_edge(\"remove_failed_tool_call_attempt\", \"fallback_agent\")\n",
    "workflow.add_edge(\"fallback_agent\", \"tools\")\n",
    "\n",
    "app = workflow.compile()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `tools` node will now return `ToolMessage`s with an `error` field in `additional_kwargs` if a tool call fails. If that happens, it will go to another node that removes the failed tool messages, and has a better model retry the tool call generation.\n",
    "\n",
    "The diagram below shows this visually:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "[encoded data, 22932 characters]",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "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. To emphasize the removal steps, let's `stream` the responses from the model so that we can see each executed node:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'agent': {'messages': [AIMessage(content=[{'text': 'Here is a haiku about water:', 'type': 'text'}, {'id': 'toolu_01DHSAkgSaCR1hrpAx1cKbJs', 'input': {'topic': ['water']}, 'name': 'master_haiku_generator', 'type': 'tool_use'}], response_metadata={'id': 'msg_015dn3iAZDnBxPCzDo8eWSub', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 384, 'output_tokens': 67}}, id='run-8b01a16f-1dcd-4d4b-b183-725f8e7a2865-0', tool_calls=[{'name': 'master_haiku_generator', 'args': {'topic': ['water']}, 'id': 'toolu_01DHSAkgSaCR1hrpAx1cKbJs', 'type': 'tool_call'}], usage_metadata={'input_tokens': 384, 'output_tokens': 67, 'total_tokens': 451})]}}\n",
      "{'tools': {'messages': [ToolMessage(content='', additional_kwargs={'error': ValidationError(model='master_haiku_generatorSchema', errors=[{'loc': ('request',), 'msg': 'field required', 'type': 'value_error.missing'}])}, name='master_haiku_generator', id='b4761ee1-4d73-482b-85a0-6c114463ab1d', tool_call_id='toolu_01DHSAkgSaCR1hrpAx1cKbJs')]}}\n",
      "{'remove_failed_tool_call_attempt': {'messages': [RemoveMessage(content='', id='run-8b01a16f-1dcd-4d4b-b183-725f8e7a2865-0'), RemoveMessage(content='', id='b4761ee1-4d73-482b-85a0-6c114463ab1d')]}}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/vadymbarda/.virtualenvs/langgraph/lib/python3.11/site-packages/langchain_core/_api/beta_decorator.py:87: LangChainBetaWarning: The class `RemoveMessage` is in beta. It is actively being worked on, so the API may change.\n",
      "  warn_beta(\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'fallback_agent': {'messages': [AIMessage(content=[{'text': 'Certainly! I\\'d be happy to help you create an incredible haiku about water. To do this, I\\'ll use the master_haiku_generator function, which requires three topics. Since you\\'ve specified water as the main theme, I\\'ll add two related concepts to create a more vivid and interesting haiku. Let\\'s use \"water,\" \"flow,\" and \"reflection\" as our three topics.', 'type': 'text'}, {'id': 'toolu_01THSivCtMnx6P7oVy4eqywy', 'input': {'request': {'topic': ['water', 'flow', 'reflection']}}, 'name': 'master_haiku_generator', 'type': 'tool_use'}], response_metadata={'id': 'msg_01HQQbQ8YjSKn37kQYSwKn8D', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 414, 'output_tokens': 158}}, id='run-c0ceb996-d5f4-4d28-9844-095c83b74ebe-0', tool_calls=[{'name': 'master_haiku_generator', 'args': {'request': {'topic': ['water', 'flow', 'reflection']}}, 'id': 'toolu_01THSivCtMnx6P7oVy4eqywy', 'type': 'tool_call'}], usage_metadata={'input_tokens': 414, 'output_tokens': 158, 'total_tokens': 572})]}}\n",
      "{'tools': {'messages': [ToolMessage(content='\"Here is a haiku about water, flow, and reflection:\\\\n\\\\nRippling waters flow,\\\\nMirroring the sky above,\\\\nTranquil reflection.\"', name='master_haiku_generator', id='b7da21e3-bc09-4f4a-a25b-3df29db69589', tool_call_id='toolu_01THSivCtMnx6P7oVy4eqywy')]}}\n",
      "{'agent': {'messages': [AIMessage(content='I hope you enjoy this haiku about the beauty and serenity of water. Please let me know if you would like me to generate another one.', response_metadata={'id': 'msg_01KZc2GPbh7xVHUQVpJoWkMK', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 587, 'output_tokens': 35}}, id='run-89d4dfce-ae04-471f-894e-7d632feeb5cb-0', usage_metadata={'input_tokens': 587, 'output_tokens': 35, 'total_tokens': 622})]}}\n"
     ]
    }
   ],
   "source": [
    "stream = app.stream(\n",
    "    {\"messages\": [(\"human\", \"Write me an incredible haiku about water.\")]},\n",
    "    {\"recursion_limit\": 10},\n",
    ")\n",
    "\n",
    "for chunk in stream:\n",
    "    print(chunk)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can see that you get a cleaner response - the more powerful model gets it right on the first try, and the smaller model's failure gets wiped from the graph state. This shorter message history also avoid overpopulating the graph state with attempts.\n",
    "\n",
    "You can also inspect this [LangSmith trace](https://smith.langchain.com/public/7ce6f1fe-48c4-400e-9cbe-1de2da6d2800/r), which shows the failed initial call to the smaller model.\n",
    "\n",
    "## Next steps\n",
    "\n",
    "You've now seen how to implement some strategies to handle tool calling errors.\n",
    "\n",
    "Next, check out some of the [other LangGraph how-to guides here](https://langchain-ai.github.io/langgraph/how-tos/)."
   ]
  }
 ],
 "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
}
