{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ur8xi4C7S06n"
      },
      "outputs": [],
      "source": [
        "# Copyright 2025 Google LLC\n",
        "#\n",
        "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#     https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JAPoU8Sm5E6e"
      },
      "source": [
        "# Building and Deploying a Human-in-the-Loop LangGraph Application with Agent Engine on Vertex AI\n",
        "\n",
        "<table align=\"left\">\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/generative-ai/blob/main/gemini/agent-engine/langgraph_human_in_the_loop.ipynb\">\n",
        "      <img width=\"32px\" src=\"https://www.gstatic.com/pantheon/images/bigquery/welcome_page/colab-logo.svg\" alt=\"Google Colaboratory logo\"><br> Open in Colab\n",
        "    </a>\n",
        "  </td>\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://console.cloud.google.com/vertex-ai/colab/import/https:%2F%2Fraw.githubusercontent.com%2FGoogleCloudPlatform%2Fgenerative-ai%2Fmain%2Fgemini%2Fagent-engine%2Flanggraph_human_in_the_loop.ipynb\">\n",
        "      <img width=\"32px\" src=\"https://lh3.googleusercontent.com/JmcxdQi-qOpctIvWKgPtrzZdJJK-J3sWE1RsfjZNwshCFgE_9fULcNpuXYTilIR2hjwN\" alt=\"Google Cloud Colab Enterprise logo\"><br> Open in Colab Enterprise\n",
        "    </a>\n",
        "  </td>    \n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/generative-ai/main/gemini/agent-engine/langgraph_human_in_the_loop.ipynb\">\n",
        "      <img src=\"https://lh3.googleusercontent.com/UiNooY4LUgW_oTvpsNhPpQzsstV5W8F7rYgxgGBD85cWJoLmrOzhVs_ksK_vgx40SHs7jCqkTkCk=e14-rj-sc0xffffff-h130-w32\" alt=\"Vertex AI logo\"><br> Open in Workbench\n",
        "    </a>\n",
        "  </td>\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/agent-engine/langgraph_human_in_the_loop.ipynb\">\n",
        "      <img width=\"32px\" src=\"https://raw.githubusercontent.com/primer/octicons/refs/heads/main/icons/mark-github-24.svg\" alt=\"GitHub logo\"><br> View on GitHub\n",
        "    </a>\n",
        "  </td>\n",
        "</table>\n",
        "\n",
        "<div style=\"clear: both;\"></div>\n",
        "\n",
        "<b>Share to:</b>\n",
        "\n",
        "<a href=\"https://www.linkedin.com/sharing/share-offsite/?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/agent-engine/langgraph_human_in_the_loop.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/8/81/LinkedIn_icon.svg\" alt=\"LinkedIn logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://bsky.app/intent/compose?text=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/agent-engine/langgraph_human_in_the_loop.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/7/7a/Bluesky_Logo.svg\" alt=\"Bluesky logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://twitter.com/intent/tweet?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/agent-engine/langgraph_human_in_the_loop.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/5/5a/X_icon_2.svg\" alt=\"X logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://reddit.com/submit?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/agent-engine/langgraph_human_in_the_loop.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://redditinc.com/hubfs/Reddit%20Inc/Brand/Reddit_Logo.png\" alt=\"Reddit logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://www.facebook.com/sharer/sharer.php?u=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/agent-engine/langgraph_human_in_the_loop.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/5/51/Facebook_f_logo_%282019%29.svg\" alt=\"Facebook logo\">\n",
        "</a>            "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "84f0f73a0f76"
      },
      "source": [
        "| | |\n",
        "|-|-|\n",
        "| Author(s) | [Xiaolong Yang](https://github.com/shawn-yang-google) |"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tvgnzT1CKxrO"
      },
      "source": [
        "## Overview\n",
        "\n",
        "[Agent Engine](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/overview) is a managed service designed to help you build and deploy agent frameworks. [LangGraph](https://langchain-ai.github.io/langgraph/) is a library for constructing stateful, multi-actor applications with LLMs, enabling the creation of sophisticated agent and multi-agent workflows.\n",
        "\n",
        "This notebook demonstrates how to build, deploy, and test a Human-in-the-Loop LangGraph application using [Agent Engine](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/overview) on Vertex AI. You'll learn how to combine LangGraph's powerful workflow orchestration with the scalability of Vertex AI to build Human-in-the-Loop generative AI applications.\n",
        "\n",
        "The previous [notebook](https://github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/agent-engine/intro_agent_engine.ipynb) covered: Defining Tools, Defining a Router, Building a LangGraph Application, Local Testing, Deploying to Vertex AI, Remote Testing, and Cleaning Up Resources.\n",
        "\n",
        "This notebook expands on those concepts and explores the following Human-in-the-Loop features:\n",
        "\n",
        "- **Reviewing Tool Calls:** Implement human oversight after tool use, allowing for verification and correction of actions before proceeding.\n",
        "- **Fetching State History:** Retrieve the complete execution history of the LangGraph application for auditing, analysis, and potential state reversion.\n",
        "- **Time Travel:** Examine the state of the agent at a specific point in time to understand past decisions.\n",
        "- **Replay:** Restart execution from a specific checkpoint without modifications to ensure consistent results.\n",
        "- **Branching:** Create alternative execution paths based on a past state, enabling the agent to explore different possibilities or correct previous errors.\n",
        "\n",
        "By the end of this notebook, you'll possess the skills to build and deploy customized Human-in-the-Loop generative AI applications using LangGraph, Agent Engine, and Vertex AI."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "61RBz8LLbxCR"
      },
      "source": [
        "## Get started"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "No17Cw5hgx12"
      },
      "source": [
        "### Install the Vertex AI SDK and Required Packages"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "tFy3H3aPgx12"
      },
      "outputs": [],
      "source": [
        "%pip install --upgrade --user --quiet \\\n",
        "    \"google-cloud-aiplatform[agent_engines,langchain]\" \\\n",
        "    requests --force-reinstall"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "R5Xep4W9lq-Z"
      },
      "source": [
        "### Restart runtime\n",
        "\n",
        "To use the newly installed packages in this Jupyter runtime, you must restart the runtime. You can do this by running the cell below, which restarts the current kernel.\n",
        "\n",
        "The restart might take a minute or longer. After it's restarted, continue to the next step."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "id": "XRvKdaPDTznN"
      },
      "outputs": [],
      "source": [
        "import IPython\n",
        "\n",
        "app = IPython.Application.instance()\n",
        "app.kernel.do_shutdown(True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SbmM4z7FOBpM"
      },
      "source": [
        "<div class=\"alert alert-block alert-warning\">\n",
        "<b>⚠️ The kernel is going to restart. Wait until it's finished before continuing to the next step. ⚠️</b>\n",
        "</div>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dmWOrTJ3gx13"
      },
      "source": [
        "### Authenticate your notebook environment (Colab only)\n",
        "\n",
        "If you're running this notebook on Google Colab, run the cell below to authenticate your environment."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "NyKGtVQjgx13"
      },
      "outputs": [],
      "source": [
        "import sys\n",
        "\n",
        "if \"google.colab\" in sys.modules:\n",
        "    from google.colab import auth\n",
        "\n",
        "    auth.authenticate_user()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DF4l8DTdWgPY"
      },
      "source": [
        "### Set Google Cloud Project Information and Initialize the Vertex AI SDK\n",
        "\n",
        "Before using Vertex AI, ensure you have an existing Google Cloud project and have [enabled the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com).\n",
        "\n",
        "Refer to the documentation for more details on [setting up a project and development environment](https://cloud.google.com/vertex-ai/docs/start/cloud-environment)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "id": "Nqwi-5ufWp_B"
      },
      "outputs": [],
      "source": [
        "PROJECT_ID = \"[your-project-id]\"  # @param {type:\"string\"}\n",
        "LOCATION = \"us-central1\"  # @param {type:\"string\"}\n",
        "STAGING_BUCKET = \"gs://[your-staging-bucket]\"  # @param {type:\"string\"}\n",
        "\n",
        "import vertexai\n",
        "\n",
        "vertexai.init(project=PROJECT_ID, location=LOCATION, staging_bucket=STAGING_BUCKET)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EdvJRUWRNGHE"
      },
      "source": [
        "## Building and Deploying a LangGraph App on Agent Engine\n",
        "\n",
        "The following sections guide you through building and deploying a LangGraph application using Agent Engine on Vertex AI."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3b3004f33544"
      },
      "source": [
        "### Import Libraries\n",
        "\n",
        "Import the required Python libraries. These libraries provide the necessary tools for interacting with LangGraph, Vertex AI, and other components of the application."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {
        "id": "67acfba81563"
      },
      "outputs": [],
      "source": [
        "from langchain.load import load as langchain_load\n",
        "import requests\n",
        "from vertexai import agent_engines"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eXIykM_rDlzJ"
      },
      "source": [
        "### Define Tools\n",
        "\n",
        "Begin by defining the tools for your LangGraph application. You'll define a custom Python function that serves as a tool within our agentic application.\n",
        "\n",
        "In this example, we'll create a simple tool that retrieves the exchange rate requested by the user.  In practice, you can define functions to interact with APIs, query databases, or perform any other tasks your agent might need to execute."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "id": "9AhaLYidDzQu"
      },
      "outputs": [],
      "source": [
        "def get_exchange_rate(\n",
        "    currency_from: str = \"USD\",\n",
        "    currency_to: str = \"EUR\",\n",
        "    currency_date: str = \"latest\",\n",
        "):\n",
        "    \"\"\"Retrieves the exchange rate between two currencies on a specified date.\n",
        "\n",
        "    Uses the Frankfurter API (https://api.frankfurter.app/) to obtain\n",
        "    exchange rate data.\n",
        "\n",
        "    Args:\n",
        "        currency_from: The base currency (3-letter currency code).\n",
        "            Defaults to \"USD\" (US Dollar).\n",
        "        currency_to: The target currency (3-letter currency code).\n",
        "            Defaults to \"EUR\" (Euro).\n",
        "        currency_date: The date for which to retrieve the exchange rate.\n",
        "            Defaults to \"latest\" for the most recent exchange rate data.\n",
        "            Can be specified in YYYY-MM-DD format for historical rates.\n",
        "\n",
        "    Returns:\n",
        "        dict: A dictionary containing the exchange rate information.\n",
        "            Example: {\"amount\": 1.0, \"base\": \"USD\", \"date\": \"2023-11-24\",\n",
        "                \"rates\": {\"EUR\": 0.95534}}\n",
        "    \"\"\"\n",
        "\n",
        "    response = requests.get(\n",
        "        f\"https://api.frankfurter.app/{currency_date}\",\n",
        "        params={\"from\": currency_from, \"to\": currency_to},\n",
        "    )\n",
        "    return response.json()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "94db54ba756b"
      },
      "source": [
        "### Define [`Checkpointers`](https://langchain-ai.github.io/langgraph/concepts/persistence/)\n",
        "\n",
        "In LangGraph, [memory is checkpointing/persistence](https://github.com/langchain-ai/langgraph/discussions/352#discussioncomment-9290376). Checkpointing saves the [state](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer-state) of the agent's execution at each node in the graph, which is crucial for: `Resuming execution`, `Debugging and Inspection`, and `Asynchronous Operations`.\n",
        "\n",
        "LangGraph provides a [Checkpointer Interface](https://langchain-ai.github.io/langgraph/concepts/persistence/#checkpointer-interface), defining methods for saving and loading the state. Several built-in checkpointers are available to implement this interface.\n",
        "\n",
        "Next, you'll define the arguments for your LangGraph application's checkpointer and create a custom Python function to act as the checkpointer builder. In this case, we'll define a simple `In Memory` checkpointer."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {
        "id": "22981b2e1c59"
      },
      "outputs": [],
      "source": [
        "checkpointer_kwargs = None\n",
        "\n",
        "\n",
        "def checkpointer_builder(**kwargs):\n",
        "    from langgraph.checkpoint.memory import MemorySaver\n",
        "\n",
        "    return MemorySaver()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "d32ce189d989"
      },
      "source": [
        "### Define the Human-in-the-Loop LangGraph Application\n",
        "\n",
        "Now, you'll integrate all the components to define your Human-in-the-Loop LangGraph application within Agent Engine.\n",
        "\n",
        "This application will utilize the tools and checkpointer you've defined. LangGraph offers a powerful framework for structuring these interactions and leveraging the capabilities of LLMs."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {
        "id": "6eeff14856cd"
      },
      "outputs": [],
      "source": [
        "agent = agent_engines.LanggraphAgent(\n",
        "    model=\"gemini-2.0-flash\",\n",
        "    tools=[get_exchange_rate],\n",
        "    model_kwargs={\"temperature\": 0, \"max_retries\": 6},\n",
        "    checkpointer_kwargs=checkpointer_kwargs,\n",
        "    checkpointer_builder=checkpointer_builder,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "be06c89a87aa"
      },
      "source": [
        "### Local Testing\n",
        "\n",
        "This section covers local testing of your LangGraph application before deployment to ensure it behaves as expected."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {
        "id": "oOJMqctkVvYA"
      },
      "outputs": [],
      "source": [
        "agent.set_up()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "metadata": {
        "id": "JHU5GhMrh6Xz"
      },
      "outputs": [],
      "source": [
        "inputs = {\n",
        "    \"messages\": [\n",
        "        (\"user\", \"What is the exchange rate from US dollars to Swedish currency?\")\n",
        "    ]\n",
        "}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {
        "id": "aA5s9KI2VwJv"
      },
      "outputs": [],
      "source": [
        "response = agent.query(\n",
        "    input=inputs,\n",
        "    config={\"configurable\": {\"thread_id\": \"synchronous-thread-id\"}},\n",
        ")\n",
        "\n",
        "response[\"messages\"][-1][\"kwargs\"][\"content\"]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GfaxXCINV7cV"
      },
      "source": [
        "You can also utilize [streaming](https://langchain-ai.github.io/langgraph/how-tos/stream-values/) mode to stream back the `values` of the graph, representing the full state after each node execution."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "metadata": {
        "id": "fc70cda0aeb0"
      },
      "outputs": [],
      "source": [
        "for state_values in agent.stream_query(\n",
        "    input=inputs,\n",
        "    stream_mode=\"values\",\n",
        "    config={\"configurable\": {\"thread_id\": \"streaming-thread-values\"}},\n",
        "):\n",
        "    print(state_values)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "afjX7glhE4o_"
      },
      "source": [
        "Alternatively, you can stream back `updates` to the graph. These represent the changes to the state after each node is executed."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {
        "id": "K4IjUrV_E3pc"
      },
      "outputs": [],
      "source": [
        "for state_updates in agent.stream_query(\n",
        "    input=inputs,\n",
        "    stream_mode=\"updates\",\n",
        "    config={\"configurable\": {\"thread_id\": \"streaming-thread-updates\"}},\n",
        "):\n",
        "    print(state_updates)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5jOSd-DqsNW3"
      },
      "source": [
        "## Human-in-the-loop"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hRadOYCYP2V9"
      },
      "source": [
        "### Reviewing Tool Calls\n",
        "\n",
        "LangGraph's Human-in-the-Loop functionality provides various [use cases](https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/#use-cases) for incorporating human intervention and oversight into agent workflows (state machines). This notebook focuses on the [Reviewing Tool Calls](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/review-tool-calls/) use case."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XWpl6JK-dqSy"
      },
      "source": [
        "To achieve this, the agent needs to [interrupt](https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/#interrupt) execution in the following scenarios:\n",
        "\n",
        "* Before invoking the tool (when the LLM generates a tool call AI Message).\n",
        "* After receiving a tool response."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 56,
      "metadata": {
        "id": "-uvK9JJokfou"
      },
      "outputs": [],
      "source": [
        "response = agent.query(\n",
        "    input=inputs,\n",
        "    interrupt_before=[\"tools\"],  # Before invoking the tool.\n",
        "    interrupt_after=[\"tools\"],  # After getting a tool message.\n",
        "    config={\"configurable\": {\"thread_id\": \"human-in-the-loop-deepdive\"}},\n",
        ")\n",
        "langchain_load(response[\"messages\"][-1]).pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-45J2HOaSfWb"
      },
      "source": [
        "The process was interrupted *before invoking the tool*.\n",
        "\n",
        "After review, we assume the LLM-generated tool call (`AI Message`) is correct and proceed to resume execution."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 57,
      "metadata": {
        "id": "4YHPNebpkfrU"
      },
      "outputs": [],
      "source": [
        "response = agent.query(\n",
        "    input=None,  # Resume (continue with the tool call AI Message).\n",
        "    interrupt_before=[\"tools\"],\n",
        "    interrupt_after=[\"tools\"],\n",
        "    config={\"configurable\": {\"thread_id\": \"human-in-the-loop-deepdive\"}},\n",
        ")\n",
        "langchain_load(response[\"messages\"][-1]).pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gCVylqwzUSfm"
      },
      "source": [
        "The process is interrupted again *after receiving the tool message*.\n",
        "\n",
        "Upon review, if the LLM-generated `Tool Message` appears correct, we can resume execution."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "metadata": {
        "id": "i8wAJxyykfuI"
      },
      "outputs": [],
      "source": [
        "response = agent.query(\n",
        "    input=None,  # Resume (continue with the Tool Message).\n",
        "    interrupt_before=[\"tools\"],\n",
        "    interrupt_after=[\"tools\"],\n",
        "    config={\"configurable\": {\"thread_id\": \"human-in-the-loop-deepdive\"}},\n",
        ")\n",
        "langchain_load(response[\"messages\"][-1]).pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Gf0msu8l1Z6v"
      },
      "source": [
        "### Fetching State History\n",
        "\n",
        "You can fetch the state history by calling `.get_state_history`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "metadata": {
        "id": "7Kp9eqFv1fTA"
      },
      "outputs": [],
      "source": [
        "for state_snapshot in agent.get_state_history(\n",
        "    config={\"configurable\": {\"thread_id\": \"human-in-the-loop-deepdive\"}},\n",
        "):\n",
        "    if state_snapshot[\"metadata\"][\"step\"] >= 0:\n",
        "        print(f'step {state_snapshot[\"metadata\"][\"step\"]}: {state_snapshot[\"config\"]}')\n",
        "        state_snapshot[\"values\"][\"messages\"][-1].pretty_print()\n",
        "        print(\"\\n\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sg72Af3U1_Im"
      },
      "source": [
        "### Time Travel\n",
        "\n",
        "LangGraph's [Time Travel](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/time-travel/) demonstrates how to build a conversational agent with persistent memory, enabling human intervention to correct past actions.  Essentially, it \"rewinds\" the conversation to a previous state, allows for mistake correction, and permits the agent to continue from that corrected point.\n",
        "\n",
        "You can \"time travel\" by calling `.get_state`. By default, the agent retrieves the `latest state`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "metadata": {
        "id": "UxeCODgILFN3"
      },
      "outputs": [],
      "source": [
        "state = agent.get_state(\n",
        "    config={\n",
        "        \"configurable\": {\n",
        "            \"thread_id\": \"human-in-the-loop-deepdive\",\n",
        "        }\n",
        "    }\n",
        ")\n",
        "\n",
        "print(f'step {state[\"metadata\"][\"step\"]}: {state[\"config\"]}')\n",
        "state[\"values\"][\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0dT7W34i2opn"
      },
      "source": [
        "To retrieve an earlier state, you need to specify the `checkpoint_id` (and `checkpoint_ns`)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "metadata": {
        "id": "wHK72zjAtbZi"
      },
      "outputs": [],
      "source": [
        "snapshot_config = {}\n",
        "for state_snapshot in agent.get_state_history(\n",
        "    config={\"configurable\": {\"thread_id\": \"human-in-the-loop-deepdive\"}},\n",
        "):\n",
        "    if state_snapshot[\"metadata\"][\"step\"] == 1:\n",
        "        snapshot_config = state_snapshot[\"config\"]\n",
        "        break\n",
        "\n",
        "snapshot_config"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "metadata": {
        "id": "hpl-ElG72LUt"
      },
      "outputs": [],
      "source": [
        "state = agent.get_state(config=snapshot_config)\n",
        "print(f'step {state[\"metadata\"][\"step\"]}: {state[\"config\"]}')\n",
        "state[\"values\"][\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "metadata": {
        "id": "j86TZj1s2cGW"
      },
      "outputs": [],
      "source": [
        "state"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "su91f3u02ytT"
      },
      "source": [
        "### Replay\n",
        "\n",
        "LangGraph's [Replay](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/time-travel/#replay-a-state) feature allows you to resume or replay a conversation from any specific point in its history.\n",
        "\n",
        "You can initiate a replay by passing the `state[\"config\"]` back to the agent. Note that the execution resumes exactly where it was left off, executing a tool call."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "metadata": {
        "id": "-OTMr3Fe7kig"
      },
      "outputs": [],
      "source": [
        "state[\"config\"]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "metadata": {
        "id": "l5FfylrfrYAJ"
      },
      "outputs": [],
      "source": [
        "for state_values in agent.stream_query(\n",
        "    input=None,  # resume\n",
        "    stream_mode=\"values\",\n",
        "    config=state[\"config\"],\n",
        "):\n",
        "    langchain_load(state_values[\"messages\"][-1]).pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RGJSM__V3zvS"
      },
      "source": [
        "### Branching\n",
        "\n",
        "LangGraph's [Branching](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/time-travel/#branch-off-a-past-state) feature allows you to modify and re-run a LangGraph conversation from a specific point in its history (rather than just from the latest state).  This enables the agent to explore alternate trajectories or allows a user to \"version control\" changes in a workflow.\n",
        "\n",
        "In this example, you will:\n",
        "* Update the tool calls from a previous step.\n",
        "* Call `.update_state` to rerun the step with the updated configuration."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "metadata": {
        "id": "Is1Ca-QE8P6T"
      },
      "outputs": [],
      "source": [
        "last_message = state[\"values\"][\"messages\"][-1]\n",
        "print(last_message)\n",
        "print(last_message.tool_calls)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rLBeJAOIcPWI"
      },
      "source": [
        "Update the tool calls from the previous step."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "metadata": {
        "id": "jP3cHIrF8VfW"
      },
      "outputs": [],
      "source": [
        "last_message.tool_calls[0][\"args\"][\"currency_date\"] = \"2024-09-01\"\n",
        "last_message.tool_calls"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2HZovMHe_cfX"
      },
      "source": [
        "Call `.update_state` to rerun the step with the updated configuration."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "metadata": {
        "id": "qB45N3eL9XiP"
      },
      "outputs": [],
      "source": [
        "branch_config = agent.update_state(\n",
        "    config=state[\"config\"],\n",
        "    values={\"messages\": [last_message]},  # the update we want to make\n",
        ")\n",
        "branch_config"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "metadata": {
        "id": "zWS9Sk_h9p8I"
      },
      "outputs": [],
      "source": [
        "for state_values in agent.stream_query(\n",
        "    input=None,  # resume\n",
        "    stream_mode=\"values\",\n",
        "    config=branch_config,\n",
        "):\n",
        "    langchain_load(state_values[\"messages\"][-1]).pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iOYFDvqFGNqg"
      },
      "source": [
        "## Deploying the Agent"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "metadata": {
        "id": "xi-3p8Wd0r4x"
      },
      "outputs": [],
      "source": [
        "remote_agent = agent_engines.create(\n",
        "    agent_engines.LanggraphAgent(\n",
        "        model=\"gemini-2.0-flash\",\n",
        "        tools=[get_exchange_rate],\n",
        "        model_kwargs={\"temperature\": 0, \"max_retries\": 6},\n",
        "        checkpointer_kwargs=checkpointer_kwargs,\n",
        "        checkpointer_builder=checkpointer_builder,\n",
        "    ),\n",
        "    requirements=[\n",
        "        \"google-cloud-aiplatform[agent_engines,langchain]\",\n",
        "        \"requests\",\n",
        "    ],\n",
        ")\n",
        "\n",
        "remote_agent"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gqt-TOmEGLL8"
      },
      "source": [
        "## Querying the Remote Agent"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2g6lOlZSsdmD"
      },
      "source": [
        "### Remote testing"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "metadata": {
        "id": "HlbK3Bsq3LlE"
      },
      "outputs": [],
      "source": [
        "for state_updates in remote_agent.stream_query(\n",
        "    input=inputs,\n",
        "    stream_mode=\"updates\",\n",
        "    config={\"configurable\": {\"thread_id\": \"remote-streaming-thread-updates\"}},\n",
        "):\n",
        "    print(state_updates)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "metadata": {
        "id": "YQE8Rfyz1jcA"
      },
      "outputs": [],
      "source": [
        "for state_values in remote_agent.stream_query(\n",
        "    input=inputs,\n",
        "    stream_mode=\"values\",\n",
        "    config={\"configurable\": {\"thread_id\": \"remote-human-in-the-loop-overall\"}},\n",
        "):\n",
        "    print(state_values)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "n7ocljPjUXYx"
      },
      "source": [
        "### Reviewing Tool Calls"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "metadata": {
        "id": "_LB3KXTUUas7"
      },
      "outputs": [],
      "source": [
        "response = remote_agent.query(\n",
        "    input=inputs,\n",
        "    interrupt_before=[\"tools\"],  # Before invoking the tool.\n",
        "    interrupt_after=[\"tools\"],  # After getting a tool message.\n",
        "    config={\"configurable\": {\"thread_id\": \"human-in-the-loop-deepdive\"}},\n",
        ")\n",
        "langchain_load(response[\"messages\"][-1]).pretty_print()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "metadata": {
        "id": "smWg54uqUs0g"
      },
      "outputs": [],
      "source": [
        "response = remote_agent.query(\n",
        "    input=None,  # Resume (continue with the tool call AI Message).\n",
        "    interrupt_before=[\"tools\"],\n",
        "    interrupt_after=[\"tools\"],\n",
        "    config={\"configurable\": {\"thread_id\": \"human-in-the-loop-deepdive\"}},\n",
        ")\n",
        "langchain_load(response[\"messages\"][-1]).pretty_print()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "metadata": {
        "id": "QRE3BCvRU0nC"
      },
      "outputs": [],
      "source": [
        "response = agent.query(\n",
        "    input=None,  # Resume (continue with the Tool Message).\n",
        "    interrupt_before=[\"tools\"],\n",
        "    interrupt_after=[\"tools\"],\n",
        "    config={\"configurable\": {\"thread_id\": \"human-in-the-loop-deepdive\"}},\n",
        ")\n",
        "langchain_load(response[\"messages\"][-1]).pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2a4e033321ad"
      },
      "source": [
        "## Cleaning up\n",
        "\n",
        "After you've finished experimenting, it's a good practice to clean up your cloud resources. You can delete the deployed Agent Engine instance to avoid any unexpected charges on your Google Cloud account."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 54,
      "metadata": {
        "id": "eb5fbfa43866"
      },
      "outputs": [],
      "source": [
        "remote_agent.delete()"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [
        "No17Cw5hgx12",
        "R5Xep4W9lq-Z",
        "dmWOrTJ3gx13",
        "DF4l8DTdWgPY"
      ],
      "name": "langgraph_human_in_the_loop.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
