跳到内容

如何为您的图添加跨线程持久性

先决条件

本指南假定您熟悉以下内容

之前的指南中,您学习了如何在单个线程中跨多个交互持久化图状态。LangGraph 还允许您跨多个线程持久化数据。例如,您可以将有关用户的信息(他们的姓名或偏好)存储在共享内存中,并在新的对话线程中重复使用它们。

在本指南中,我们将展示如何构建和使用一个具有共享内存的图,该共享内存使用 Store 接口实现。

注意

本指南中使用的 Store API 的支持是在 LangGraph v0.2.32 中添加的。

本指南中使用的 Store API 的 indexquery 参数的支持是在 LangGraph v0.2.54 中添加的。

设置

首先,让我们安装所需的软件包并设置我们的 API 密钥

%%capture --no-stderr
%pip install -U langchain_openai langgraph
import getpass
import os


def _set_env(var: str):
    if not os.environ.get(var):
        os.environ[var] = getpass.getpass(f"{var}: ")


_set_env("ANTHROPIC_API_KEY")
_set_env("OPENAI_API_KEY")

设置 LangSmith 以进行 LangGraph 开发

注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。LangSmith 允许您使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用程序 — 阅读此处了解更多关于如何开始的信息

定义存储

在本例中,我们将创建一个能够检索有关用户偏好的信息的图。我们将通过定义 InMemoryStore 来实现这一点 - 一个可以在内存中存储数据并查询该数据的对象。然后,我们在编译图时传递 store 对象。这允许图中的每个节点访问 store:当您定义节点函数时,您可以定义 store 关键字参数,LangGraph 将自动传递您编译图时使用的 store 对象。

当使用 Store 接口存储对象时,您需要定义两件事

  • 对象的命名空间,一个元组(类似于目录)
  • 对象键(类似于文件名)

在我们的示例中,我们将使用 ("memories", <user_id>) 作为命名空间,并使用随机 UUID 作为每个新记忆的键。

重要的是,为了确定用户,我们将通过节点函数的 config 关键字参数传递 user_id

让我们首先定义一个已经填充了一些用户记忆的 InMemoryStore

from langgraph.store.memory import InMemoryStore
from langchain_openai import OpenAIEmbeddings

in_memory_store = InMemoryStore(
    index={
        "embed": OpenAIEmbeddings(model="text-embedding-3-small"),
        "dims": 1536,
    }
)

API 参考: OpenAIEmbeddings

创建图

import uuid
from typing import Annotated
from typing_extensions import TypedDict

from langchain_anthropic import ChatAnthropic
from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.memory import MemorySaver
from langgraph.store.base import BaseStore


model = ChatAnthropic(model="claude-3-5-sonnet-20240620")


# NOTE: we're passing the Store param to the node --
# this is the Store we compile the graph with
def call_model(state: MessagesState, config: RunnableConfig, *, store: BaseStore):
    user_id = config["configurable"]["user_id"]
    namespace = ("memories", user_id)
    memories = store.search(namespace, query=str(state["messages"][-1].content))
    info = "\n".join([d.value["data"] for d in memories])
    system_msg = f"You are a helpful assistant talking to the user. User info: {info}"

    # Store new memories if the user asks the model to remember
    last_message = state["messages"][-1]
    if "remember" in last_message.content.lower():
        memory = "User name is Bob"
        store.put(namespace, str(uuid.uuid4()), {"data": memory})

    response = model.invoke(
        [{"role": "system", "content": system_msg}] + state["messages"]
    )
    return {"messages": response}


builder = StateGraph(MessagesState)
builder.add_node("call_model", call_model)
builder.add_edge(START, "call_model")

# NOTE: we're passing the store object here when compiling the graph
graph = builder.compile(checkpointer=MemorySaver(), store=in_memory_store)
# If you're using LangGraph Cloud or LangGraph Studio, you don't need to pass the store or checkpointer when compiling the graph, since it's done automatically.

API 参考: ChatAnthropic | RunnableConfig | StateGraph | START | MemorySaver

注意

如果您正在使用 LangGraph Cloud 或 LangGraph Studio,则在编译图时不需要传递 store,因为它会自动完成。

运行图!

现在让我们在 config 中指定一个用户 ID,并告诉模型我们的名字

config = {"configurable": {"thread_id": "1", "user_id": "1"}}
input_message = {"role": "user", "content": "Hi! Remember: my name is Bob"}
for chunk in graph.stream({"messages": [input_message]}, config, stream_mode="values"):
    chunk["messages"][-1].pretty_print()
================================ Human Message =================================

Hi! Remember: my name is Bob
================================== Ai Message ==================================

Hello Bob! It's nice to meet you. I'll remember that your name is Bob. How can I assist you today?

config = {"configurable": {"thread_id": "2", "user_id": "1"}}
input_message = {"role": "user", "content": "what is my name?"}
for chunk in graph.stream({"messages": [input_message]}, config, stream_mode="values"):
    chunk["messages"][-1].pretty_print()
================================ Human Message =================================

what is my name?
================================== Ai Message ==================================

Your name is Bob.
我们现在可以检查我们的内存存储,并验证我们实际上已经为用户保存了记忆

for memory in in_memory_store.search(("memories", "1")):
    print(memory.value)
{'data': 'User name is Bob'}
现在让我们为另一个用户运行图,以验证关于第一个用户的记忆是自包含的

config = {"configurable": {"thread_id": "3", "user_id": "2"}}
input_message = {"role": "user", "content": "what is my name?"}
for chunk in graph.stream({"messages": [input_message]}, config, stream_mode="values"):
    chunk["messages"][-1].pretty_print()
================================ Human Message =================================

what is my name?
================================== Ai Message ==================================

I apologize, but I don't have any information about your name. As an AI assistant, I don't have access to personal information about users unless it has been specifically shared in our conversation. If you'd like, you can tell me your name and I'll be happy to use it in our discussion.

评论