{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
      "metadata": {},
      "source": [
        "# How to add persistence (\"memory\") to your graph\n",
        "\n",
        "Many AI applications need memory to share context across multiple interactions. In LangGraph, memory is provided for any [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph) through [Checkpointers](https://github.com/langchain-ai/langgraph/tree/e4ca7ab69c599fd77dd4f0d47280849d715392cc/libs/checkpoint).\n",
        "\n",
        "When creating any LangGraph workflow, you can set them up to persist their state by doing using the following:\n",
        "\n",
        "1. A [Checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#basecheckpointsaver).\n",
        "2. Call `compile(checkpointer=my_checkpointer)` when compiling the graph.\n",
        "\n",
        "There are several options for checkpointers to use.\n",
        "\n",
        "1. [MemorySaver](https://langchain-ai.github.io/langgraph/reference/checkpoints/#memorysaver) is an in-memory key-value store for Graph state.\n",
        "2. [SqliteSaver](https://langchain-ai.github.io/langgraph/reference/checkpoints/#sqlitesaver) allows you to save to a Sqlite db locally or in memory.\n",
        "3. There are various external databases that can be used for persistence, such as [Postgres](https://langchain-ai.github.io/langgraph/how-tos/persistence_postgres/), [MongoDB](https://langchain-ai.github.io/langgraph/how-tos/persistence_mongodb/), and [Redis](https://langchain-ai.github.io/langgraph/how-tos/persistence_redis/).\n",
        " \n",
        "Here is an example using [MemorySaver](https://langchain-ai.github.io/langgraph/reference/checkpoints/#memorysaver) in memory:\n",
        "```python\n",
        "from langgraph.graph import StateGraph\n",
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "\n",
        "builder = StateGraph(....)\n",
        "# ... define the graph\n",
        "memory = MemorySaver()\n",
        "graph = builder.compile(checkpointer=memory)\n",
        "...\n",
        "```\n",
        "\n",
        "This works for [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph) and all its subclasses, such as [MessageGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#messagegraph).\n",
        "\n",
        "Below is an example.\n",
        "\n",
        "<div class=\"admonition tip\">\n",
        "    <p class=\"admonition-title\">Note</p>\n",
        "    <p>\n",
        "        In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool, checkpointer=checkpointer)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.2/docs/how_to/agent_executor/#concepts\">AgentExecutor</a> class.\n",
        "    </p>\n",
        "</div>    "
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7cbd446a-808f-4394-be92-d45ab818953c",
      "metadata": {},
      "source": [
        "## Setup\n",
        "\n",
        "First we need to install the packages required"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%capture --no-stderr\n",
        "%pip install --quiet -U langgraph langchain_anthropic"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0abe11f4-62ed-4dc4-8875-3db21e260d1d",
      "metadata": {},
      "source": [
        "Next, we need to set API keys for OpenAI (the LLM we will use) and Tavily (the search tool we will use)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
      "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(\"ANTHROPIC_API_KEY\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f0ed46a8-effe-4596-b0e1-a6a29ee16f5c",
      "metadata": {},
      "source": [
        "Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
      "metadata": {},
      "outputs": [],
      "source": [
        "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
        "_set_env(\"LANGCHAIN_API_KEY\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4cf509bc",
      "metadata": {},
      "source": [
        "## Set up the State\n",
        "\n",
        "The state is the interface for all the nodes."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 37,
      "id": "14619607",
      "metadata": {},
      "outputs": [],
      "source": [
        "from typing import Annotated\n",
        "\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.graph.message import add_messages\n",
        "\n",
        "# Add messages essentially does this with more\n",
        "# robust handling\n",
        "# def add_messages(left: list, right: list):\n",
        "#     return left + right\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "21ac643b-cb06-4724-a80c-2862ba4773f1",
      "metadata": {},
      "source": [
        "## Set up the tools\n",
        "\n",
        "We will first define the tools we want to use.\n",
        "For this simple example, we will use create a placeholder search engine.\n",
        "However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
      "metadata": {},
      "outputs": [],
      "source": [
        "from langchain_core.tools import tool\n",
        "\n",
        "\n",
        "@tool\n",
        "def search(query: str):\n",
        "    \"\"\"Call to surf the web.\"\"\"\n",
        "    # This is a placeholder for the actual implementation\n",
        "    return [\"The answer to your question lies within.\"]\n",
        "\n",
        "\n",
        "tools = [search]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "01885785-b71a-44d1-b1d6-7b5b14d53b58",
      "metadata": {},
      "source": [
        "Now we can create our [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/?h=tool+node#toolnode). This \n",
        "object actually **runs** the tools (aka functions)  that the LLM has asked to use."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7",
      "metadata": {},
      "outputs": [],
      "source": [
        "from langgraph.prebuilt import ToolNode\n",
        "\n",
        "tool_node = ToolNode(tools)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5497ed70-fce3-47f1-9cad-46f912bad6a5",
      "metadata": {},
      "source": [
        "## Set up the model\n",
        "\n",
        "Now we need to load the [chat model](https://python.langchain.com/v0.2/docs/concepts/#chat-models) to power our agent.\n",
        "For the design below, it must satisfy two criteria:\n",
        "\n",
        "1. It should work with **messages** (since our state contains a list of chat messages)\n",
        "2. It should work with [**tool calling**](https://python.langchain.com/v0.2/docs/concepts/#functiontool-calling).\n",
        "\n",
        "<div class=\"admonition tip\">\n",
        "    <p class=\"admonition-title\">Note</p>\n",
        "    <p>\n",
        "        These model requirements are not general requirements for using LangGraph - they are just requirements for this one example.\n",
        "    </p>\n",
        "</div>    \n",
        "    "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
      "metadata": {},
      "outputs": [],
      "source": [
        "from langchain_openai import ChatOpenAI\n",
        "\n",
        "# We will set streaming=True so that we can stream tokens\n",
        "# See the streaming section for more information on this.\n",
        "model = ChatOpenAI(temperature=0, streaming=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a77995c0-bae2-4cee-a036-8688a90f05b9",
      "metadata": {},
      "source": [
        "\n",
        "After we've done this, we should make sure the model knows that it has these tools available to call.\n",
        "We can do this by converting the LangChain tools into the format for OpenAI function calling, and then bind them to the model class.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
      "metadata": {},
      "outputs": [],
      "source": [
        "bound_model = model.bind_tools(tools)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e03c5094-9297-4d19-a04e-3eedc75cefb4",
      "metadata": {},
      "source": [
        "## Define the graph \n",
        "\n",
        "We now need to define a few different nodes in our graph.\n",
        "In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
        "There are two main nodes we need for this:\n",
        "\n",
        "1. The agent: responsible for deciding what (if any) actions to take.\n",
        "2. A function to invoke tools: if the agent decides to take an action, this node will then execute that action.\n",
        "\n",
        "We will also need to define some edges.\n",
        "Some of these edges may be conditional.\n",
        "The reason they are conditional is that based on the output of a node, one of several paths may be taken.\n",
        "The path that is taken is not known until that node is run (the LLM decides).\n",
        "\n",
        "1. Conditional Edge: after the agent is called, we should either:\n",
        "   a. If the agent said to take an action, then the function to invoke tools should be called\n",
        "   b. If the agent said that it was finished, then it should finish\n",
        "2. Normal Edge: after the tools are invoked, it should always go back to the agent to decide what to do next\n",
        "\n",
        "Let's define the nodes, as well as a function to decide how what conditional edge to take."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Define the function that determines whether to continue or not\n",
        "from typing import Literal\n",
        "\n",
        "\n",
        "def should_continue(state: State) -> Literal[\"action\", \"__end__\"]:\n",
        "    \"\"\"Return the next node to execute.\"\"\"\n",
        "    last_message = state[\"messages\"][-1]\n",
        "    # If there is no function call, then we finish\n",
        "    if not last_message.tool_calls:\n",
        "        return \"__end__\"\n",
        "    # Otherwise if there is, we continue\n",
        "    return \"action\"\n",
        "\n",
        "\n",
        "# Define the function that calls the model\n",
        "def call_model(state: State):\n",
        "    response = model.invoke(state[\"messages\"])\n",
        "    # We return a list, because this will get added to the existing list\n",
        "    return {\"messages\": response}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ffd6e892-946c-4899-8cc0-7c9291c1f73b",
      "metadata": {},
      "source": [
        "We can now put it all together and define the graph!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "812b4e70-4956-4415-8880-db48b3dcbad2",
      "metadata": {},
      "outputs": [],
      "source": [
        "from langgraph.graph import StateGraph, START\n",
        "\n",
        "# Define a new graph\n",
        "workflow = StateGraph(State)\n",
        "\n",
        "# Define the two nodes we will cycle between\n",
        "workflow.add_node(\"agent\", call_model)\n",
        "workflow.add_node(\"action\", tool_node)\n",
        "\n",
        "# Set the entrypoint as `agent`\n",
        "# This means that this node is the first one called\n",
        "workflow.add_edge(START, \"agent\")\n",
        "\n",
        "# We now add a conditional edge\n",
        "workflow.add_conditional_edges(\n",
        "    # First, we define the start node. We use `agent`.\n",
        "    # This means these are the edges taken after the `agent` node is called.\n",
        "    \"agent\",\n",
        "    # Next, we pass in the function that will determine which node is called next.\n",
        "    should_continue,\n",
        ")\n",
        "\n",
        "# We now add a normal edge from `tools` to `agent`.\n",
        "# This means that after `tools` is called, `agent` node is called next.\n",
        "workflow.add_edge(\"action\", \"agent\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bc9c8536-f90b-44fa-958d-5df016c66d8f",
      "metadata": {},
      "source": [
        "**Persistence**\n",
        "\n",
        "To add in persistence, we pass in a checkpoint when compiling the graph"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 30,
      "id": "6845ed6a-d155-4105-9160-28849877248b",
      "metadata": {},
      "outputs": [],
      "source": [
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "\n",
        "memory = MemorySaver()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "79d29875-8aa8-434c-9f20-1c58346a6249",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Finally, we compile it!\n",
        "# This compiles it into a LangChain Runnable,\n",
        "# meaning you can use it as you would any other runnable\n",
        "app = workflow.compile(checkpointer=memory)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7654ebcc-2179-41b4-92d1-6666f6f8634f",
      "metadata": {},
      "source": [
        "<div class=\"admonition tip\">\n",
        "    <p class=\"admonition-title\">Note</p>\n",
        "    <p>\n",
        "        If you're using LangGraph Cloud, you <strong>don't need</strong> to pass checkpointer when compiling the graph, since it's done automatically.\n",
        "    </p>\n",
        "</div>"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "0d49697f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "image/jpeg": "[encoded data, 10904 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",
      "id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159",
      "metadata": {},
      "source": [
        "## Interacting with the Agent\n",
        "\n",
        "We can now interact with the agent and see that it remembers previous messages!\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "cfd140f0-a5a6-4697-8115-322242f197b5",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "hi! I'm bob\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "Hello Bob! How can I assist you today?\n"
          ]
        }
      ],
      "source": [
        "from langchain_core.messages import HumanMessage\n",
        "\n",
        "config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
        "input_message = HumanMessage(content=\"hi! I'm bob\")\n",
        "for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
        "    event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "08ae8246-11d5-40e1-8567-361e5bef8917",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "what is my name?\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "Your name is Bob.\n"
          ]
        }
      ],
      "source": [
        "input_message = HumanMessage(content=\"what is my name?\")\n",
        "for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
        "    event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3f47bbfc-d9ef-4288-ba4a-ebbc0136fa9d",
      "metadata": {},
      "source": [
        "If we want to start a new conversation, we can pass in a different thread id. Poof! All the memories are gone!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "id": "273d56a8-f40f-4a51-a27f-7c6bb2bda0ba",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "what is my name?\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "I'm sorry, I do not know your name as I am an AI assistant and do not have access to personal information.\n"
          ]
        }
      ],
      "source": [
        "input_message = HumanMessage(content=\"what is my name?\")\n",
        "for event in app.stream(\n",
        "    {\"messages\": [input_message]},\n",
        "    {\"configurable\": {\"thread_id\": \"3\"}},\n",
        "    stream_mode=\"values\",\n",
        "):\n",
        "    event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e833f994",
      "metadata": {},
      "source": [
        "All the checkpoints are persisted to the checkpointer, so you can always resume previous threads."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "8578a66d-6489-4e03-8c23-fd0530278455",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "You forgot??\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "I apologize for the confusion. I am an AI assistant and I do not have the ability to remember information from previous interactions. How can I assist you today, Bob?\n"
          ]
        }
      ],
      "source": [
        "input_message = HumanMessage(content=\"You forgot??\")\n",
        "for event in app.stream(\n",
        "    {\"messages\": [input_message]},\n",
        "    {\"configurable\": {\"thread_id\": \"2\"}},\n",
        "    stream_mode=\"values\",\n",
        "):\n",
        "    event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "eb20430f",
      "metadata": {},
      "outputs": [],
      "source": []
    }
  ],
  "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.9"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
