[Deep Agents:LangChain的Agent Harness-09]利用MemoryMiddleware构建能够自我学习和进化的Agent

Deep Agents将MemoryMiddleware管理的Memory视为Agent的核心资产,并将其作为一等公民。Memory与工具、模型具有同等的重要性,是Agent正常运转的必备组件。Memory建立在由Backend抽象出来的文件系统上,意味着这个所谓的记忆不再存储在不可见的内存或复杂的向量数据库里,而是变成了看得见、摸得着、改得了的磁盘文件。Agent像写日记一样记录经验,而我们可以像管理硬盘一样管理它的灵魂。基于文件系统的Memory设计带来了几个显著的优势:

  • 可解释性:我们直接查看文件内容知道Agent记了什么。如果出现了推理问题,我们查看文件确实是否源于记忆错乱
  • 易于管理和调试:如果它记错了,我们只需要手动删掉或改掉那个文件,它的Memory就立刻变了;

MemoryMiddleware提供了一个持久化的记忆系统,使得Agent能够跨对话会话积累和利用知识。这种记忆系统不仅可以存储事实性的知识,还可以记录经验和学习过程,从而使得Agent具备了学习和进化的能力。借助于MemoryMiddleware,Agent可以在每次对话结束时将新的知识写入到Memory中,并且在下一次对话开始时从Memory中读取之前积累的知识来指导当前的对话。这种机制使得Agent能够不断地学习和适应用户的需求,提供更加个性化和智能化的服务。

1. 让订餐助手记住你的饮食习惯

下面的示例展示了如何利用MemoryMiddleware来构建一个具有学习能力的订餐Agent。这个Agent会根据用户的饮食习惯来处理订单请求,并且在用户提到新的饮食习惯或忌口时,立即将这些信息写入到Memory中,以便在未来的对话中参考和使用。

from typing import Literal
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call
from langchain.tools import tool
from deepagents.middleware import MemoryMiddleware,FilesystemMiddleware
from langchain_openai import ChatOpenAI
from deepagents.backends import FilesystemBackend
from dotenv import load_dotenv
import asyncio

load_dotenv()

@tool
def place_order(dish_name:Literal["麻婆豆腐","辣椒炒肉","剁椒鱼头","番茄炒蛋","清蒸鲈鱼","清炒菜心"], quantity:int):
    """订餐工具,提供菜名和数量,返回订单信息"""
    return f"Order placed: {quantity} x {dish_name}"

backend=FilesystemBackend(root_dir="./agent_data", virtual_mode=True)
file_system_middleware = FilesystemMiddleware(backend=backend)
memory_middleware = MemoryMiddleware(backend=backend, sources=["preferences.md"])

agent = create_agent(
    model=ChatOpenAI(model="gpt-5.2-chat"),
    tools=[place_order],
    system_prompt=(
        "你是一个私人订餐助手,帮助用户下单。请使用`place_order`工具尽可以根据用户的饮食习惯来处理订单请求"
        "无需用户确认,自己选择菜品和数量来下单。"
        "如果用户提到了新的饮食习惯或忌口,立即写入`preferences.md`中"),
    middleware=[file_system_middleware, memory_middleware] # type: ignore
)

async def main():
    result = await agent.ainvoke(input = {"messages": [{
        "role": "user", 
        "content": "帮我点一份外卖,我不能吃辣"
    }]})
    print(result["messages"][-1].content)
    
    result = await agent.ainvoke(input = {"messages": [{
        "role": "user", 
        "content": "帮我点一份外卖"
    }]})
    print(result["messages"][-1].content)
asyncio.run(main())

这个演示实例利用create_agent创建的Agent注册了用来点餐的place_order工具。注册的FilesystemMiddlewareMemoryMiddleware都采用同一个FilesystemBackend,根目录为./agent_data(注册FilesystemMiddleware的目的在于我们需要适用它提供的操作文件的工具)。MemoryMiddleware配置了一个名为preferences.md的Memory源文件,我们可以把它看作是Agent的饮食偏好日记。我们利用系统提示词让Agent尽量按照用户饮食习惯来点餐。

我们两次调用了Agent,第一次调用中用户明确表示了不能吃辣的饮食习惯,Agent在处理这个请求时会将这个信息写入到preferences.md文件中。第二次调用中,用户没有提到饮食习惯的信息,但是由于Agent已经在第一次调用中记住了用户不能吃辣的偏好,所以它会参考这个信息来点餐,从而避免点了辣的菜,如下的输出充分体现了这一点:

好的,已经帮你下单啦 😊  
考虑到你**不能吃辣**,我给你搭配了一份清淡又均衡的外卖:

- 清蒸鲈鱼 × 1  
- 番茄炒蛋 × 1  
- 清炒菜心 × 1  

都是不辣、口味清爽的菜式,营养也很均衡。  
如果你想换成偏素一点、偏肉一点,或者有其他忌口,随时告诉我,我可以下次直接按你的习惯来点。
好的,已经帮你点好外卖啦 ✅  
好的,已经帮你点好外卖啦 ✅  

根据你**不能吃辣**的饮食习惯,我给你安排了一份清淡又营养的搭配:  
- 番茄炒蛋 × 1  
- 清蒸鲈鱼 × 1  
- 清炒菜心 × 1  

如果你想换口味、加菜,或者下次有新的忌口/偏好,随时告诉我就行~

MemoryMiddleware的实现其实非常简单。其state_schema字段表示的状态类型MemoryState中包含了一个memory_contents字段,它返回的字典用来存储记忆内容。它的__init__方法接受一个backend参数和一个sources参数,前者用于指定Memory的存储后端,后者用于指定源文件列表。

class MemoryMiddleware(AgentMiddleware[MemoryState, ContextT, ResponseT]):

    state_schema = MemoryState

    def __init__(
        self,
        *,
        backend: BACKEND_TYPES,
        sources: list[str],
    ) -> None

    def before_agent(self, state: MemoryState, runtime: Runtime, config: RunnableConfig) -> MemoryStateUpdate | None
    async def abefore_agent(self, state: MemoryState, runtime: Runtime, config: RunnableConfig) -> MemoryStateUpdate | None
    def wrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
    ) -> ModelResponse[ResponseT]
    async def awrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],
    ) -> ModelResponse[ResponseT]

class MemoryState(AgentState):
    memory_contents: NotRequired[Annotated[dict[str, str], PrivateStateAttr]]
class MemoryStateUpdate(TypedDict):
    memory_contents: dict[str, str]

2. MemoryMiddleware是如何让Agent拥有跨Thread记忆的?

MemoryMiddleware通过重写before_agent/abefore_agent方法来实现Memory的读取和更新逻辑。这两个方法返回的MemoryStateUpdate对象具有与MemoryState完全一致的结构,加载的Memory内容转换成路径/内容的映射字典后,会存储在memory_contents通道中。before_agent/abefore_agent方法会利用它避免重复加载。在重写的wrap_model_call/awrap_model_call方法中,Memory的内容会转换成LLM请求系统提示词的一部分,进而实现利用记忆指导模型的推理的目的。对于上面演示的这个例子,在附加了由MemoryMiddleware生成的系统提示词后,完整的提示词内容如下(不包含FileSystemMiddleware附加的系统提示词),其中- 忌口:不能吃辣就是写入preferences.md中的内容:

你是一个私人订餐助手,帮助用户下单。请使用`place_order`工具尽可以根据用户的饮食习惯来处理订单请求无需用户确认,自己选择菜品和数量来下单。如果用户提到了新的饮食习惯或忌口,立即写入`preferences.md`中

<agent_memory>
preferences.md
- 忌口:不能吃辣

</agent_memory>

<memory_guidelines>
    The above <agent_memory> was loaded in from files in your filesystem. As you learn from your interactions with the user, you can save new knowledge by calling the `edit_file` tool.

    **Learning from feedback:**
    - One of your MAIN PRIORITIES is to learn from your interactions with the user. These learnings can be implicit or explicit. This means that in the future, you will remember this important information.
    - When you need to remember something, updating memory must be your FIRST, IMMEDIATE action - before responding to the user, before calling other tools, before doing anything else. Just update memory immediately.
    - When user says something is better/worse, capture WHY and encode it as a pattern.
    - Each correction is a chance to improve permanently - don't just fix the immediate issue, update your instructions.
    - A great opportunity to update your memories is when the user interrupts a tool call and provides feedback. You should update your memories immediately before revising the tool call.
    - Look for the underlying principle behind corrections, not just the specific mistake.
    - The user might not explicitly ask you to remember something, but if they provide information that is useful for future use, you should update your memories immediately.

    **Asking for information:**
    - If you lack context to perform an action (e.g. send a Slack DM, requires a user ID/email) you should explicitly ask the user for this information.
    - It is preferred for you to ask for information, don't assume anything that you do not know!
    - When the user provides information that is useful for future use, you should update your memories immediately.

    **When to update memories:**
    - When the user explicitly asks you to remember something (e.g., "remember my email", "save this preference")
    - When the user describes your role or how you should behave (e.g., "you are a web researcher", "always do X")
    - When the user gives feedback on your work - capture what was wrong and how to improve
    - When the user provides information required for tool use (e.g., slack channel ID, email addresses)
    - When the user provides context useful for future tasks, such as how to use tools, or which actions to take in a particular situation
    - When you discover new patterns or preferences (coding styles, conventions, workflows)

    **When to NOT update memories:**
    - When the information is temporary or transient (e.g., "I'm running late", "I'm on my phone right now")
    - When the information is a one-time task request (e.g., "Find me a recipe", "What's 25 * 4?")
    - When the information is a simple question that doesn't reveal lasting preferences (e.g., "What day is it?", "Can you explain X?")
    - When the information is an acknowledgment or small talk (e.g., "Sounds good!", "Hello", "Thanks for that")
    - When the information is stale or irrelevant in future conversations
    - Never store API keys, access tokens, passwords, or any other credentials in any file, memory, or system prompt.
    - If the user asks where to put API keys or provides an API key, do NOT echo or save it.

    **Examples:**
    Example 1 (remembering user information):
    User: Can you connect to my google account?
    Agent: Sure, I'll connect to your google account, what's your google account email?
    User: john@example.com
    Agent: Let me save this to my memory.
    Tool Call: edit_file(...) -> remembers that the user's google account email is john@example.com

    Example 2 (remembering implicit user preferences):
    User: Can you write me an example for creating a deep agent in LangChain?
    Agent: Sure, I'll write you an example for creating a deep agent in LangChain <example code in Python>
    User: Can you do this in JavaScript
    Agent: Let me save this to my memory.
    Tool Call: edit_file(...) -> remembers that the user prefers to get LangChain code examples in JavaScript
    Agent: Sure, here is the JavaScript example<example code in JavaScript>

    Example 3 (do not remember transient information):
    User: I'm going to play basketball tonight so I will be offline for a few hours.
    Agent: Okay I'll add a block to your calendar.
    Tool Call: create_calendar_event(...) -> just calls a tool, does not commit anything to memory, as it is transient information
</memory_guidelines>
内容概要:本文介绍了一个关于三相桥式全控整流及有源逆变电路的实验仿真模型,重点研究三相整流器与逆变器在Simulink环境下的建模与仿真技术。内容涵盖电力电子变换器的工作原理、控制策略设计、系统动态响应分析,并进一步扩展至10kV配电网中不同中性点接地方式(中性点不接地、经小电阻接地、经消弧线圈接地)下的单相、两相短路接地及相间短路故障的仿真研究,全面呈现了电力系统典型故障的暂态特性。此外,文档还整合了丰富的科研资源,涵盖电力系统优化、新能源并网、故障诊断、微电网调度等多个前沿方向,充分体现了Matlab/Simulink在电气工程仿真中的核心地位广泛应用价值。; 适合人群:电气工程、自动化、电力电子等相关专业的高校学生、科研人员及工程技术人员,具备一定的电路理论基础仿真软件操作经验者更佳。; 使用场景及目标:①用于教学实验中帮助理解三相整流与逆变电路的工作机制;②支撑科研项目中对电力系统故障特性的建模与分析;③作为开发新型控制算法(如PWM控制、低电压穿越等)的仿真验证平台;④辅助完成毕业设计、课题研究或工程方案评估; 阅读建议:此资源以Simulink仿真实现为核心,强调理论与实践结合,建议读者在学习过程中同步搭建模型,动手调试参数,深入理解各模块功能与系统整体行为,同时可参考文中提供的完整资源链接拓展研究视野。
内容概要:本文介绍了一个关于风光制氢合成氨系统优化研究的论文复现资源,依托Cplex求解器在Matlab环境中实现系统建模与求解。该资源聚焦于新能源耦合系统,涵盖风能、太阳能发电制氢,并进一步合成氨的全流程能量管理与优化调度,通过数学建模与优化算法实现系统经济性与运行效率的最大化。内容不仅包括风光出力不确定性处理、电解水制氢、氢气储存与转化、氨合成工艺等关键环节的建模,还整合了多种智能优化算法与电力系统调度策略,如二阶锥规划、多目标优化与需求响应机制,旨在为科研人员提供一套完整的综合能源系统优化研究框架与代码实现范例。; 适合人群:具备一定电力系统、优化理论及Matlab编程基础的研究生、科研人员及工程技术人员,尤其适合从事新能源系统优化、综合能源系统规划、氢能与氨能转化等前沿方向的研究者。; 使用场景及目标:① 复现高水平期刊论文中的风光制氢合成氨系统优化模型,掌握Cplex在Matlab中的建模与求解流程;② 学习并应用二阶锥规划、多目标优化、需求响应等先进优化方法于综合能源系统科研项目中;③ 借助提供的完整Matlab代码案例,快速搭建仿真环境,加速科研进程,提升学术创新能力与工程实践水平。; 阅读建议:此资源以科研复现为核心,强调理论与实践深度融合,建议读者在学习过程中结合文档中的代码实例,逐步调试与理解模型构建逻辑,并尝试进行参数调整与模型拓展,以深化对综合能源系统多能耦合与优化调度机制的理解与应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值