☰
Hermes Agent 持久记忆架构拆解:分层存储与主动召回实战
2026/10/20 8:26:45 网站建设 项目流程

1. 从“金鱼记忆”到“长期记忆”:为什么我们需要一个会长记性的 Agent

做 AI 应用开发的人都有一个共同的痛点:你花了一下午调教好的对话助手,关掉窗口再打开,它就像被敲了一记闷棍,完全不记得你是谁、之前聊过什么、上次那个任务做到哪一步了。这种“金鱼记忆”在单轮问答场景下还能忍,一旦涉及多步骤任务、跨会话协作、个性化服务,就彻底没法用了。

Hermes Agent 这个项目,核心要解决的就是这个问题。它不是又一个套壳对话机器人,而是一个带持久记忆层的智能体框架。简单说,它让 AI Agent 拥有了“记性”——能记住用户偏好、历史交互、任务上下文,甚至能从过往的成功和失败中提炼经验,在后续任务中主动调用。

这个项目适合谁?如果你正在做以下事情,它值得你花时间研究:需要跨会话保持用户画像的客服系统、需要多轮迭代才能完成复杂任务的工作流引擎、需要积累领域知识越用越聪明的垂直助手。哪怕你只是想给自己的个人助理加一个“别每次都问我同样问题”的能力,Hermes Agent 的记忆架构设计也能给你不少启发。

我花了大概三周时间把这个项目的核心逻辑跑通,又花了两周做定制化改造,踩了不少坑,也总结了一些文档里不会写的经验。下面我把整个拆解过程按模块讲清楚,从设计思路到实操细节,再到问题排查,尽量让不同基础的朋友都能拿走能用的东西。

2. 记忆架构的整体设计:为什么是“分层存储+主动召回”

2.1 核心设计思路:把记忆当成数据库来管,而不是塞进 Prompt

很多开发者第一反应是:要记忆?简单,把历史对话拼接到 Prompt 里不就行了。我一开始也这么干过,结果就是 Token 消耗爆炸,而且模型在超长上下文里会“迷失”,该记住的没记住,不该关注的细节反而干扰了判断。

Hermes Agent 的思路完全不同。它把记忆拆成了三个独立的层,每层有不同的存储介质、生命周期和召回策略:

记忆层级存储内容存储介质生命周期召回方式
工作记忆当前会话的即时上下文内存/Redis单次会话全量注入
情景记忆历史交互事件、任务执行记录向量数据库长期持久语义检索
语义记忆提炼后的用户偏好、领域知识结构化数据库+向量库长期持久规则+语义混合

这个分层设计的精妙之处在于:它模拟了人类记忆的工作方式。你和一个老朋友聊天,不需要回忆你们认识以来所有的对话,只需要记住“他不吃辣”“他最近在学吉他”“上次答应帮他找的资料还没发”——这些就是语义记忆。而具体某次聊天的细节,只有在相关话题触发时才会被回忆起来,这就是情景记忆。

提示:不要试图用一个向量库解决所有记忆问题。结构化信息(如用户ID、偏好标签)用关系型数据库查得更快更准,非结构化信息(如对话摘要、任务描述)才适合向量检索。混在一起用,查询效率和准确率都会打折扣。

2.2 为什么选择“主动召回”而不是“全量注入”

全量注入的问题前面说了,Token 成本高、干扰大。但主动召回也有个坑:召回什么、召回多少、什么时候召回,这三个问题如果处理不好,Agent 要么“失忆”,要么“过度联想”。

Hermes Agent 的做法是引入一个记忆管理器模块,它负责在每次用户输入后、模型推理前,执行以下判断流程:

  1. 当前输入是否包含需要历史信息的意图(如“上次那个方案”“我之前说过”)
  2. 如果包含,从情景记忆中检索 Top-K 相关事件
  3. 从语义记忆中提取与当前任务相关的用户偏好和约束
  4. 将召回结果压缩成结构化摘要,注入到 Prompt 的指定区域

这个流程的关键在于压缩。召回的记忆不能原封不动塞进去,必须经过摘要和格式化。我实测下来,把 10 条相关记忆压缩成 200 字以内的结构化摘要,效果比直接塞 2000 字原始记录好得多。

2.3 记忆的写入策略:什么时候该记,什么时候该忘

记忆不是越多越好。我见过一个反面案例:某开发者把所有对话都存进向量库,结果 Agent 变得“神经质”,经常引用几个月前无关的对话内容,用户体验极差。

Hermes Agent 的写入策略有几个触发条件:

  • 显式触发:用户说“记住这个”“以后都这样”时,强制写入语义记忆
  • 任务完成触发:一个多步骤任务成功完成后,将任务摘要和关键决策点写入情景记忆
  • 偏好推断触发:当用户连续多次做出相同选择时(如每次都选“简洁模式”),自动提炼为偏好写入语义记忆
  • 定时衰减:情景记忆有 TTL(生存时间),超过一定时间未被召回的记忆会被降权或归档

这个策略的核心逻辑是:记忆的价值在于被使用,而不是被存储。一条从未被召回的记忆,占着存储空间还增加检索噪声,不如让它自然衰减。

3. 核心模块拆解:记忆管理器、召回引擎与反思机制

3.1 记忆管理器:Agent 的“海马体”

记忆管理器是整个系统的大脑,它决定了什么信息值得记、以什么形式记、放在哪里。我在实现时把它拆成了三个子模块:

写入判断模块负责给每条交互打标签。不是所有对话都值得写入长期记忆,比如“你好”“谢谢”这类寒暄,写进去就是噪声。我的做法是给每条消息计算一个“记忆价值分”,综合考虑信息密度、情感强度、任务相关性三个维度。信息密度看是否包含具体事实或决策,情感强度看用户是否表达了明确喜好或不满,任务相关性看是否与当前进行中的任务有关。分数超过阈值的才进入写入队列。

摘要生成模块负责把原始对话压缩成可存储的记忆单元。这里有个经验:摘要不能只靠模型生成,还要保留原始消息的指针。因为模型摘要可能丢失关键细节,当后续需要精确回忆时,可以通过指针回溯原始记录。我的做法是摘要存向量库,原始记录存对象存储,两者用同一个 ID 关联。

冲突消解模块负责处理记忆矛盾。用户上周说“我喜欢详细解释”,这周说“以后简洁点”,两条记忆冲突了怎么办?Hermes Agent 的策略是时间优先+置信度加权。新记忆默认置信度更高,但如果旧记忆被多次召回且用户没有纠正,置信度会累积。冲突时取置信度高的,同时把旧记忆标记为“待验证”,下次相关场景时主动询问用户确认。

3.2 召回引擎:怎么在正确的时间想起正确的事

召回引擎的挑战在于平衡召回率和精确率。召回太少,Agent 显得健忘;召回太多,Agent 显得啰嗦且容易跑偏。

我的实现方案是两阶段召回:

第一阶段用规则过滤快速缩小范围。比如当前对话涉及“代码”,就只召回与编程相关的记忆;涉及“日程”,就只召回时间管理相关的记忆。规则可以基于关键词、意图分类或实体识别来触发。

第二阶段用语义检索在缩小后的范围内找最相关的 Top-K。这里有个细节:查询向量不能只用当前用户输入,还要拼接最近几轮对话的摘要。因为用户说“那个方案再改改”时,单看这句话根本不知道“那个方案”是什么,必须结合上下文。

注意:Top-K 的 K 值不要设太大。我实测下来,K=3 到 K=5 效果最好。超过 5 条,模型注意力会被分散,反而容易忽略最关键的那条记忆。如果确实需要更多信息,宁可分多次召回,也不要一次性塞太多。

3.3 反思机制:让 Agent 从经验中学习

这是 Hermes Agent 最有意思的部分。它不只是被动地存储和召回,还会主动反思。

具体来说,每次任务完成后,Agent 会触发一个反思流程:回顾整个任务执行过程,分析哪些步骤顺利、哪些步骤卡壳、用户的反馈是正面还是负面。然后生成一条“经验总结”写入语义记忆,格式类似:“当用户要求做 X 类任务时,采用 Y 方法效果更好,注意避免 Z 问题。”

这个反思机制让 Agent 有了“越用越聪明”的可能性。我拿一个代码生成任务测试过:第一周 Agent 生成的代码经常缺少错误处理,用户反馈了几次后,反思机制生成了“生成代码时必须包含异常捕获”的经验。第二周再测试,Agent 主动加上了 try-catch 块,用户满意度明显提升。

反思的触发频率可以配置。太频繁会浪费计算资源,太稀疏又学不到东西。我的建议是:任务成功时每 5 次反思一次,任务失败时立即反思。

4. 实操落地:从零搭建一个带记忆的 Agent

4.1 环境准备与依赖选型

先说我用的技术栈,这套组合是我试过几套方案后觉得最稳的:

  • 向量数据库:选了一个轻量级的开源方案,支持本地部署,单机就能跑,不用额外运维
  • 关系型数据库:用 SQLite 起步,数据量大了再迁移到 PostgreSQL
  • 缓存层:Redis,存工作记忆和热点召回结果
  • 模型层:支持多模型切换,我主要用两个,一个负责推理,一个负责摘要和反思

安装过程不复杂,核心依赖就几个包。但有个坑要注意:向量数据库的索引类型选择会影响召回速度。数据量小于 10 万条时用扁平索引就行,超过后再考虑 IVF 或 HNSW。我一开始就上了 HNSW,结果小数据量下反而比扁平索引慢,因为图构建有开销。

# 核心依赖安装(示意) pip install vector-db-client pip install redis pip install sqlalchemy pip install openai # 或其他模型 SDK

4.2 记忆写入的完整流程与参数配置

写入流程我拆成了五步,每步都有可调参数:

第一步:消息预处理。把用户输入和 Agent 回复拼接成标准格式,去除无关字符,提取实体和意图标签。

第二步:记忆价值评分。我用的评分公式是:

score = 0.4 * info_density + 0.3 * sentiment_intensity + 0.3 * task_relevance

info_density 用命名实体数量除以总词数,sentiment_intensity 用情感分类模型的置信度,task_relevance 用当前消息与进行中任务的语义相似度。阈值我设的是 0.6,低于这个值不写入长期记忆。

第三步:摘要生成。调用模型生成 50 字以内的摘要,同时保留原始消息 ID。摘要的 Prompt 我调了好几版,最后定下来的模板是:

请将以下对话压缩成一条记忆摘要,要求: 1. 保留关键事实和决策 2. 保留用户偏好和约束 3. 不超过50字 4. 以“用户”或“Agent”开头标明主体

第四步:冲突检测。用新摘要的向量去语义记忆里检索相似度超过 0.85 的旧记忆,如果有,触发冲突消解流程。

第五步:写入存储。摘要和向量写入向量库,结构化标签写入关系库,原始消息写入对象存储。

4.3 召回流程的实现细节与调优

召回流程我调了大概两周才稳定下来。核心代码如下(伪代码示意):

def recall_memory(user_input, session_context): # 第一阶段:规则过滤 intent = classify_intent(user_input) candidate_tags = map_intent_to_tags(intent) # 第二阶段:语义检索 query_vector = embed(user_input + session_context[-3:]) candidates = vector_db.search( query_vector, filter={"tags": {"$in": candidate_tags}}, top_k=10 ) # 第三阶段:重排序 reranked = rerank_by_recency_and_confidence(candidates) # 第四阶段:压缩注入 memory_summary = compress_to_summary(reranked[:5]) return memory_summary

调优的关键在第三阶段的重排序。我用的排序公式是:

final_score = 0.5 * semantic_similarity + 0.3 * recency_score + 0.2 * confidence_score

recency_score 用指数衰减,半衰期设的是 7 天。confidence_score 根据记忆被召回后用户是否纠正来动态调整,用户没纠正就加分,纠正了就减分。

实操心得:召回结果一定要做去重。我遇到过同一个事实被多条记忆重复召回的情况,因为用户在不同对话里说了类似的话。去重逻辑用语义相似度判断,超过 0.9 的只保留置信度最高的那条。

4.4 反思机制的触发与经验沉淀

反思机制的实现相对独立,它是一个定时任务,也可以手动触发。核心逻辑是:

def reflect_on_task(task_record): # 提取任务执行轨迹 trajectory = extract_trajectory(task_record) # 分析成功与失败点 analysis = analyze_trajectory(trajectory) # 生成经验总结 lesson = generate_lesson(analysis) # 写入语义记忆,标记为“经验”类型 semantic_memory.write( content=lesson, type="lesson", confidence=0.8, source_task=task_record.id )

经验总结的 Prompt 我改了很多版,关键是让模型输出可操作的规则,而不是泛泛而谈。比如“要注意错误处理”这种没用,“生成 Python 代码时,如果涉及文件操作,必须包含 try-except 块”这种才有指导意义。

5. 常见问题与排查技巧实录

5.1 记忆召回不准确:从三个维度排查

召回不准确是最常见的问题,我总结了一个排查清单:

问题现象可能原因排查方法解决方案
召回无关记忆查询向量质量差检查 embedding 模型是否适合当前领域换领域适配的 embedding 模型
该召回没召回规则过滤太严打印过滤前后的候选数量放宽标签匹配逻辑
召回重复内容去重阈值太高统计召回结果的相似度分布降低去重阈值到 0.85
召回旧记忆时间衰减太慢检查 recency_score 计算缩短半衰期或提高衰减系数

我踩过最坑的一次是 embedding 模型选错了。用了一个通用模型,结果在代码领域的语义相似度计算完全不准,“函数”和“方法”的向量距离很远。换成代码领域微调过的模型后,召回准确率从 60% 提升到 85%。

5.2 记忆冲突处理:当用户改变主意时

用户改变偏好是常态,但处理不好会让 Agent 显得“精神分裂”。我的处理流程是:

  1. 检测到冲突后,不立即删除旧记忆,而是把旧记忆标记为“历史偏好”
  2. 新记忆写入时,在元数据里记录“替代了哪条旧记忆”
  3. 下次召回时,如果新旧记忆都被召回,优先返回新的,但把旧的作为“历史参考”附在后面
  4. 如果用户再次确认旧偏好,则恢复旧记忆的置信度

这个流程的好处是保留了偏好演变的历史轨迹,对于需要理解用户长期行为的场景很有价值。

5.3 性能优化:当记忆量增长到十万条以上

记忆量大了之后,召回延迟会明显上升。我做了几个优化:

索引优化:向量库的索引从扁平换成 IVF,查询速度提升 3 倍。但要注意 IVF 需要训练,训练数据要覆盖所有聚类中心。

缓存热点:把最近 7 天被召回过的记忆缓存在 Redis 里,命中率大概 40%,平均延迟降低 50ms。

异步写入:记忆写入改成异步队列,不阻塞主流程。用了一个简单的生产者-消费者模式,写入延迟从 200ms 降到 20ms 以内。

分级存储:超过 90 天未被召回的记忆迁移到冷存储,召回时先查热存储,没有再查冷存储。

注意:异步写入有个坑,如果 Agent 在写入完成前就崩溃了,记忆会丢失。我的做法是写入队列持久化到磁盘,重启后自动恢复未完成的任务。

5.4 隐私与安全:记忆系统的红线

记忆系统存储了大量用户交互数据,隐私保护是必须考虑的问题。我做了几件事:

  • 所有记忆内容加密存储,密钥不落盘
  • 支持用户一键清除所有记忆,清除操作不可逆
  • 敏感信息(如密码、身份证号)在写入前自动脱敏
  • 记忆召回时做权限校验,确保只能召回当前用户的数据

脱敏这块我用的正则+模型双重检测,正则覆盖常见格式,模型检测变体。实测下来,正则能拦住 90% 的敏感信息,剩下的靠模型兜底。

6. 记忆系统的扩展方向与个人体会

这个项目的基础架构跑通后,我尝试了几个扩展方向,有些效果不错,有些还在摸索。

多 Agent 共享记忆是个有意思的方向。让多个 Agent 共享一个语义记忆库,每个 Agent 有自己的情景记忆。这样团队协作时,一个 Agent 学到的经验其他 Agent 也能用。我测试了一个客服场景,三个 Agent 共享记忆后,整体解决率提升了 15%。但挑战在于记忆冲突会更复杂,需要引入“记忆所有权”和“记忆投票”机制。

记忆可视化对调试帮助很大。我写了一个简单的 Web 界面,把记忆的写入时间、召回次数、置信度变化画成图表。有一次发现某条记忆的置信度在持续下降,追查后发现是用户每次都被问烦了直接跳过,说明这条记忆的召回时机不对。可视化让这种隐性问题变得可见。

跨模态记忆是下一步想做的。目前只处理文本,但实际场景中图片、语音、文件都是重要的记忆载体。比如用户发了一张设计图说“按这个风格来”,这张图就应该被存入记忆,下次生成时作为参考。技术上的难点在于跨模态检索,文本查询怎么准确召回图片记忆,我还在试不同的方案。

我个人在实际操作中的体会是:记忆系统的核心不是技术,而是对用户意图的理解。同样的对话,理解不同,该记的东西就不同。我见过一个案例,用户说“帮我订张票”,Agent 记住了“订票”这个动作,但没记住用户之前说过的“要靠窗”。后来我把意图理解模块和记忆写入模块做了更紧密的耦合,让意图识别结果直接指导记忆提取,效果好了很多。

最后分享一个小技巧:定期做记忆审计。我每个月会抽样 100 条记忆,人工检查摘要质量和召回准确性。有一次发现模型把用户的玩笑话当成了真实偏好记了下来,导致后续推荐一直跑偏。这种问题不审计根本发现不了。记忆系统就像花园,不修剪就会杂草丛生。

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

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

立即咨询