默认情况下,Strands智能体每次对话都从零开始。上一轮聊过的偏好、身份、历史,下一轮全部清零。如果你的智能体在会话之间"忘了用户是谁",问题不在模型,而在状态管理这一层。
Strands的文档把智能体状态分成三类:会话管理负责持久化完整对话,方便你恢复;对话管理负责把一次会话控制在上下文窗口内;记忆则负责跨会话携带持久知识,不需要重放旧对话。三者解决的是不同问题,缺了第三类,用户就得每次重新自我介绍。
记忆管理器到底做三件事
给智能体挂上一个MemoryManager,再配一个或多个存储,管理器就承担三项职责:
- 召回:智能体通过工具按需搜索记忆。
- 注入:模型运行前,相关记忆被自动加进提示词。
- 抽取:对话消息被转成记忆写入存储。
挂上存储后,召回和注入默认开启,写入则是可选项,需要你主动打开。
第一步:先用本地测试存储跑通
原型阶段可以用TestMemoryStore,默认把内容持久化到用户目录下的notes.json。代码很简短:从strands导入Agent,从strands.memory导入MemoryManager,再导入TestMemoryStore,建一个名为notes的存储,然后把MemoryManager挂到Agent上。
这段代码能直接跑。但它只适合在笔记本上验证:家目录里的一个JSON文件既不区分用户,也扛不住容器重新部署。生产环境里,Strands文档给出了Amazon Bedrock知识库存储、可架在任何存储后端上的文件存储,以及自定义存储接口。
第二步:换成按用户隔离的托管存储
MemorySync提供了strands-memorysync,它是同一个MemoryManager下的存储实现。要求Python 3.10以上、带MemoryManager的Strands版本(2026年起),以及环境变量MEMORYSYNC_API_KEY里的API密钥。
安装只需一行pip命令,同时装上strands-agents和strands-memorysync。构造存储时,user_id是必填项,指明这些记忆属于谁;session_id可选,用来给这次对话的事实打标签;extraction参数传入InvocationTrigger(),表示每一轮都发送抽取。
实际运行时发生了什么:每次模型调用前,管理器向存储询问相关记忆并注入;每次交互结束后,只有用户的消息被送去抽取事实。助手回复、工具消息、系统消息都不会被发送。在一次本地运行中,被召回的记忆确实出现在了模型的输入里,而唯一的写入就是那条role为"user"的用户消息,助手回复没有被送出。
第三步:处理"前几轮被遗忘"的意外
Strands默认的抽取触发器每五次调用才触发一次,所以用户最初的几轮交流还没被写入,早期的召回自然是空的。这是设计如此。同步的agent调用每次结束后会冲刷,但invoke_async不会。
想让第一轮交流就留下记忆,就显式传入InvocationTrigger()。在本地运行中,第一轮的用户消息在那次调用后立刻被写入。MemorySync的指南推荐这么做,因为它的写入是幂等的,重试不会被重复抽取。
第四步:选一个稳定的user_id
用自己的用户ID、邮箱或UUID都行,关键是每个会话都用同一个值。换一个值,召回就是空的。user_id同时也是用户之间的隔离边界,应当为每个用户(或每个请求)建一个存储,而不是让多个用户共用一个。
可选的显式记忆工具
调用store.get_tools()会得到两个工具:memorysync_search和memorysync_save。这两个名字在机器上确认过。这里刻意没有删除工具,删除只能走API或管理操作,用户身份也永远不会成为模型可以改动的工具参数。
几个常见疑问
还需要会话管理器吗?如果你想恢复一模一样的对话,需要。记忆和会话持久化解决的是不同问题,Strands文档也是分开讲的。
刚存完东西,召回为什么是空的?抽取是异步执行的,给它一点时间。同时确认两次运行用的是同一个user_id,以及你没有停留在五轮默认触发器上。
报ModuleNotFoundError: strands?那是框架本身没装,执行pip install strands-agents即可。
想先看看一条事实能不能扛过一次页面刷新,再决定要不要接进项目?可以在浏览器里直接试MemorySync,不需要注册。
热门跟贴