想象一下与一个朋友交谈,但他忘记了你曾经说过的一切。每次对话都从零开始。没有记忆,没有上下文,没有进展。这会让人感到尴尬、疲惫和不近人情。不幸的是,这正是当今大多数AI系统的表现。它们很聪明,是的,但它们缺少关键的东西:Agentic Memory 记忆。
让我们首先谈谈记忆在AI中的真正含义以及为什么它很重要。
1. 简介
1.1 当今AI中的记忆幻象
像ChatGPT或编程助手这样的工具感觉很有用,直到你发现自己一遍又一遍地重复指令或偏好。要构建能够_学习、进化_和_协作_的智能体,真正的记忆不仅仅是有益的——它是必需的。
这种由上下文窗口和巧妙的提示工程创造的记忆幻象让许多人相信智能体已经"记住"了。实际上,今天的大多数智能体都是无状态的,无法从过去的交互中学习或随时间适应。
要从无状态工具转变为真正智能、自主的**(有状态的)智能体,我们需要给它们记忆**,而不仅仅是更大的提示或更好的检索。
1.2 AI智能体中的记忆是什么意思?
在AI智能体的语境中,记忆是在时间、任务和多个用户交互中保留和回忆相关信息的能力。它允许智能体记住过去发生的事情,并使用这些信息来改善未来的行为。
记忆不是关于仅存储聊天历史或向提示中添加更多tokens。它是关于构建一个持续的内部状态,这个状态会进化并影响智能体的每次交互,即使是相隔数周或数月的交互。
定义智能体记忆的三个支柱:
- **状态:**知道_现在_正在发生什么
- **持久性:**跨会话保留知识
- **选择:**决定什么值得记住
这些共同实现了我们以前从未有过的东西:连续性。
1.3 记忆在智能体技术栈中的位置
MemOS: 大语言模型内存增强生成操作系统
让我们将记忆放在现代智能体的架构中。典型组件:
- 用于推理和答案生成的LLM
- 策略或规划器(例如ReAct、AutoGPT风格)
- 访问工具/APIs
- 用于获取文档或过去数据的检索器
问题在于:**这些组件都不记得昨天发生了什么。**没有内部状态。没有不断发展的理解。没有记忆。
在循环中使用记忆:
这将智能体从单次使用的助手转变为不断进化的协作者。
1.4 上下文窗口 ≠ 记忆
一个常见的误解是大型上下文窗口将消除对记忆的需求。
但这种方法由于某些限制而不足。使用更多上下文调用LLM的主要缺点之一是它们可能是:**昂贵的:**更多tokens = 更高的成本和延迟
上下文窗口帮助智能体在_会话内_保持一致。记忆允许智能体在_会话间_表现智能。即使上下文长度达到100K tokens,缺乏持久性、优先级和显著性使其不足以实现真正的智能。
1.5 为什么RAG与记忆不同
虽然RAG(检索增强生成)和记忆系统都检索信息来支持LLM,但它们解决的是_非常不同的问题_。
- RAG在推理时将外部知识带入提示中。它对于用文档中的事实来支撑响应很有用。
- 但RAG本质上是无状态的——它不知道以前的交互、用户身份或当前查询与过去对话的关系。
另一方面,记忆带来了连续性。它捕获用户偏好、过去的查询、决策和失败,并在未来的交互中使它们可用。
这样想:
RAG帮助智能体回答得更好。记忆帮助智能体表现得更聪明。
你需要两者——RAG来告知LLM,记忆来塑造其行为。
2. 智能体中的记忆类型
在基础层面,AI智能体中的记忆有两种形式:
- 短期记忆:在单次交互中保持即时上下文。
- 长期记忆:跨会话、任务和时间持续知识。
就像在人类中一样,这些记忆类型服务于不同的认知功能。短期记忆帮助智能体在当下保持连贯。长期记忆帮助它学习、个性化和适应。
👉 简单的经验法则:
- 短期记忆 = AI在与你交谈时"现在记住"的内容。
- 长期记忆 = AI在许多对话后"学习并稍后回忆"的内容。
2.1 短期记忆(或工作记忆)
AI系统中最基本的记忆形式保持即时上下文——就像一个人记住刚刚在对话中说过的话。这包括:
- 对话历史:最近的消息及其顺序
- 工作记忆:临时变量和状态
- 注意力上下文:对话的当前焦点
2.2 长期记忆
更复杂的AI应用程序实现长期记忆以跨对话保留信息。
这包括:
2.2.1 程序性记忆
定义你的智能体知道要做什么,直接编码在你的代码中。从简单的模板到复杂的推理流程——这是你的逻辑层。
这是你智能体的"肌肉记忆"——学习的行为变成自动的。就像你打字时不会有意识地思考每个按键一样,你的智能体将内化诸如"总是优先处理关于API文档的电子邮件"或"对技术问题的响应使用更有帮助的语调"这样的过程。
2.2.2 情景记忆(例子)
用户特定的过去交互和经验。它能够实现连续性、个性化和随时间学习。
这是你智能体的"相册"——过去事件和交互的具体记忆。就像你可能记得听到重要新闻时你在哪里一样,你的智能体将记住,“上次这个客户发邮件询问截止日期延期时,我的回应太僵硬了,造成了摩擦"或"当电子邮件包含’快速问题’这个短语时,它们通常需要详细的技术解释,这些解释根本不快速。”
2.2.3 语义记忆(事实)
通过向量搜索或RAG检索的事实性世界知识。
这是你智能体的"百科全书"——它学到的关于你的世界的事实。就像你知道巴黎是法国的首都,而不记得你是何时或如何学到的一样,你的智能体将记住"Alice是API文档的联系人"或"John喜欢早上开会。"这是独立于具体经验而存在的知识。
3. 记忆管理
许多AI应用程序需要记忆来在多个交互中共享上下文。LangGraph支持构建对话智能体所需的两种记忆类型:
- 短期记忆:通过在会话内维护消息历史来跟踪正在进行的对话。
- 长期记忆:跨会话存储用户特定或应用程序级别的数据。
启用短期记忆后,长对话可能超过LLM的上下文窗口。常见解决方案是:
- 修剪:删除前N条或后N条消息(在调用LLM之前)
- 摘要:总结历史中的早期消息并用摘要替换它们
- 从LangGraph状态中永久删除消息
- 自定义策略(例如,消息过滤等)
这允许智能体跟踪对话而不超过LLM的上下文窗口。
4. 写入记忆
虽然人类通常在睡眠期间形成长期记忆,但AI智能体需要不同的方法。智能体应该何时以及如何创建新记忆?智能体写入记忆至少有两种主要方法:“在热路径上"和"在后台”。
4.1 在热路径上写入记忆
在运行时创建记忆带来了优势和挑战。积极的一面,这种方法允许实时更新,使新记忆立即可用于后续交互。它还实现了透明度,因为用户可以在创建和存储记忆时得到通知。
例如,ChatGPT使用一个save_memories工具来更新记忆作为内容字符串,决定是否以及如何在每个用户消息中使用这个工具。
4.2 在后台写入记忆
将创建记忆作为单独的后台任务提供了几个优势。它消除了主应用程序中的延迟,将应用程序逻辑与记忆管理分离,并允许智能体更专注地完成任务。这种方法还在计时记忆创建方面提供了灵活性,以避免冗余工作。
5. 添加短期记忆
短期记忆(线程级持久性)使智能体能够跟踪多轮对话。要添加短期记忆:
from langchain.chat_models import init_chat_model
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.memory import InMemorySaver
model = init_chat_model(model="anthropic:claude-3-5-haiku-latest")
def call_model(state: MessagesState):
response = model.invoke(state["messages"])
return {"messages": response}
builder = StateGraph(MessagesState)
builder.add_node(call_model)
builder.add_edge(START, "call_model")
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
config = {
"configurable": {
"thread_id": "1"
}
}
for chunk in graph.stream(
{"messages": [{"role": "user", "content": "hi! I'm bob"}]},
config,
stream_mode="values",
):
chunk["messages"][-1].pretty_print()
for chunk in graph.stream(
{"messages": [{"role": "user", "content": "what's my name?"}]},
config,
stream_mode="values",
):
chunk["messages"][-1].pretty_print()
5.1 在生产环境中
在生产环境中,你会想要使用由数据库支持的检查点:
示例:使用MongoDB检查点
要使用MongoDB检查点,你需要一个MongoDB集群。如果你还没有集群,请按照这个指南创建一个。
pip install -U pymongo langgraph langgraph-checkpoint-mongodb
from langchain.chat_models import init_chat_model
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.mongodb import MongoDBSaver
model = init_chat_model(model="anthropic:claude-3-5-haiku-latest")
DB_URI = "localhost:27017"
with MongoDBSaver.from_conn_string(DB_URI) as checkpointer:
def call_model(state: MessagesState):
response = model.invoke(state["messages"])
return {"messages": response}
builder = StateGraph(MessagesState)
builder.add_node(call_model)
builder.add_edge(START, "call_model")
graph = builder.compile(checkpointer=checkpointer)
config = {
"configurable": {
"thread_id": "1"
}
}
for chunk in graph.stream(
{"messages": [{"role": "user", "content": "hi! I'm bob"}]},
config,
stream_mode="values"
):
chunk["messages"][-1].pretty_print()
for chunk in graph.stream(
{"messages": [{"role": "user", "content": "what's my name?"}]},
config,
stream_mode="values"
):
chunk["messages"][-1].pretty_print()
5.2 使用子图
如果你的图包含子图,你只需要在编译父图时提供检查点。LangGraph会自动将检查点传播到子图。
from langgraph.graph import START, StateGraph
from langgraph.checkpoint.memory import InMemorySaver
from typing import TypedDict
class State(TypedDict):
foo: str
# 子图
def subgraph_node_1(state: State):
return {"foo": state["foo"] + "bar"}
subgraph_builder = StateGraph(State)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph = subgraph_builder.compile()
# 父图
def node_1(state: State):
return {"foo": "hi! " + state["foo"]}
builder = StateGraph(State)
builder.add_node("node_1", subgraph)
builder.add_edge(START, "node_1")
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
5.3 管理检查点
你可以查看和删除检查点存储的信息:
查看线程状态(检查点)
config = {
"configurable": {
"thread_id": "1",
# 可选择提供特定检查点的ID,
# 否则显示最新检查点
# "checkpoint_id": "1f029ca3-1f5b-6704-8004-820c16b69a5a"
}
}
graph.get_state(config)
查看线程历史(检查点)
config = {
"configurable": {
"thread_id": "1"
}
}
list(graph.get_state_history(config))
删除线程的所有检查点
thread_id = "1"
checkpointer.delete_thread(thread_id)
6. 添加长期记忆
使用长期记忆(跨线程持久性)来跨对话存储用户特定或应用程序特定的数据。这对于聊天机器人等应用程序很有用,你想要记住用户偏好或其他信息。
要使用长期记忆,我们需要在创建图时提供一个存储:
import uuid
from typing_extensions import Annotated, TypedDict
from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import BaseStore
model = init_chat_model(model="anthropic:claude-3-5-haiku-latest")
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}"
# 如果用户要求模型记住,则存储新记忆
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)
builder.add_edge(START, "call_model")
checkpointer = InMemorySaver()
store = InMemoryStore()
graph = builder.compile(
checkpointer=checkpointer,
store=store,
)
config = {
"configurable": {
"thread_id": "1",
"user_id": "1",
}
}
for chunk in graph.stream(
{"messages": [{"role": "user", "content": "Hi! Remember: my name is Bob"}]},
config,
stream_mode="values",
):
chunk["messages"][-1].pretty_print()
config = {
"configurable": {
"thread_id": "2",
"user_id": "1",
}
}
for chunk in graph.stream(
{"messages": [{"role": "user", "content": "what is my name?"}]},
config,
stream_mode="values",
):
chunk["messages"][-1].pretty_print()
6.1 在生产环境中
在生产环境中,你会想要使用由数据库支持的检查点:
示例:使用Postgres存储
你需要在第一次使用Postgres存储时调用store.setup()
pip install -U "psycopg[binary,pool]" langgraph langgraph-checkpoint-postgres
from langchain_core.runnables import RunnableConfig
from langchain.chat_models import init_chat_model
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.store.postgres import PostgresStore
from langgraph.store.base import BaseStore
model = init_chat_model(model="anthropic:claude-3-5-haiku-latest")
DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"
with (
PostgresStore.from_conn_string(DB_URI) as store,
PostgresSaver.from_conn_string(DB_URI) as checkpointer,
):
# store.setup()
# checkpointer.setup()
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}"
# 如果用户要求模型记住,则存储新记忆
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)
builder.add_edge(START, "call_model")
graph = builder.compile(
checkpointer=checkpointer,
store=store,
)
config = {
"configurable": {
"thread_id": "1",
"user_id": "1",
}
}
for chunk in graph.stream(
{"messages": [{"role": "user", "content": "Hi! Remember: my name is Bob"}]},
config,
stream_mode="values",
):
chunk["messages"][-1].pretty_print()
config = {
"configurable": {
"thread_id": "2",
"user_id": "1",
}
}
for chunk in graph.stream(
{"messages": [{"role": "user", "content": "what is my name?"}]},
config,
stream_mode="values",
):
chunk["messages"][-1].pretty_print()
6.2 语义搜索
你可以在图的记忆存储中启用语义搜索:这让图智能体通过语义相似性搜索存储中的项目。
from typing import Optional
from langchain.embeddings import init_embeddings
from langchain.chat_models import init_chat_model
from langgraph.store.base import BaseStore
from langgraph.store.memory import InMemoryStore
from langgraph.graph import START, MessagesState, StateGraph
llm = init_chat_model("openai:gpt-4o-mini")
# 创建启用语义搜索的存储
embeddings = init_embeddings("openai:text-embedding-3-small")
store = InMemoryStore(
index={
"embed": embeddings,
"dims": 1536,
}
)
store.put(("user_123", "memories"), "1", {"text": "I love pizza"})
store.put(("user_123", "memories"), "2", {"text": "I am a plumber"})
def chat(state, *, store: BaseStore):
# 基于用户的最后一条消息搜索
items = store.search(
("user_123", "memories"), query=state["messages"][-1].content, limit=2
)
memories = "\n".join(item.value["text"] for item in items)
memories = f"## Memories of user\n{memories}" if memories else ""
response = llm.invoke(
[
{"role": "system", "content": f"You are a helpful assistant.\n{memories}"},
*state["messages"],
]
)
return {"messages": [response]}
builder = StateGraph(MessagesState)
builder.add_node(chat)
builder.add_edge(START, "chat")
graph = builder.compile(store=store)
for message, metadata in graph.stream(
input={"messages": [{"role": "user", "content": "I'm hungry"}]},
stream_mode="messages",
):
print(message.content, end="")
在设计你的智能体时,以下问题可以作为有用的指南:
- 什么类型的内容应该你的智能体学习:事实/知识?过去事件的摘要?规则和风格?
- 何时应该形成记忆(以及谁应该形成记忆)
- 在哪里应该存储记忆?(在提示中?语义存储中?)。这在很大程度上决定了它们将如何被回忆。
7. 构建电子邮件智能体:分步指南
通过将所有三种类型的长期记忆编织在一起,我们的智能体变得真正智能和个性化。想象一个助手:
- 注意到来自特定客户的电子邮件如果在24小时内没有回答通常需要跟进
- 学习你的写作风格和语调,为外部通信调整正式回应,为团队成员调整随意回应
- 记住复杂的项目上下文,而不需要你重复解释
- 更好地预测你想看到哪些电子邮件与自动处理哪些电子邮件
工作流程:
START → 分流(情景 + 程序性记忆)→ 决策 → 响应智能体(语义 + 程序性记忆)→ END
让我们通过实现它来将这个愿景变成现实!😃
导入和设置
!pip install langmem langchain_community python-dotenv --quiet
# ===============================================================================
# 导入
# ===============================================================================
import os
import warnings
import numpy as np
from dotenv import load_dotenv
from typing import TypedDict, Literal, Annotated, List
# LangGraph和LangChain导入
from langgraph.graph import StateGraph, START, END, add_messages
from langgraph.prebuilt import create_react_agent
from langgraph.store.memory import InMemoryStore
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langchain.prompts import PromptTemplate
from langchain.schema import HumanMessage
from pydantic import BaseModel, Field
# 用于记忆工具的LangMem导入
from langmem import create_manage_memory_tool, create_search_memory_tool, create_multi_prompt_optimizer
# 用于可视化的IPython
from IPython.display import Image, display
# 抑制来自嵌入计算的numpy警告
warnings.filterwarnings("ignore", category=RuntimeWarning, module="numpy")
warnings.filterwarnings("ignore", category=RuntimeWarning, module="langgraph")
现在,让我们初始化我们的环境和工具:
%env OPENAI_API_KEY=<your_openai_api_key>
# ===============================================================================
# 配置
# ===============================================================================
# 加载环境变量
load_dotenv()
# 用户配置
USER_ID = "test_user"
CONFIG = {"configurable": {"langgraph_user_id": USER_ID}}
# 初始化核心组件
llm = init_chat_model("openai:gpt-4o-mini")
store = InMemoryStore(index={"embed": "openai:text-embedding-3-small"})
记忆存储在这里特别重要——它就像给我们的智能体一个大脑,可以存储和检索信息。我们使用内存存储以简化,但在生产环境中,你可能想要使用持久数据库。
7.1 定义智能体的"大脑":状态
现在我们需要设计智能体的工作记忆——它在处理任务时保持追踪正在进行工作的心理草稿板。这与我们稍后将实现的长期记忆不同——它更像是你在工作任务时积极保持在心中的事情。
class State(TypedDict):
"""
电子邮件智能体工作流的状态管理。
这个状态在LangGraph工作流中的节点之间传递,包含:
- email_input:要处理的传入电子邮件
- messages:响应智能体的对话历史
- triage_result:分流的分类结果('ignore'、'notify'、'respond')
状态在工作流中移动时会发展,积累信息
帮助智能体做出更好的决策并提供上下文响应。
"""
email_input: dict # 传入的电子邮件
messages: Annotated[list, add_messages] # 对话历史
triage_result: str # 分流的结果(ignore、notify、respond)
这个State对象包含三个关键信息:
- 当前正在处理的电子邮件
- 正在进行的对话(如果有的话)
- 关于如何处理电子邮件的决策
把它想象成医生在病房巡视时的剪贴板——它包含做出决策所需的即时信息,而患者的完整病史仍然在病历室(我们的记忆存储)中。
7.2 分流中心:决定做什么(使用情景记忆)
首先,我们将创建一个结构,让我们的智能体解释其推理和分类:
class Router(BaseModel):
"""
电子邮件分流分类的结构化输出模型。
这个模型确保LLM以一致的格式提供推理和分类,
使分流过程透明且可调试。
推理字段帮助我们理解智能体如何做出决策,
这对于改进提示和训练示例至关重要。
"""
reasoning: str = Field(description="分类背后的逐步推理。")
classification: Literal["ignore", "respond", "notify"] = Field(
description="电子邮件的分类:'ignore'、'notify'或'respond'。"
)
# 使用结构化输出初始化LLM路由器
llm_router = llm.with_structured_output(Router)
为了利用情景记忆,我们需要一种方法来格式化过去交互的示例:
def format_few_shot_examples(examples):
"""
为少样本学习格式化情景记忆示例。
这个函数将存储的电子邮件示例从情景记忆
转换为可以包含在分流提示中的格式化字符串。
Args:
examples: 来自memory.search()的存储电子邮件示例列表
Returns:
str: 用于少样本学习的格式化示例字符串
"""
formatted_examples = []
for eg in examples:
email = eg.value['email']
label = eg.value['label']
formatted_examples.append(
f"From: {email['author']}\nSubject: {email['subject']}\nBody: {email['email_thread'][:300]}...\n\nClassification: {label}"
)
return "\n\n".join(formatted_examples)
这个函数将我们存储的示例转换为帮助模型从中学习的格式,就像向新员工展示带有如何处理不同情况注释示例的培训手册。
现在,让我们创建使用情景记忆的电子邮件分流功能:
def triage_email(state: State, config: dict, store: InMemoryStore) -> dict:
"""
使用情景记忆的基本电子邮件分流功能。
这个函数使用以下方式对传入的电子邮件进行分类:
1. 静态提示模板
2. 来自情景记忆的少样本示例
Args:
state: 包含email_input的当前工作流状态
config: 包含用于记忆命名空间的user_id的配置
store: 用于访问情景记忆的InMemoryStore
Returns:
dict: 带有triage_result的更新状态
"""
email = state["email_input"]
user_id = config["configurable"]["langgraph_user_id"]
namespace = ("email_assistant", user_id, "examples") # 情景记忆的命名空间
# 从记忆中检索相关示例
examples = store.search(namespace, query=str(email))
formatted_examples = format_few_shot_examples(examples)
prompt_template = PromptTemplate.from_template("""你是一个电子邮件分流助手。对以下电子邮件进行分类:
From: {author}
To: {to}
Subject: {subject}
Body: {email_thread}
分类为'ignore'、'notify'或'respond'。
以下是一些以前分类的示例:
{examples}
""")
prompt = prompt_template.format(examples=formatted_examples, **email)
messages = [HumanMessage(content=prompt)]
result = llm_router.invoke(messages)
return {"triage_result": result.classification}
这个函数是情景记忆工作的核心。当新邮件到达时,它不会孤立地分析它——它搜索过去的类似邮件,看看这些邮件是如何处理的。这就像一个医生记住,“最后三个有这些症状的患者对这种治疗反应良好。”
7.3 使用语义记忆定义工具
现在让我们给我们的智能体一些工具来使用。首先,写邮件和检查日历的基本能力:
@tool
def write_email(to: str, subject: str, content: str) -> str:
"""
用于撰写和发送电子邮件回复的工具。
这个工具可用于响应智能体,允许它:
- 起草专业的电子邮件回复
- 发送对电子邮件询问的回复
- 处理电子邮件通信任务
Args:
to: 收件人电子邮件地址
subject: 电子邮件主题行
content: 电子邮件正文内容
Returns:
str: 电子邮件发送的确认消息
"""
print(f"Sending email to {to} with subject '{subject}'\nContent:\n{content}\n")
return f"Email sent to {to} with subject '{subject}'"
@tool
def check_calendar_availability(day: str) -> str:
"""
用于检查日历可用性的工具。
这个工具允许响应智能体:
- 检查可用的会议时间
- 安排约会
- 提供日历信息
Args:
day: 要检查可用性的日期
Returns:
str: 指定日期的可用时间段
"""
return f"Available times on {day}: 9:00 AM, 2:00 PM, 4:00 PM"
请注意,这些是用于演示目的的简化实现。在生产环境中,你会将这些函数连接到实际的电子邮件和日历API(如Gmail API或Microsoft Graph API)。例如,write_email
函数会与真实的电子邮件服务交互来发送消息,check_calendar_availability
会查询你的实际日历数据。
现在,让我们添加语义记忆工具——我们智能体存储和检索关于世界事实的能力:
# ===============================================================================
# 工具配置
# ===============================================================================
# 创建LangMem记忆工具
manage_memory_tool = create_manage_memory_tool(namespace=("email_assistant", "{langgraph_user_id}", "collection"))
search_memory_tool = create_search_memory_tool(namespace=("email_assistant", "{langgraph_user_id}", "collection"))
# 响应智能体的所有可用工具
tools = [write_email, check_calendar_availability, manage_memory_tool, search_memory_tool]
7.4 响应智能体:创建核心助手(使用语义记忆)
我们现在将创建使用所有记忆系统处理响应的核心智能体:
def create_agent_prompt(state, config, store):
"""
使用程序性记忆为响应智能体创建动态提示。
这个函数演示了程序性记忆如何实现自适应行为:
1. 从记忆存储中检索当前响应提示
2. 将其与对话历史结合
3. 返回为LLM正确格式化的提示
Args:
state: 带有消息历史的当前工作流状态
config: 包含用于记忆访问的user_id的配置
store: 用于访问程序性记忆的InMemoryStore
Returns:
list: 为LLM格式化的消息,包括系统提示 + 历史
"""
messages = state['messages']
user_id = config["configurable"]["langgraph_user_id"]
print("响应智能体:使用自适应系统提示初始化")
# 从程序性记忆获取当前响应提示
system_prompt = store.get(("email_assistant", user_id, "prompts"), "response_prompt")
print("程序性记忆:检索到当前响应提示")
# 确保系统提示是字符串
if not isinstance(system_prompt, str):
system_prompt = str(system_prompt.value) if hasattr(system_prompt, 'value') else str(system_prompt)
return [{"role": "system", "content": system_prompt}] + messages
这个函数创建一个从程序性记忆中提取指令并传递当前对话的提示。这就像一个经理在响应复杂情况之前检查公司手册,确保他们遵循最新的协议。
请注意,我们现在已经设置了两个关键的记忆系统:
- 情景记忆(在分流功能中)
- 语义记忆(在这些智能体工具中)
但我们仍然需要添加程序性记忆来完成我们智能体的认知能力。这将在接下来的部分中出现,我们将使我们的智能体能够基于反馈随时间改善其行为。
7.5 构建图:连接各个部分
现在,让我们将一切整合到一个连贯的工作流中:
def create_basic_email_agent(store):
"""
工厂函数,用于创建仅使用情景记忆的基本电子邮件处理智能体。
这个函数创建一个更简单的工作流,仅用于比较目的,只使用:
1. 情景记忆:过去电子邮件示例的少样本学习
Args:
store: 包含情景记忆的InMemoryStore
Returns:
CompiledGraph: 具有基本记忆功能的可执行工作流
"""
# 定义工作流
workflow = StateGraph(State)
# 使用基本分流功能(仅情景记忆)
workflow.add_node("triage", lambda state, config: triage_email(state, config, store))
# 使用静态提示创建基本响应智能体
response_agent = create_react_agent(
tools=tools,
prompt=create_agent_prompt,
store=store,
model=llm
)
workflow.add_node("response_agent", response_agent)
def route_based_on_triage(state):
if state["triage_result"] == "respond":
return "response_agent"
else:
return END
# 路由逻辑保持不变
workflow.add_edge(START, "triage")
workflow.add_conditional_edges("triage", route_based_on_triage,
{
"response_agent": "response_agent",
END: END
})
# 编译并返回图
return workflow.compile(store=store)
这个工作流定义了我们智能体的逻辑路径:
- 首先,使用情景记忆对传入的电子邮件进行分流
- 如果需要响应,使用语义记忆激活响应智能体
- 否则,结束过程(对于"忽略"或"通知"电子邮件)
这就像在工厂中设置装配线站——每个部分都有自己的工作,但它们一起工作创造最终产品。
7.6 运行它!(并存储一些记忆)
是时候测试我们的智能体了:
# 样本数据和初始化
# ===============================================================================
# 用于测试的样本电子邮件
email_input = {
"author": "Alice Smith <alice.smith@company.com>",
"to": "John Doe <john.doe@company.com>",
"subject": "Quick question about API documentation",
"email_thread": """Hi John,
I was reviewing the API documentation and noticed a few endpoints are missing. Could you help?
Thanks,
Alice""",
}
# 初始提示
initial_triage_prompt = """你是一个电子邮件分流助手。对以下电子邮件进行分类:
From: {author}
To: {to}
Subject: {subject}
Body: {email_thread}
分类为'ignore'、'notify'或'respond'。
以下是一些以前分类的示例:
{examples}
"""
initial_response_prompt = """你是一个有用的助手。使用可用的工具,包括记忆工具,来协助用户。"""
让我们还向我们的情景记忆添加一个训练示例,以帮助智能体在未来识别垃圾邮件:
def initialize_memory():
"""使用默认示例和提示初始化记忆"""
# 向情景记忆添加少样本示例
example1 = {
"email": {
"author": "Spammy Marketer <spam@example.com>",
"to": "John Doe <john.doe@company.com>",
"subject": "BIG SALE!!!",
"email_thread": "Buy our product now and get 50% off!",
},
"label": "ignore",
}
store.put(("email_assistant", USER_ID, "examples"), "spam_example", example1)
# 使用默认提示初始化程序性记忆
store.put(("email_assistant", USER_ID, "prompts"), "triage_prompt", initial_triage_prompt)
store.put(("email_assistant", USER_ID, "prompts"), "response_prompt", initial_response_prompt)
这就像训练新助手:"看到这种邮件了吗?你可以安全地忽略这些。"我们提供的示例越多,智能体的理解就越细致。
7.7 添加程序性记忆(更新指令)——最后的点睛之笔!
现在,对于最复杂的记忆系统,程序性记忆允许我们的智能体基于反馈改善其指令。
让我们创建一个从记忆中提取指令的分流功能版本:
def triage_email_with_procedural_memory(state: State, config: dict, store: InMemoryStore) -> dict:
"""
使用情景和程序性记忆的高级电子邮件分流。
这是自适应学习系统的核心,结合:
1. 程序性记忆:随时间改善的动态提示
2. 情景记忆:过去分类的少样本示例
Args:
state: 包含email_input的当前工作流状态
config: 包含用于记忆命名空间的user_id的配置
store: 具有两种记忆类型的InMemoryStore
Returns:
dict: 带有triage_result分类的更新状态
"""
email = state["email_input"]
user_id = config["configurable"]["langgraph_user_id"]
print(f"分流:分析来自{email['author']}的电子邮件,主题:'{email['subject']}'")
# 检索当前分流提示(程序性记忆)
current_prompt_template = store.get(("email_assistant", user_id, "prompts"), "triage_prompt")
print("程序性记忆:检索到当前分流提示")
# 确保提示模板是字符串
if not isinstance(current_prompt_template, str):
current_prompt_template = str(current_prompt_template) # 如果是Item对象则转换为字符串
# 从记忆中检索相关示例(情景记忆)
namespace = ("email_assistant", user_id, "examples")
examples = store.search(namespace, query=str(email))
formatted_examples = format_few_shot_examples(examples)
print(f"情景记忆:从过去分类中找到{len(examples)}个相关示例")
# 格式化提示
prompt = PromptTemplate.from_template(current_prompt_template).format(examples=formatted_examples, **email)
messages = [HumanMessage(content=prompt)]
result = llm_router.invoke(messages)
print(f"分流结果:{result.classification}")
print(f"推理:{result.reasoning}")
return {"triage_result": result.classification}
这个函数将程序性记忆(当前提示模板)与情景记忆(相关示例)集成,以做出分流决策。
现在,让我们创建一个可以基于反馈改进我们提示的函数:
def optimize_prompts(feedback: str, config: dict, store: InMemoryStore):
"""
程序性记忆学习:基于性能反馈优化提示。
这个函数实现了实现渐进改善的核心学习机制:
1. 通过反馈分析当前提示性能
2. 使用AI驱动的优化来改进提示
3. 使用更好的提示更新程序性记忆
4. 未来的智能体实例自动使用改进的提示
Args:
feedback: 描述性能问题的人工反馈
config: 包含用于记忆访问的user_id的配置
store: 用于更新程序性记忆的InMemoryStore
Returns:
str: 关于改进的确认消息
示例演进:
初始:"今天我如何协助您?"
反馈后:"我如何协助您处理API文档?"
"""
print("\n优化:开始提示改进过程...")
user_id = config["configurable"]["langgraph_user_id"]
# 获取当前提示
print("检索:从程序性记忆获取当前提示")
triage_prompt = store.get(("email_assistant", user_id, "prompts"), "triage_prompt").value
response_prompt = store.get(("email_assistant", user_id, "prompts"), "response_prompt").value
# 基于我们的实际电子邮件创建更相关的测试示例
sample_email = {
"author": "Alice Smith <alice.smith@company.com>",
"to": "John Doe <john.doe@company.com>",
"subject": "Quick question about API documentation",
"email_thread": "Hi John, I was reviewing the API documentation and noticed a few endpoints are missing. Could you help? Thanks, Alice",
}
print("分析:使用反馈创建对话轨迹")
# 创建优化器
optimizer = create_multi_prompt_optimizer(llm)
# 创建更相关的带有反馈的对话轨迹
conversation = [
{"role": "system", "content": response_prompt},
{"role": "user", "content": f"I received this email: {sample_email}"},
{"role": "assistant", "content": "How can I assist you today?"}
]
# 格式化提示
prompts = [
{"name": "triage", "prompt": triage_prompt},
{"name": "response", "prompt": response_prompt}
]
# 更相关的轨迹
trajectories = [(conversation, {"feedback": feedback})]
print("优化:使用AI基于反馈改进提示...")
result = optimizer.invoke({"trajectories": trajectories, "prompts": prompts})
# 提取改进的提示
improved_triage_prompt = next(p["prompt"] for p in result if p["name"] == "triage")
improved_response_prompt = next(p["prompt"] for p in result if p["name"] == "response")
# 为API文档问题添加特定指令
improved_triage_prompt = improved_triage_prompt + "\n\n特别关注关于API文档或缺失端点的电子邮件——这些是高优先级的,应该总是分类为'respond'。"
improved_response_prompt = improved_response_prompt + "\n\n在回应关于文档或API问题的电子邮件时,承认提到的具体问题并提供具体帮助,而不是通用回应。"
print("存储:在程序性记忆中更新提示")
# 存储改进的提示
store.put(("email_assistant", user_id, "prompts"), "triage_prompt", improved_triage_prompt)
store.put(("email_assistant", user_id, "prompts"), "response_prompt", improved_response_prompt)
print("改进完成:提示已增强!")
print(f"分流提示预览:{improved_triage_prompt[:100]}...")
print(f"响应提示预览:{improved_response_prompt[:100]}...")
return "基于反馈改进了提示!"
这个函数是程序性记忆的精髓。它接受诸如"你没有正确优先处理API文档电子邮件"这样的反馈,并使用它来重写智能体的核心指令。优化器就像一个教练观看比赛录像,研究哪里出了问题并更新战术手册。它分析对话示例和反馈,然后完善指导智能体行为的提示。它不是简单地记住特定的更正,而是将潜在的经验教训吸收到其整体方法中,类似于厨师如何基于顾客反馈改进食谱,而不是每次简单地遵循不同的指令。
7.8 运行我们完整的记忆增强智能体!
现在让我们将一切整合到一个可以随时间进化的完整系统中:
def create_email_agent(store):
"""
工厂函数,用于创建支持记忆的电子邮件处理智能体。
这个函数构建一个结合所有三种记忆类型的LangGraph工作流:
1. 情景记忆:过去电子邮件示例的少样本学习
2. 语义记忆:通过记忆工具的上下文信息
3. 程序性记忆:随时间改善的自适应提示
Args:
store: 包含所有记忆系统的InMemoryStore
Returns:
CompiledGraph: 具有记忆功能的可执行工作流
"""
# 定义工作流
workflow = StateGraph(State)
workflow.add_node("triage", lambda state, config: triage_email_with_procedural_memory(state, config, store))
# 创建一个将使用最新提示的新响应智能体
response_agent = create_react_agent(
tools=tools,
prompt=create_agent_prompt,
store=store,
model=llm
)
workflow.add_node("response_agent", response_agent)
def route_based_on_triage(state):
if state["triage_result"] == "respond":
return "response_agent"
else:
return END
# 路由逻辑保持不变
workflow.add_edge(START, "triage")
workflow.add_conditional_edges("triage", route_based_on_triage,
{
"response_agent": "response_agent",
END: END
})
# 编译并返回图
return workflow.compile(store=store)
这个函数创建一个使用我们提示的最新版本的新智能体——确保它总是反映我们最新的学习和反馈。
让我们运行两次——一次使用原始设置,一次在我们提供反馈改进后:
# ===============================================================================
# 主要执行:渐进改善的演示
# ===============================================================================
"""
这个演示显示智能体如何随时间学习和改善:
1. 优化前:智能体使用初始提示和最少示例
2. 记忆积累:向情景记忆添加新示例
3. 反馈处理:人工反馈触发提示优化
4. 优化后:智能体使用带有更多示例的改进提示
关键洞察:每次运行都受益于所有以前的学习!
"""
def run_demonstration():
"""支持记忆的学习系统的主要演示"""
print("🚀 开始支持记忆的电子邮件智能体演示")
print("=" * 60)
# 使用默认示例和提示初始化记忆
print("📚 初始化:设置记忆系统...")
initialize_memory()
print("✅ 记忆已初始化:情景和程序性记忆就绪")
# 演示设置
inputs = {"email_input": email_input, "messages": []}
# 比较:基本vs高级智能体
print("\n" + "=" * 60)
print("📧 阶段1:基本智能体(仅情景记忆)")
print("=" * 60)
basic_agent = create_basic_email_agent(store)
print("🏗️ 基本智能体已创建:仅使用情景记忆")
print("\n🔄 基本工作流执行:")
for output in basic_agent.stream(inputs, config=CONFIG):
for key, value in output.items():
if key not in ['triage', 'response_agent']: # 跳过内部节点输出
print(f"-----\n{key}:")
print(value)
print("-----")
# 高级智能体 - 优化前
print("\n" + "=" * 60)
print("📧 阶段2:高级智能体(优化前)")
print("=" * 60)
agent = create_email_agent(store)
print("🏗️ 高级智能体已创建:使用情景 + 程序性记忆")
print("\n🔄 高级工作流执行(学习前):")
for output in agent.stream(inputs, config=CONFIG):
for key, value in output.items():
if key not in ['triage', 'response_agent']: # 跳过内部节点输出
print(f"-----\n{key}:")
print(value)
print("-----")
print("\n" + "=" * 60)
print("🧠 阶段3:记忆增强 & 反馈")
print("=" * 60)
# 向情景记忆添加具体示例
api_doc_example = {
"email": {
"author": "Developer <dev@company.com>",
"to": "John Doe <john.doe@company.com>",
"subject": "API Documentation Issue",
"email_thread": "Found missing endpoints in the API docs. Need urgent update.",
},
"label": "respond",
}
store.put(("email_assistant", USER_ID, "examples"), "api_doc_example", api_doc_example)
print("📚 情景记忆:添加了API文档示例")
# 提供优化反馈
feedback = """智能体没有正确识别关于API文档问题的电子邮件
是高优先级的,需要立即关注。当电子邮件提到
'API文档'时,应该总是分类为'respond'并使用有帮助的语调。
另外,智能体不应该只是回应'今天我如何协助您?',而应该
承认提到的具体文档问题并提供帮助。"""
print("💬 收到反馈:性能改进建议")
# 基于反馈优化提示
optimize_prompts(feedback, CONFIG, store)
# 在优化后使用新的智能体处理相同的电子邮件
print("\n" + "=" * 60)
print("📧 阶段4:高级智能体(优化后)")
print("=" * 60)
new_agent = create_email_agent(store)
print("🏗️ 优化智能体已创建:具有优化记忆状态的新智能体")
print("\n🔄 高级工作流执行(学习后):")
for output in new_agent.stream(inputs, config=CONFIG):
for key, value in output.items():
if key not in ['triage', 'response_agent']: # 跳过内部节点输出
print(f"-----\n{key}:")
print(value)
print("-----")
print("\n" + "=" * 60)
print("🎉 演示完成:比较显示了支持记忆的学习!")
print("📊 结果摘要:")
print(" 1️⃣ 基本智能体:静态提示,仅情景记忆")
print(" 2️⃣ 高级智能体(前):初始程序性记忆 + 情景记忆")
print(" 3️⃣ 高级智能体(后):优化的程序性记忆 + 增强的情景记忆")
print("=" * 60)
# 运行演示
if __name__ == "__main__":
run_demonstration()
让我们看看输出:
🚀 开始支持记忆的电子邮件智能体演示
============================================================
📚 初始化:设置记忆系统...
✅ 记忆已初始化:情景和程序性记忆就绪
============================================================
📧 阶段1:基本智能体(仅情景记忆)
============================================================
🏗️ 基本智能体已创建:仅使用情景记忆
🔄 基本工作流执行:
-----
响应智能体:使用自适应系统提示初始化
程序性记忆:检索到当前响应提示
-----
============================================================
📧 阶段2:高级智能体(优化前)
============================================================
🏗️ 高级智能体已创建:使用情景 + 程序性记忆
🔄 高级工作流执行(学习前):
分流:分析来自Alice Smith <alice.smith@company.com>的电子邮件,主题:'Quick question about API documentation'
程序性记忆:检索到当前分流提示
情景记忆:从过去分类中找到2个相关示例
分流结果:respond
推理:来自Alice Smith的电子邮件是关于API文档中缺失端点的帮助请求。这表明需要回应来协助她解决问题,因为这涉及可能影响她工作或团队工作的重要文档。与垃圾邮件或非紧急询问不同,这是一个有效的工作相关请求,需要回应。
-----
响应智能体:使用自适应系统提示初始化
程序性记忆:检索到当前响应提示
-----
============================================================
🧠 阶段3:记忆增强 & 反馈
============================================================
📚 情景记忆:添加了API文档示例
💬 收到反馈:性能改进建议
优化:开始提示改进过程...
检索:从程序性记忆获取当前提示
分析:使用反馈创建对话轨迹
优化:使用AI基于反馈改进提示...
存储:在程序性记忆中更新提示
改进完成:提示已增强!
分流提示预览:你是一个电子邮件分流助手。基于内容对以下电子邮件进行分类并确定...
响应提示预览:你是一个有用的助手。使用可用的工具,包括记忆工具,来协助用户。当...
============================================================
📧 阶段4:高级智能体(优化后)
============================================================
🏗️ 优化智能体已创建:具有优化记忆状态的新智能体
🔄 高级工作流执行(学习后):
分流:分析来自Alice Smith <alice.smith@company.com>的电子邮件,主题:'Quick question about API documentation'
程序性记忆:检索到当前分流提示
情景记忆:从过去分类中找到2个相关示例
分流结果:respond
推理:来自Alice Smith的电子邮件讨论API文档中的缺失端点,这完全符合分流提示中概述的高优先级关键短语。提示明确说明此类电子邮件应该总是分类为'respond'。考虑到API文档对工作流的重要性,有必要提供帮助。
-----
响应智能体:使用自适应系统提示初始化
程序性记忆:检索到当前响应提示
-----
============================================================
🎉 演示完成:比较显示了支持记忆的学习!
📊 结果摘要:
1️⃣ 基本智能体:静态提示,仅情景记忆
2️⃣ 高级智能体(前):初始程序性记忆 + 情景记忆
3️⃣ 高级智能体(后):优化的程序性记忆 + 增强的情景记忆
============================================================
看看响应的差异!在我们的反馈之后,智能体应该:
- 一致地识别API文档问题为高优先级
- 提供更具体、有帮助的响应,承认实际问题
- 提供具体帮助而不是通用的客套话
8. 结论
记忆是智能AI智能体的基石,使它们能够保留、回忆和适应信息。我们探索了短期和长期记忆——程序性、情景性和语义性——如何集成到智能体技术栈中,超越了仅仅的上下文窗口或RAG。通过构建记忆增强的电子邮件智能体,我们演示了这些概念的实际应用,从状态管理到语义搜索和自适应指令。掌握记忆使AI能够提供个性化、上下文感知的解决方案,为在生产环境中更智能、更有能力的系统铺平道路。