{ "cells": [ { "cell_type": "markdown", "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53", "metadata": {}, "source": [ "# How to add thread-level persistence to your graph\n", "\n", "
\n", "

Prerequisites

\n", "

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

\n", "

\n", "
\n", "\n", "Many AI applications need memory to share context across multiple interactions. In LangGraph, this kind of memory can be added to any [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph) using [thread-level persistence](https://langchain-ai.github.io/langgraph/concepts/persistence) .\n", "\n", "When creating any LangGraph graph, you can set it up to persist its state by adding a [checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#basecheckpointsaver) when compiling the graph:\n", "\n", "```python\n", "from langgraph.checkpoint.memory import MemorySaver\n", "\n", "checkpointer = MemorySaver()\n", "graph.compile(checkpointer=checkpointer)\n", "```\n", "\n", "This guide shows how you can add thread-level persistence to your graph.\n", "\n", "
\n", "

Note

\n", "

\n", " If you need memory that is shared across multiple conversations or users (cross-thread persistence), check out this how-to guide).\n", "

\n", "
" ] }, { "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": 1, "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": 2, "id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89", "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", "id": "f0ed46a8-effe-4596-b0e1-a6a29ee16f5c", "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": "4cf509bc", "metadata": {}, "source": [ "## Define graph\n", "\n", "We will be using a single-node graph that calls a [chat model](https://python.langchain.com/docs/concepts/#chat-models).\n", "\n", "Let's first define the model we'll be using:" ] }, { "cell_type": "code", "execution_count": 3, "id": "892b54b9-75f0-4804-9ed0-88b5e5532989", "metadata": {}, "outputs": [], "source": [ "from langchain_anthropic import ChatAnthropic\n", "\n", "model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")" ] }, { "cell_type": "markdown", "id": "7b7a2792-982b-4e47-83eb-0c594725d1c1", "metadata": {}, "source": [ "Now we can define our `StateGraph` and add our model-calling node:" ] }, { "cell_type": "code", "execution_count": 4, "id": "87326ea6-34c5-46da-a41f-dda26ef9bd74", "metadata": {}, "outputs": [], "source": [ "from typing import Annotated\n", "from typing_extensions import TypedDict\n", "\n", "from langgraph.graph import StateGraph, MessagesState, START\n", "\n", "\n", "def call_model(state: MessagesState):\n", " response = model.invoke(state[\"messages\"])\n", " return {\"messages\": response}\n", "\n", "\n", "builder = StateGraph(MessagesState)\n", "builder.add_node(\"call_model\", call_model)\n", "builder.add_edge(START, \"call_model\")\n", "graph = builder.compile()" ] }, { "cell_type": "markdown", "id": "250d8fd9-2e7a-4892-9adc-19762a1e3cce", "metadata": {}, "source": [ "If we try to use this graph, the context of the conversation will not be persisted across interactions:" ] }, { "cell_type": "code", "execution_count": 5, "id": "6fa9a5e3-7101-43ab-a811-592e222b9580", "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! It's nice to meet you. How are you doing today? Is there anything I can help you with or would you like to chat about something in particular?\n", "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", "what's my name?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", "I apologize, but I don't have access to your personal information, including your name. I'm an AI language model designed to provide general information and answer questions to the best of my ability based on my training data. I don't have any information about individual users or their personal details. If you'd like to share your name, you're welcome to do so, but I won't be able to recall it in future conversations.\n" ] } ], "source": [ "input_message = {\"type\": \"user\", \"content\": \"hi! I'm bob\"}\n", "for chunk in graph.stream({\"messages\": [input_message]}, stream_mode=\"values\"):\n", " chunk[\"messages\"][-1].pretty_print()\n", "\n", "input_message = {\"type\": \"user\", \"content\": \"what's my name?\"}\n", "for chunk in graph.stream({\"messages\": [input_message]}, stream_mode=\"values\"):\n", " chunk[\"messages\"][-1].pretty_print()" ] }, { "cell_type": "markdown", "id": "bc9c8536-f90b-44fa-958d-5df016c66d8f", "metadata": {}, "source": [ "## Add persistence\n", "\n", "To add in persistence, we need to pass in a [Checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#langgraph.checkpoint.base.BaseCheckpointSaver) when compiling the graph." ] }, { "cell_type": "code", "execution_count": 6, "id": "f088933f-264c-477f-9a7d-03f6e9d4ee3a", "metadata": {}, "outputs": [], "source": [ "from langgraph.checkpoint.memory import MemorySaver\n", "\n", "memory = MemorySaver()\n", "graph = builder.compile(checkpointer=memory)\n", "# If you're using LangGraph Cloud or LangGraph Studio, you don't need to pass the checkpointer when compiling the graph, since it's done automatically." ] }, { "cell_type": "markdown", "id": "7654ebcc-2179-41b4-92d1-6666f6f8634f", "metadata": {}, "source": [ "
\n", "

Note

\n", "

\n", " If you're using LangGraph Cloud or LangGraph Studio, you don't need to pass checkpointer when compiling the graph, since it's done automatically.\n", "

\n", "
" ] }, { "cell_type": "markdown", "id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159", "metadata": {}, "source": [ "We can now interact with the agent and see that it remembers previous messages!" ] }, { "cell_type": "code", "execution_count": 7, "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! It's nice to meet you. How are you doing today? Is there anything in particular you'd like to chat about or any questions you have that I can help you with?\n" ] } ], "source": [ "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", "input_message = {\"type\": \"user\", \"content\": \"hi! I'm bob\"}\n", "for chunk in graph.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n", " chunk[\"messages\"][-1].pretty_print()" ] }, { "cell_type": "markdown", "id": "1bb07bf8-68b7-4049-a0f1-eb67a4879a3a", "metadata": {}, "source": [ "You can always resume previous threads:" ] }, { "cell_type": "code", "execution_count": 8, "id": "08ae8246-11d5-40e1-8567-361e5bef8917", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", "what's my name?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", "Your name is Bob, as you introduced yourself at the beginning of our conversation.\n" ] } ], "source": [ "input_message = {\"type\": \"user\", \"content\": \"what's my name?\"}\n", "for chunk in graph.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n", " chunk[\"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": 9, "id": "273d56a8-f40f-4a51-a27f-7c6bb2bda0ba", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", "what's is my name?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", "I apologize, but I don't have access to your personal information, including your name. As an AI language model, I don't have any information about individual users unless it's provided within the conversation. If you'd like to share your name, you're welcome to do so, but otherwise, I won't be able to know or guess it.\n" ] } ], "source": [ "input_message = {\"type\": \"user\", \"content\": \"what's my name?\"}\n", "for chunk in graph.stream(\n", " {\"messages\": [input_message]},\n", " {\"configurable\": {\"thread_id\": \"2\"}},\n", " stream_mode=\"values\",\n", "):\n", " chunk[\"messages\"][-1].pretty_print()" ] } ], "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 }