☰
让AI Agent跨会话记住用户偏好:Hermes Agent记忆架构与实践
2026/10/14 5:34:38 网站建设 项目流程

记得刚把带记忆的Agent跑通时,最明显的感觉不是“它聪明了”,而是“它终于像一个长期共事的人,而不是一个每次见面都重新自我介绍的路人”。这个项目代号叫Hermes Agent,核心就一句话:让AI Agent拥有跨会话的长期记忆。它能在上一轮对话结束时记住关键信息,在下一轮对话开始时自动想起,并且根据用户偏好调整回答方式。很多Agent Demo看起来聪明,实际上只是“短时上下文”玩得好,一旦窗口清空、会话重建,模型对用户一无所知。Hermes Agent就是专门解决这个断层问题的实践产物。这篇文章会从记忆架构设计、向量记忆仓实现、检索与遗忘策略、实际跑通流程,以及我在调试和压测中踩过的坑,完整拆开讲一遍。适合自己动手做过Agent、想给项目加“记性”的开发者,或者正在纠结“怎么让Agent记住用户偏好”的人。

1. 项目整体设计与记忆机制拆解

1.1 为什么Agent最缺的是“记性”

大模型本身的上下文窗口再大,本质上还是一次性的。你开一个新会话,模型不会记得你昨天让它帮你跟踪过某个项目的进度,也不会记得你明确说过“不要用术语,用大白话解释”。这就导致很多Agent应用看起来很惊艳,用起来却总差一口气:每次都要重复交代背景、重复设定口径、甚至重复贴一遍资料。它们不是能力不够,是“记性”不够。

“会长记性”的核心价值,就是让Agent能够把历史对话、用户偏好、任务状态、领域知识持久化,并且在下一次交互时主动使用这些信息。这跟人的记忆非常像:我们和同一个人聊天,不需要每次重报家门,因为大脑会从长期记忆里调出“这个人是谁、上次聊到哪、他偏好什么、他不喜欢什么”。Agent要做的,就是把这个机制用工程手段复刻出来。

Hermes Agent在设计时定下几个原则:记忆必须结构化、检索必须高效、存储必须可扩展、遗忘必须有策略。四个原则听起来简单,真正实现时会牵扯出不少细节,后面会逐个展开。

1.2 记忆模型:短期、长期、工作记忆如何分工

直接上完整的大模型记忆系统容易失控,我一开始的版本把所有对话历史一股脑塞进向量库,结果检索命中率惨不忍睹。后来把记忆模型拆成了三层,才真正稳定下来。

  • 短期记忆:对应当前会话的上下文,直接靠大模型的上下文窗口承载。这部分不需要持久化,会话结束即可丢弃。
  • 工作记忆:当前会话中需要持续关注的动态状态,比如用户正在操作的任务进度、临时设定的条件、暂时性的偏好。这一层会以结构化摘要的形式写入长期存储,但更新频率高、生命周期短。
  • 长期记忆:跨越会话的稳定信息,比如用户的职业背景、表达偏好、明确表达过的喜好与禁忌、反复出现的主题。这部分是Hermes Agent的“镇店之宝”,也是检索增强的主要来源。

三层记忆的流转关系很直接:短期记忆随着对话推进,有沉淀价值的信息由工作记忆层抽取并归纳,再写入长期记忆;长期记忆在下一轮会话开始时被检索出来,回填到上下文里,变成新的短期记忆。我后来把整条链路叫作“记忆生命周期”,因为信息确实是从短期状态流向长期存储,又被读回短期上下文的循环过程。

这个设计的好处是:检索规模可控、存储成本可控、召回质量高。如果所有历史对话全部扁平化丢进向量库,检索时会被大量低价值信息干扰,噪声一大,Agent就分不清“用户上次随口提的一句”和“用户长期稳定的核心偏好”哪个更重要。

2. 核心实现:向量记忆仓与检索增强

2.1 记忆的写入:从对话到向量化

要记住东西,首先得把“值得记的信息”从对话里抽出来。原始对话文本是不能直接进向量库的,会越存越乱。我采用的方式是“先摘要、再结构化、后向量化”三步走。

第一步,把当前窗口的对话交给模型做一次轻量摘要,提取出:用户陈述的事实、用户的偏好或禁忌、任务进展状态、当前未完成事项。第二步,把摘要解析成JSON结构,每个记忆条目包含内容、类型、时间戳、重要度评分。第三步,把JSON序列化成文本片段,交给嵌入模型转成向量,写入向量数据库。

这里有个关键细节:记忆条目要分块。比如用户在一段对话里同时说了“我是做数据分析的”和“我讨厌每周汇总邮件”,这两条信息不能揉进同一个向量块里。原因很简单,检索的时候往往只需要其中某一条,混合块会在相似度计算上稀释掉关键信号。我早期在本上吃过亏,把两条信息放一块,结果检索“用户职业背景”时混进了邮件偏好,十分恼火。

向量化的具体操作可以用现成的嵌入模型。为了让记忆检索更贴合领域语义,我对中文场景做过分词预处理,实际测试下来对小规模记忆库没有明显收益,反而多了一步故障点,后期直接去掉,用原句嵌入。

写入阶段输入输出说明
摘要抽取对话原文自然语言摘要过滤寒暄、废话、临时性内容
结构化拆分摘要JSON记忆条目类型分类、重要度打分
向量化JSON文本片段浮点向量分块后逐条嵌入

2.2 记忆的召回:相关性检索与时间衰减

写入只是存,召回才是真正决定“记性好不好”的环节。Hermes Agent在召回上做了两层过滤。

第一层是向量相似度检索。用户开启新会话,Agent先从长期记忆库里按当前输入语义检索Top-K条最相似的历史记忆。这一步是基础筛选。第二层是时间衰减与重要度加权。纯向量相似度有个毛病:很久以前的一条闲聊记录,和“用户量化偏好”这类稳定特征向量撞车概率不高,但如果是同类话题,旧记录往往不如新记录有参考价值。所以检索出来的条目要重新算一遍融合分:

融合分 = 向量相似度 × 0.6 + 重要度 × 0.3 + 时间衰减系数 × 0.1

时间衰减系数不是简单线性衰减,而是用半衰期函数。我设置了7天半衰期,7天前的记忆权重减半,14天前再减半,但不会归零。这个设计的直觉是:长期稳定的偏好不应该被彻底遗忘,但近期信息更有参考价值。用户上周说你“喜欢简洁回答”,比两个月前说过的同类型话更值得被遵守。

召回结果最后会被组装成“记忆上下文块”,塞进系统提示词里。这个块有固定格式,包含记忆原文、发生时间、重要度,让模型能分清“这是历史事实”和“这是当前对话”。

2.3 记忆管理与遗忘策略

“会记住”和“会忘掉”是一体两面。很多Agent只做加法不做减法,记忆库里堆了几万条陈年旧事,检索速度下滑是一方面,更麻烦的是过期信息会污染判断。

Hermes Agent的遗忘策略分主动和被动两种。

主动遗忘,靠时间衰减和重要度双重判断。每条记忆都带时间戳和重要度,当系统发现某个条目的融合分低于阈值,就会把它标记为“归档”,不再参与召回,但保留在冷存储里。归档不等于删除,万一用户突然问起半年前的事,还可以从冷存储里捞出来。

被动遗忘,靠用户反馈。如果用户明确纠正过Agent某次记忆错误的使用方式,系统会把对应记忆条目直接标记为无效。比如我告诉Agent“以后不要主动推荐食物”,系统记录之后,Agent还继续推荐,我一句“说了多少遍不要推荐吃的”,这条记忆会被降权并最终退出召回范围。

遗忘策略落地时最需要注意的是一点:不要因为单次纠正就物理删除记忆。用户可能只是当时心情不好,或者语境变化。物理删除不可恢复,情绪化删除容易误伤。降权、归档、冷处理,是更稳妥的方案。

3. 实操过程:跑通一个会长记性的Agent

3.1 环境准备与依赖清单

我在实操中用的是Python环境加一个轻量级向量数据库。具体依赖如下:

python>=3.10 openai>=1.12.0 numpy>=1.24.0 jieba>=0.42.1

向量库选了嵌入式模式,零运维,适合本地开发。模型接口走的标准Chat Completion API。整个项目的核心逻辑就三个模块:记忆抽取器、向量存储层、检索融合模块。

依赖安装有个小坑:openai库升级到1.x之后,接口风格变化比较大,如果你参考的老代码是在0.x版本写的,大概率会碰到openai.ChatCompletion.create报错,需要改成client.chat.completions.create方式。我第一次跑的时候被这个点卡了半小时,后来翻更新日志才反应过来。

3.2 核心代码:记忆层与调用流程

记忆层最简单的实现,是把对话历史转成摘要后写入向量库。逻辑不复杂,关键是抽象好接口。下面这段是我调试通过的简版实现,去掉了鉴权和异常处理的外壳,只留核心链路。

# -*- coding: utf-8 -*- import json, time import numpy as np class MemoryUnit: def __init__(self, content, mem_type, importance, timestamp=None): self.content = content self.mem_type = mem_type self.importance = importance self.timestamp = timestamp or int(time.time()) def to_document(self): return json.dumps({ "content": self.content, "type": self.mem_type, "importance": self.importance, "ts": self.timestamp }, ensure_ascii=False) # 模拟召回与融合打分 def recall_memory(query_vector, memory_vectors, memories, top_k=5): scores = [] for i, mv in enumerate(memory_vectors): sim = cosine_similarity(query_vector, mv) mem = memories[i] time_decay = 0.5 ** ((time.time() - mem.timestamp) / 604800) fused = sim * 0.6 + mem.importance * 0.3 + time_decay * 0.1 scores.append((fused, i)) scores.sort(reverse=True) return [memories[i] for _, i in scores[:top_k]]

上面这段代码只是示意,向量库的实际读写由嵌入式库负责,不需要自己维护向量列表。我保留这段是为了展示融合打分的形式,它的核心在于time_decay的计算:604800秒正好是7天,半衰期逻辑直接用了指数衰减。

调用流程我封装成了一个agent_with_memory函数,每次用户发消息,先走记忆召回,把历史记忆注入Prompt,再调模型。这样上层业务逻辑完全感知不到记忆的存在,对已有的Agent代码侵入性很小。

3.3 实测场景:跨会话记住用户偏好

跑通一个功能最直接的验证方法,就是用真实对话场景来测。我设计了一个模拟场景,连续两天以不同会话分别说要考数据分析师认证,以及不喜欢长篇大论的回复风格。

第一轮会话结束时,记忆抽取器应该沉淀两条记忆条目:一条是“用户在准备数据分析师认证”,类型为任务状态;另一条是“用户不喜欢长篇大论”,类型为表达偏好。第二条我会给更高的重要度,因为表达偏好比临时任务更有长期参考价值。

第二轮会话我直接问“我上次说要准备什么来着”,没有提供任何背景。Agent从向量库里检索到“数据分析师认证”这条记忆,并回填到上下文,直接给出准确回答。这就验证了记忆的跨会话能力。如果这时候再问“你能帮我参考一下备考计划吗”,Agent应该会配合“不喜欢长篇大论”的记忆,输出精炼的规划建议,而不是再一次输出几千字庞大文章。

这个测试有两个关键指标:一是记忆召回准不准,二是被召回的记忆有没有真正影响模型输出风格。很多实现只做到“存了但没用”或者“存了但召不回”,这两点必须同时打通,才叫真正的“会长记性”。

4. 踩坑记录与性能调优

4.1 常见问题速查表

做这个项目的过程中,我踩过不少坑,专门整理成了一张表,方便后来人直接对照排查。

问题现象根本原因解决办法
记忆被插入但不生效检索融合分太低,召回没覆盖到目标记忆调低top_k拿更多候选,或提高重要度占比
检索结果全是近期闲聊时间衰减权重过高,把长期稳定偏好挤掉了降低衰减系数,或对稳定偏好单独加权
向量库里垃圾太多抽取器没有过滤寒暄和临时无意义内容在抽取阶段增加“是否有保留价值”的判断
越跑越慢无限制累积记忆,检索规模膨胀启用归档策略,把低融合分条目冷却
用户纠正一次但系统反复犯记忆更新没有同步到抽取器纠正信息也要写入记忆,且标记高重要度
上下文窗口溢出回填记忆块过大限制回填条数和每条长度,按top_k截断

这六个问题里面,最隐蔽的是第一个。Memorize是物理过程,Recall是逻辑过程,你存了不代表能想起来。项目跑了一段时间后,我再三强调检索融合分的设计,就是这个原因:记忆系统真正的智障,往往发生在召回阶段。

4.2 调参经验与内存占用控制

调参这件事没有银弹,但有几组数值是我经过多轮尝试后觉得比较稳的初始值。

重要度评分我用1到10分。用户直接表达的偏好给8到10分,任务进展给6到7分,闲聊型的事实给3到4分。这个打分会直接影响融合分里的0.3权重部分,所以评分的稳定性很重要。如果模型打分忽高忽低,检索结果就会随机漂移。

top_k值我建议从5开始。记忆库规模小的时候5够用,超过5000条之后可以升到8到10。但注意top_k不是越大越好,回填的记忆块太大会挤占上下文空间,反而稀释模型对当前输入的注意力。

内存占用方面,嵌入式向量库在10000条记忆以内,开发环境实测占用在300MB左右。如果你要跑大规模并发,建议把向量库独立出去,做成服务化部署。我在单机Demo阶段是直接进程内嵌的,方便调试,但不适合生产。

性能优化的一个关键技巧是:记忆抽取不要每次对话都全量跑。我设置了触发条件——当对话长度超过10轮,或检测到明确偏好表达时,才触发抽取器。这样可以大幅减少不必要的嵌入计算,也让记忆库的增量更有价值。

5. 经验总结与扩展方向

5.1 从Demo到生产环境,还缺什么

Hermes Agent目前的形态,本质上是一个完整的记忆框架Demo。从它能跑,到它能真正扛住生产环境流量,中间还差几步硬功夫。

第一是多租户的记忆隔离。现在的设计里记忆属于单一用户,一旦要接多人使用,就必须在每一个记忆条目上挂身份ID,所有读写操作前先做身份过滤。不然就会出现“用户A说讨厌什么,用户B的回答被莫名其妙地纠正”这种事故。

第二是记忆的一致性。多个会话同时写入同一条记忆时可能冲突,比如一个会话里用户说喜欢快节奏回复,另一个会话里却说希望慢节奏细致分析。这种矛盾需要合并策略,最稳妥的是以最新一次明确表达为准,而不是通过投票。

第三是记忆的可解释性。用户如果问“你为什么这么回复我”,系统最好能明确指出“因为你上次说过不要长篇大论”。这一步我还没完全实现,但准备把记忆条目的ID直接拼进Prompt的引用信息里,让模型在回答时可以引用来源。

第四是记忆的安全与隐私。长期记忆里存了很多用户个人偏好,生产环境里必须加密存储、权限分级、支持用户主动清空记忆。记忆不能变成黑盒,用户应该有权把Agent“忘掉”。

5.2 记忆能力还能怎么玩

这个框架在基础上还能扩展出不少有意思的方向。

多模态记忆是顺理成章的下一步。现在只记文本,用户上传图片、语音,经过解析后同样可以沉淀成记忆条目,比如“用户在讨论UI设计时偏好用深色调截图举例”。

记忆共享可以做Agent协作。多个Agent各自维护不同领域的记忆,通过一个共享的记忆网关互相调用。比如负责日程的Agent记住了“用户周五有会”,负责内容生产的Agent在安排写作计划时可以主动避开这个时间。

反而我觉得最值得期待的是“记忆复盘”。系统定期把记忆库里的高频主题和长期模式整理成一份“用户画像报告”,让用户直接看到Agent眼中的自己是什么样。这个过程既能增强信任感,也是一种很直观的可用性展示。

我个人在实际操作中的体会是,记忆系统最怕的不是笨,而是乱。笨可以通过调参优化,一旦存取逻辑混乱、遗忘策略失效,整条链路都会失控。所以宁可先小规模跑通,把召回质量打磨稳定,再逐步扩大记忆规模。这个项目做到现在,“会长记性”已经从功能扩展成了一个思维习惯,设计任何Agent能力时都会下意识问一句:这个信息,值得被记住多久?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询