mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-22 09:35:07 +02:00
Also, remove comment from bash script that makes insertion of `uv` harder
363 lines
12 KiB
Plaintext
363 lines
12 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"attachments": {},
|
|
"cell_type": "markdown",
|
|
"id": "d2eecb96-cf0e-47ed-8116-88a7eaa4236d",
|
|
"metadata": {},
|
|
"source": [
|
|
"# How to add cross-thread persistence (functional API)\n",
|
|
"\n",
|
|
"!!! info \"Prerequisites\"\n",
|
|
"\n",
|
|
" This guide assumes familiarity with the following:\n",
|
|
" \n",
|
|
" - [Functional API](../../concepts/functional_api/)\n",
|
|
" - [Persistence](../../concepts/persistence/)\n",
|
|
" - [Memory](../../concepts/memory/)\n",
|
|
" - [Chat Models](https://python.langchain.com/docs/concepts/chat_models/)\n",
|
|
"\n",
|
|
"LangGraph allows you to persist data across **different [threads](../../concepts/persistence/#threads)**. For instance, you can store information about users (their names or preferences) in a shared (cross-thread) memory and reuse them in the new threads (e.g., new conversations).\n",
|
|
"\n",
|
|
"When using the [functional API](../../concepts/functional_api/), you can set it up to store and retrieve memories by using the [Store](https://langchain-ai.github.io/langgraph/reference/store/#langgraph.store.base.BaseStore) interface:\n",
|
|
"\n",
|
|
"1. Create an instance of a `Store`\n",
|
|
"\n",
|
|
" ```python\n",
|
|
" from langgraph.store.memory import InMemoryStore, BaseStore\n",
|
|
" \n",
|
|
" store = InMemoryStore()\n",
|
|
" ```\n",
|
|
"\n",
|
|
"2. Pass the `store` instance to the `entrypoint()` decorator and expose `store` parameter in the function signature:\n",
|
|
"\n",
|
|
" ```python\n",
|
|
" from langgraph.func import entrypoint\n",
|
|
"\n",
|
|
" @entrypoint(store=store)\n",
|
|
" def workflow(inputs: dict, store: BaseStore):\n",
|
|
" my_task(inputs).result()\n",
|
|
" ...\n",
|
|
" ```\n",
|
|
" \n",
|
|
"In this guide, we will show how to construct and use a workflow that has a shared memory implemented using the [Store](https://langchain-ai.github.io/langgraph/reference/store/#langgraph.store.base.BaseStore) interface.\n",
|
|
"\n",
|
|
"!!! note Note\n",
|
|
"\n",
|
|
" Support for the [`Store`](https://langchain-ai.github.io/langgraph/reference/store/#langgraph.store.base.BaseStore) API that is used in this guide was added in LangGraph `v0.2.32`.\n",
|
|
"\n",
|
|
" Support for __index__ and __query__ arguments of the [`Store`](https://langchain-ai.github.io/langgraph/reference/store/#langgraph.store.base.BaseStore) API that is used in this guide was added in LangGraph `v0.2.54`.\n",
|
|
"\n",
|
|
"!!! tip \"Note\"\n",
|
|
"\n",
|
|
" If you need to add cross-thread persistence to a `StateGraph`, check out this [how-to guide](../cross-thread-persistence).\n",
|
|
"\n",
|
|
"## Setup\n",
|
|
"\n",
|
|
"First, let's install the required packages and set our API keys"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "3457aadf",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%capture --no-stderr\n",
|
|
"%pip install -U langchain_anthropic langchain_openai langgraph"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "aa2c64a7",
|
|
"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\")\n",
|
|
"_set_env(\"OPENAI_API_KEY\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "51b6817d",
|
|
"metadata": {},
|
|
"source": [
|
|
"!!! tip \"Set up [LangSmith](https://smith.langchain.com) 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](https://docs.smith.langchain.com)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "6b5b3d42-3d2c-455e-ac10-e2ae74dc1cf1",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Example: simple chatbot with long-term memory"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c4c550b5-1954-496b-8b9d-800361af17dc",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Define store\n",
|
|
"\n",
|
|
"In this example we will create a workflow that will be able to retrieve information about a user's preferences. We will do so by defining an `InMemoryStore` - an object that can store data in memory and query that data.\n",
|
|
"\n",
|
|
"When storing objects using the `Store` interface you define two things:\n",
|
|
"\n",
|
|
"* the namespace for the object, a tuple (similar to directories)\n",
|
|
"* the object key (similar to filenames)\n",
|
|
"\n",
|
|
"In our example, we'll be using `(\"memories\", <user_id>)` as namespace and random UUID as key for each new memory.\n",
|
|
"\n",
|
|
"Importantly, to determine the user, we will be passing `user_id` via the config keyword argument of the node function.\n",
|
|
"\n",
|
|
"Let's first define our store!"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"id": "a7f303d6-612e-4e34-bf36-29d4ed25d802",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from langgraph.store.memory import InMemoryStore\n",
|
|
"from langchain_openai import OpenAIEmbeddings\n",
|
|
"\n",
|
|
"in_memory_store = InMemoryStore(\n",
|
|
" index={\n",
|
|
" \"embed\": OpenAIEmbeddings(model=\"text-embedding-3-small\"),\n",
|
|
" \"dims\": 1536,\n",
|
|
" }\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3389c9f4-226d-40c7-8bfc-ee8aac24f79d",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Create workflow"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "2a30a362-528c-45ee-9df6-630d2d843588",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import uuid\n",
|
|
"\n",
|
|
"from langchain_anthropic import ChatAnthropic\n",
|
|
"from langchain_core.runnables import RunnableConfig\n",
|
|
"from langchain_core.messages import BaseMessage\n",
|
|
"from langgraph.func import entrypoint, task\n",
|
|
"from langgraph.graph import add_messages\n",
|
|
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
|
"from langgraph.store.base import BaseStore\n",
|
|
"\n",
|
|
"\n",
|
|
"model = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n",
|
|
"\n",
|
|
"\n",
|
|
"@task\n",
|
|
"def call_model(messages: list[BaseMessage], memory_store: BaseStore, user_id: str):\n",
|
|
" namespace = (\"memories\", user_id)\n",
|
|
" last_message = messages[-1]\n",
|
|
" memories = memory_store.search(namespace, query=str(last_message.content))\n",
|
|
" info = \"\\n\".join([d.value[\"data\"] for d in memories])\n",
|
|
" system_msg = f\"You are a helpful assistant talking to the user. User info: {info}\"\n",
|
|
"\n",
|
|
" # Store new memories if the user asks the model to remember\n",
|
|
" if \"remember\" in last_message.content.lower():\n",
|
|
" memory = \"User name is Bob\"\n",
|
|
" memory_store.put(namespace, str(uuid.uuid4()), {\"data\": memory})\n",
|
|
"\n",
|
|
" response = model.invoke([{\"role\": \"system\", \"content\": system_msg}] + messages)\n",
|
|
" return response\n",
|
|
"\n",
|
|
"\n",
|
|
"# NOTE: we're passing the store object here when creating a workflow via entrypoint()\n",
|
|
"@entrypoint(checkpointer=InMemorySaver(), store=in_memory_store)\n",
|
|
"def workflow(\n",
|
|
" inputs: list[BaseMessage],\n",
|
|
" *,\n",
|
|
" previous: list[BaseMessage],\n",
|
|
" config: RunnableConfig,\n",
|
|
" store: BaseStore,\n",
|
|
"):\n",
|
|
" user_id = config[\"configurable\"][\"user_id\"]\n",
|
|
" previous = previous or []\n",
|
|
" inputs = add_messages(previous, inputs)\n",
|
|
" response = call_model(inputs, store, user_id).result()\n",
|
|
" return entrypoint.final(value=response, save=add_messages(inputs, response))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f22a4a18-67e4-4f0b-b655-a29bbe202e1c",
|
|
"metadata": {},
|
|
"source": [
|
|
"!!! note Note\n",
|
|
"\n",
|
|
" If you're using LangGraph Cloud or LangGraph Studio, you __don't need__ to pass store to the entrypoint decorator, since it's done automatically."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "552d4e33-556d-4fa5-8094-2a076bc21529",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Run the workflow!"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "1842c626-6cd9-4f58-b549-58978e478098",
|
|
"metadata": {},
|
|
"source": [
|
|
"Now let's specify a user ID in the config and tell the model our name:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"id": "c871a073-a466-46ad-aafe-2b870831057e",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
|
"\n",
|
|
"Hello Bob! Nice to meet you. I'll remember that your name is Bob. How can I help you today?\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"config = {\"configurable\": {\"thread_id\": \"1\", \"user_id\": \"1\"}}\n",
|
|
"input_message = {\"role\": \"user\", \"content\": \"Hi! Remember: my name is Bob\"}\n",
|
|
"for chunk in workflow.stream([input_message], config, stream_mode=\"values\"):\n",
|
|
" chunk.pretty_print()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"id": "d862be40-1f8a-4057-81c4-b7bf073dc4c1",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
|
"\n",
|
|
"Your name is Bob.\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"config = {\"configurable\": {\"thread_id\": \"2\", \"user_id\": \"1\"}}\n",
|
|
"input_message = {\"role\": \"user\", \"content\": \"what is my name?\"}\n",
|
|
"for chunk in workflow.stream([input_message], config, stream_mode=\"values\"):\n",
|
|
" chunk.pretty_print()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "80fd01ec-f135-4811-8743-daff8daea422",
|
|
"metadata": {},
|
|
"source": [
|
|
"We can now inspect our in-memory store and verify that we have in fact saved the memories for the user:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"id": "76cde493-89cf-4709-a339-207d2b7e9ea7",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"{'data': 'User name is Bob'}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"for memory in in_memory_store.search((\"memories\", \"1\")):\n",
|
|
" print(memory.value)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "23f5d7eb-af23-4131-b8fd-2a69e74e6e55",
|
|
"metadata": {},
|
|
"source": [
|
|
"Let's now run the workflow for another user to verify that the memories about the first user are self contained:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"id": "d362350b-d730-48bd-9652-983812fd7811",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
|
"\n",
|
|
"I don't have any information about your name. I can only see our current conversation without any prior context or personal details about you. If you'd like me to know your name, feel free to tell me!\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"config = {\"configurable\": {\"thread_id\": \"3\", \"user_id\": \"2\"}}\n",
|
|
"input_message = {\"role\": \"user\", \"content\": \"what is my name?\"}\n",
|
|
"for chunk in workflow.stream([input_message], config, stream_mode=\"values\"):\n",
|
|
" chunk.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.12.3"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|