{ "cells": [ { "cell_type": "markdown", "id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4", "metadata": {}, "source": [ "# How to add human-in-the-loop processes to the prebuilt ReAct agent\n", "\n", "
\n", "

Prerequisites

\n", "

\n", " This guide assumes familiarity with the following:\n", "

\n", "

\n", "
\n", "\n", "This guide will show how to add human-in-the-loop processes to the prebuilt ReAct agent. Please see [this tutorial](../create-react-agent) for how to get started with the prebuilt ReAct agent\n", "\n", "You can add a a breakpoint before tools are called by passing `interrupt_before=[\"tools\"]` to `create_react_agent`. Note that you need to be using a checkpointer for this to work." ] }, { "cell_type": "markdown", "id": "7be3889f-3c17-4fa1-bd2b-84114a2c7247", "metadata": {}, "source": [ "## Setup\n", "\n", "First, let's install the required packages and set our API keys" ] }, { "cell_type": "code", "execution_count": 2, "id": "a213e11a-5c62-4ddb-a707-490d91add383", "metadata": {}, "outputs": [], "source": [ "%%capture --no-stderr\n", "%pip install -U langgraph langchain-openai" ] }, { "cell_type": "code", "execution_count": 3, "id": "23a1885c-04ab-4750-aefa-105891fddf3e", "metadata": {}, "outputs": [], "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(\"OPENAI_API_KEY\")" ] }, { "cell_type": "markdown", "id": "d4c5c054", "metadata": {}, "source": [ "
\n", "

Set up LangSmith for LangGraph development

\n", "

\n", " Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here. \n", "

\n", "
" ] }, { "cell_type": "markdown", "id": "03c0f089-070c-4cd4-87e0-6c51f2477b82", "metadata": {}, "source": [ "## Code" ] }, { "cell_type": "code", "execution_count": 4, "id": "7a154152-973e-4b5d-aa13-48c617744a4c", "metadata": {}, "outputs": [], "source": [ "# First we initialize the model we want to use.\n", "from langchain_openai import ChatOpenAI\n", "\n", "model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n", "\n", "\n", "# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n", "from typing import Literal\n", "\n", "from langchain_core.tools import tool\n", "\n", "\n", "@tool\n", "def get_weather(location: str):\n", " \"\"\"Use this to get weather information from a given location.\"\"\"\n", " if location.lower() in [\"nyc\", \"new york\"]:\n", " return \"It might be cloudy in nyc\"\n", " elif location.lower() in [\"sf\", \"san francisco\"]:\n", " return \"It's always sunny in sf\"\n", " else:\n", " raise AssertionError(\"Unknown Location\")\n", "\n", "\n", "tools = [get_weather]\n", "\n", "# We need a checkpointer to enable human-in-the-loop patterns\n", "from langgraph.checkpoint.memory import MemorySaver\n", "\n", "memory = MemorySaver()\n", "\n", "# Define the graph\n", "\n", "from langgraph.prebuilt import create_react_agent\n", "\n", "graph = create_react_agent(\n", " model, tools=tools, interrupt_before=[\"tools\"], checkpointer=memory\n", ")" ] }, { "cell_type": "markdown", "id": "00407425-506d-4ffd-9c86-987921d8c844", "metadata": {}, "source": [ "## Usage\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "16636975-5f2d-4dc7-ab8e-d0bea0830a28", "metadata": {}, "outputs": [], "source": [ "def print_stream(stream):\n", " \"\"\"A utility to pretty print the stream.\"\"\"\n", " for s in stream:\n", " message = s[\"messages\"][-1]\n", " if isinstance(message, tuple):\n", " print(message)\n", " else:\n", " message.pretty_print()" ] }, { "cell_type": "code", "execution_count": 8, "id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", "what is the weather in SF, CA?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", " get_weather (call_YjOKDkgMGgUZUpKIasYk1AdK)\n", " Call ID: call_YjOKDkgMGgUZUpKIasYk1AdK\n", " Args:\n", " location: SF, CA\n" ] } ], "source": [ "from langchain_core.messages import HumanMessage\n", "\n", "config = {\"configurable\": {\"thread_id\": \"42\"}}\n", "inputs = {\"messages\": [(\"user\", \"what is the weather in SF, CA?\")]}\n", "\n", "print_stream(graph.stream(inputs, config, stream_mode=\"values\"))" ] }, { "cell_type": "markdown", "id": "ca40a719", "metadata": {}, "source": [ "We can verify that our graph stopped at the right place:" ] }, { "cell_type": "code", "execution_count": 9, "id": "3decf001-7228-4ed5-8779-2b9ed98a74ea", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Next step: ('tools',)\n" ] } ], "source": [ "snapshot = graph.get_state(config)\n", "print(\"Next step: \", snapshot.next)" ] }, { "cell_type": "markdown", "id": "7de6ca78", "metadata": {}, "source": [ "Now we can either approve or edit the tool call before proceeding to the next node. If we wanted to approve the tool call, we would simply continue streaming the graph with `None` input. If we wanted to edit the tool call we need to update the state to have the correct tool call, and then after the update has been applied we can continue.\n", "\n", "We can try resuming and we will see an error arise:" ] }, { "cell_type": "code", "execution_count": 10, "id": "740bbaeb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", " get_weather (call_YjOKDkgMGgUZUpKIasYk1AdK)\n", " Call ID: call_YjOKDkgMGgUZUpKIasYk1AdK\n", " Args:\n", " location: SF, CA\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: get_weather\n", "\n", "Error: AssertionError('Unknown Location')\n", " Please fix your mistakes.\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", " get_weather (call_CLu9ofeBhtWF2oheBspxXkfE)\n", " Call ID: call_CLu9ofeBhtWF2oheBspxXkfE\n", " Args:\n", " location: San Francisco, CA\n" ] } ], "source": [ "print_stream(graph.stream(None, config, stream_mode=\"values\"))" ] }, { "cell_type": "markdown", "id": "c1cf5950", "metadata": {}, "source": [ "This error arose because our tool argument of \"San Francisco, CA\" is not a location our tool recognizes.\n", "\n", "Let's show how we would edit the tool call to search for \"San Francisco\" instead of \"San Francisco, CA\" - since our tool as written treats \"San Francisco, CA\" as an unknown location. We will update the state and then resume streaming the graph and should see no errors arise:" ] }, { "cell_type": "code", "execution_count": 11, "id": "1c81ed9f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'configurable': {'thread_id': '42',\n", " 'checkpoint_ns': '',\n", " 'checkpoint_id': '1ef801d1-5b93-6bb9-8004-a088af1f9cec'}}" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "state = graph.get_state(config)\n", "\n", "last_message = state.values[\"messages\"][-1]\n", "last_message.tool_calls[0][\"args\"] = {\"location\": \"San Francisco\"}\n", "\n", "graph.update_state(config, {\"messages\": [last_message]})" ] }, { "cell_type": "code", "execution_count": 12, "id": "83148e08-63e8-49e5-a08b-02dc907bed1d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", " get_weather (call_CLu9ofeBhtWF2oheBspxXkfE)\n", " Call ID: call_CLu9ofeBhtWF2oheBspxXkfE\n", " Args:\n", " location: San Francisco\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: get_weather\n", "\n", "It's always sunny in sf\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", "The weather in San Francisco is currently sunny.\n" ] } ], "source": [ "print_stream(graph.stream(None, config, stream_mode=\"values\"))" ] }, { "cell_type": "markdown", "id": "8202a5f9", "metadata": {}, "source": [ "Fantastic! Our graph updated properly to query the weather in San Francisco and got the correct \"It's always sunny in sf\" response from the tool, and then responded to the user accordingly." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "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.4" } }, "nbformat": 4, "nbformat_minor": 5 }