
Hermes 这类智能体框架在单轮对话里可以回答得很漂亮可一旦关掉窗口它就像失忆了一样。给 Hermes 装上记忆外挂真正要解决的并不是把模型底座换得更大而是把用户偏好、历史结论、失败记录和任务状态保存下来在下次对话时按需取回。适合先动手的人有两类一类在基于 Hermes 做个人助理或客服问答另一类在跑重复性任务却被同一个问题反复卡住。我会按实际落地顺序拆记忆分层、接入点、存储结构、参数判断、排查链路和边界控制。很多记忆需求不需要一开始就上复杂架构先抓住一条主线对话前读取对话后写入需要时注入 prompt。1. Hermes 智能体缺的不是聪明而是“记住了什么”1.1 智力型 AI 和实用型 AI 的差距在哪模型本身的知识来自训练阶段它知道很多通用知识但它不知道“你”上一次跟它聊了什么。你可能已经告诉过 Hermes“我这边是电商项目术语要保留英文”下次提问时它又默认翻译成中文。不是模型变笨了而是 conversation history 已经清空它只能基于当前请求重新理解。实用型 AI 和演示型 AI 的一个明显分界线就在这里。演示型 AI 只要单轮回答惊艳就够了实用型 AI 必须能连续承接多轮、多天的任务。用户会默认“你应该记得我说过的话”这是人对助手的基本期望。所以记忆外挂解决的不是模型智商问题而是“状态延续”问题。模型负责推理记忆模块负责告诉模型这个用户是谁、之前做过什么、现在最在意什么。1.2 记忆模块在整个智能体架构里的位置在常见智能体流程里用户输入不会直接进模型。通常会经过意图识别、提示词组装、工具调用、上下文管理最后再把模型输出返回给用户。记忆模块最适合插在两个位置一个是模型调用之前负责把相关历史信息一起组装进 prompt另一个是模型调用之后负责把本次对话中有价值的信息提取出来写入存储。我一般会画一条最小链路用户输入 - 读取记忆 - 组装 prompt - 模型推理 - 执行动作 - 更新记忆 - 返回结果。记忆模块在这里相当于一个外部数据层。它和模型本身解耦所以就算以后换底座模型历史记忆也不需要重做。反过来如果一开始就把记忆逻辑写死在系统提示词里后面每次改 prompt 都会牵连到记忆格式维护成本会快速上升。1.3 记忆外挂的“外挂”到底指什么“外挂”在这里不是游戏外挂而是给智能体挂载的外部扩展模块。它单独负责数据的写入、检索、更新和过期清理业务代码只需要调用两个函数读取记忆、保存记忆。这样做的最大好处是你不需要重新训练模型也不需要改动 Hermes 的核心调度逻辑就可以让同一套模型表现出“记得我”的效果。所谓“越用越聪明”本质上是记忆模块里的用户画像和任务状态越来越完整检索命中越来越准。2. 先想清楚记忆系统要具备哪几层再动手写代码2.1 会话级记忆只负责当前上下文会话级记忆就是当前对话窗口里的历史消息。大多数智能体框架自带的 history 就是这一层。它的生命周期很短窗口关闭或上下文超长截断后早期信息就丢了。很多人的误区是以为把 system prompt 里塞满历史消息就能解决记忆问题。实际上那只是在堆 token超过窗口长度后照样被截断而且会让模型分不清哪句是当前指令、哪句是历史背景。会话级记忆适合保存临时信息比如用户正在调试的代码、当前页面的 URL、上次工具调用返回的中间结果。不建议把长期偏好放在这一层。2.2 用户级记忆跨对话持久化的关键用户级记忆是记忆外挂的核心。它要保存的是“不依赖某一次对话”的信息常见的有用户身份和称呼方式回答风格偏好喜欢短回答还是长回答要不要给代码是否保留英文术语历史结论上次完成了什么任务下一步准备做什么对象信息项目名称、数据库表名、服务器地址等业务上下文这部分必须落到外部存储。最简单可以用 SQLite 或本地 JSON 文件按 user_id 分组。用户 ID 要从稳定来源取比如登录账号、会话生成的唯一 ID。注意不要拿浏览器 UA、临时 token 当用户 ID否则两次登录之间记忆会断。2.3 任务级记忆让批量任务不重复犯错如果你用 Hermes 跑的是批量任务比如批量改写文档、批量审核文本、自动化测试流程那还需要任务级记忆。任务级记忆记录的是“这次任务做到哪了、哪条失败、失败原因、是否重试、输出命名规则”。这类记忆不需要很高的智能检索反而更像状态表。我一般会用一张任务表保存task_id、item_id、status、error_message、retry_count、started_at、finished_at。每处理完一条立即更新状态。这样即使中间崩溃重启后也能从上次失败点继续而不是从头跑一遍。2.4 三层记忆的协作关系三层记忆不是互相替代而是互相补充。会话级记忆提供当前上下文用户级记忆提供长期画像任务级记忆提供执行进度。协作方式可以理解为对话开始时优先加载用户级记忆对话过程中会话级记忆实时更新每轮结束后把值得沉淀的信息回写到用户级或任务级存储。用一个表来看比较清楚层级生命周期典型存储主要用途会话级单次对话内存、缓存维护上下文避免重复提问用户级长期JSON、SQLite、向量库保存偏好、身份、历史结论任务级任务周期数据库表、文件记录批量状态、失败重试、去重先把这个分层想清楚再写代码就不会乱。很多人一上来就接向量数据库结果连最基本的历史偏好都没存住这就是层级没分明白。3. 给 Hermes 加记忆外挂的落地步骤3.1 第一步确定接入点先写一个记忆钩子给 Hermes 加记忆不需要一开始就侵入核心调度。先找出消息处理入口在模型调用前加“读取记忆”在回复返回前加“保存记忆”。下面是一个示例伪代码具体函数名以你用的 Hermes 版本为准def handle_message(user_id: str, user_input: str): # 模型调用前读取与该用户相关的记忆 memories memory_store.retrieve(user_id, user_input) # 组装 prompt把记忆作为参考信息注入 prompt build_prompt(user_input, memories) # 模型正常推理 reply agent.chat(prompt) # 模型调用后把值得记住的信息写入存储 memory_store.save(user_id, user_input, reply) return reply这段逻辑看起来很简单但它把记忆链路完整串起来了。最关键的一点是“读取”和“保存”都放在同一个入口里避免某些分支漏掉。如果你有工具调用、RPA 步骤也要在同一个流程里更新记忆否则下次还是会丢状态。3.2 第二步设计记忆条目和存储结构记忆条目需要包含足够的元信息否则后面检索不到也清不掉。一个比较稳妥的 JSON 结构长这样{ user_id: user_001, updated_at: 2025-01-01T12:00:00Z, items: [ { id: memo_001, type: preference, content: 用户希望回答控制在300字以内, created_at: 2025-01-01T10:00:00Z, last_access_at: 2025-01-01T12:00:00Z, source: chat } ] }type 字段建议不要乱写固定几个枚举preference、project_info、conclusion、state、error_record。后面做检索和清理时按 type 过滤会比全文扫描方便很多。数据量不大时一个 JSON 文件或者 SQLite 表就够了数据量大了再考虑向量库。存储结构定好后要尽早确认记忆目录的位置。用 Docker 部署 Hermes 时需要把记忆目录挂载到宿主机否则容器重建后记忆会丢失在 Windows 本地跑时要注意 JSON 文件路径里的反斜杠和 UTF-8 编码中文内容写进文件后变成乱码后面检索基本就废了。3.3 第三步查询时把记忆注入上下文记忆不是把所有历史都塞回给模型而是按当前输入筛选出最相关的几条。检索方式可以分阶段先按关键词和 type 过滤把明显无关的记忆去掉。再用向量相似度排序取 top_k 条。最后按更新时间做轻微衰减避免旧记忆占用太多权重。注入时注意位置。我习惯把记忆放在系统提示和用户问题之间并加一行说明以下是该用户的历史记忆摘要如果与当前问题无关请忽略 - 用户偏好回答尽量控制在300字以内 - 之前结论上周完成了订单查询接口联调 - 当前任务正在整理 Hermes 部署文档 请根据用户当前问题做出回答这样模型既能看到背景又不会被历史信息带偏。注入的内容要控制长度建议限制在 1000 到 2000 字符以内具体看模型上下文窗口和任务复杂度。3.4 第四步用最小链路跑通验证第一次测试不要直接上生产数据。我一般会准备三组固定输入第一轮让 Hermes 记住一个偏好比如“我只需要最终结论不要分析过程”。第二轮模拟新开会话问一个需要该偏好的问题。第三轮修改偏好再确认系统有没有更新旧记忆。跑完后直接看三件事第一轮的偏好是否被写入第二轮输出是否体现该偏好第三轮更新后旧记忆有没有被覆盖或标记过期。能跑通这三步记忆外挂的最小闭环就算成立了。3.5 从单条对话扩展到批量任务批量任务和单条对话的差异主要在于状态管理。如果一次要处理 100 条输入不能只靠对话记忆因为中间可能崩溃、超时、并发冲突。建议加一个任务列表每条记录包含item_id当前处理到哪一条status待处理、处理中、成功、失败、跳过error_message失败原因retry_count已经重试几次伪代码思路大概是for item in task_list: select * from task_state where item_id current if status in (success, skip): continue try: reply agent.chat(build_prompt(item, memory)) save_output(reply) update_state(item_id, statussuccess) except Exception as e: update_state(item_id, statusfailed, errorstr(e))这样既能断点续跑也能统计失败率。不要一上来就开高并发先把单条跑通再按 2、4、8 逐步往上加。4. 关键参数和效果判断标准4.1 记忆有没有生效先看这四个现象很多人在调试记忆时只看模型有没有报错。报错少了但记忆不一定生效。我会按下面四个现象逐个确认重新开会话后模型能不能说出用户上次提到的偏好。同一个问题问两次如果历史已经有结论模型是否会引用而不是重新回答。修改偏好后模型是否按新偏好走而不是继续沿用过时的旧偏好。批量任务重启后能否从上次失败点继续而不是从头开始。如果这四个现象都没出现记忆链路大概率有问题。如果只有部分出现说明写入、检索、注入之间的某个环节没对齐。4.2 常用参数和参考范围参数设置会直接影响记忆效果。下面是我在实际调试中会留意的几项范围仅供参考需要按你的模型和数据量调整参数作用参考建议top_k每次注入几条记忆3 到 8 条比较稳太多会冲淡当前问题similarity_threshold低于多少相似度不算相关0.6 左右起步太低会带回噪声memory_expire_days记忆多少天后过期个人偏好类可以长临时状态类建议短max_inject_length注入记忆的最大字符数1000 到 2000 字符避免吃掉上下文max_items_per_user单用户最多保留多少条500 条左右触发压缩和清理参数之间会互相影响。top_k 调大后响应时间会变长噪声也变多similarity_threshold 调高后命中率会下降。我建议一次只调一个参数每次对照 20 条真实对话来看效果不要同时改好几个。4.3 资源占用和响应速度怎么判断记忆模块不是免费的。加记忆后响应时间通常会增加原因主要有三个读取存储、向量检索或 embedding 调用、注入后让模型读更多 token。如果只是本地 JSON 文件加关键词过滤每次增加的开销很小。如果接向量库和 embedding就要关注单次检索耗时。一个可以接受的判断标准是加了记忆后单次响应时间比不加时增加不超过 20%-30%。超过这个范围先看是不是检索太慢再看是不是注入内容太多。如果用的是云端 embedding 接口网络耗时和限流也要算进去。4.4 冷启动问题没有历史记忆怎么办新用户或新任务没有历史记忆时记忆模块不能报错也不能给模型塞空数据。建议做降级处理没有检索到记忆就只传一个空标识让模型按普通对话处理同时把第一轮对话中提取到的信息写入记忆作为后续的冷启动数据。冷启动阶段不要急着让模型“背熟”用户第一轮多收集事实少做推断。比如用户说“我是做 Java 开发的”这算事实用户说“我觉得 Spring 挺好”这只能算观点不应该直接写死成长期偏好。5. 常见问题排查链路不是所有“记不住”都是记忆模块的问题5.1 先确认现象卡在哪个环节“模型记不住”听起来是一个问题实际可能发生在三个不同环节写入环节对话后根本没有保存成功。检索环节保存进去了但查询时没取出来。注入环节取出来了但没放进 prompt或者放了但被系统指令覆盖。排查顺序建议按这个链路走。第一步打开日志确认每一轮处理有没有执行 memory_store.save 和 memory_store.retrieve。如果 save 没执行检查代码路径和异常捕获如果 save 执行但 retrieve 结果为空检查 user_id 是否一致如果 retrieve 有结果但模型输出没有体现检查 prompt 组装顺序和指令冲突。5.2 用户 ID 和会话 ID 混用是最高频问题很多次“记忆不生效”的根因是写入时用的 user_id 和读取时用的不是同一个。比如写入用账号 ID读取用了一次会话临时生成的 session ID或者第一次登录用的 ID 大小写不同。排查时先在日志里把 user_id、session_id、请求时间打出来确认两次请求使用的是同一个稳定 ID。我习惯把用户 ID 单独设置不跟会话 ID 混在一起。会话 ID 只用于当前对话窗口的上下文用户 ID 才用于长期记忆。如果当前系统只有会话 ID就先在会话里加一个匿名映射表而不是把会话 ID 当用户 ID 用。5.3 检索到了但命中质量差如果 retrieve 返回了内容但模型还是不像“记得”大概率是检索结果相关性不够。常见原因有三个相似度阈值设置过低导致无关记忆混进来top_k 设置过小把真正相关的漏掉了embedding 模型和文本语言不匹配中文内容用效果差的向量表示排序自然乱。可以先从日志里把本次检索到的记忆条目录出来人工判断前几条是否真的相关。如果不相关优先调整相似度阈值和 top_k如果还是不行再考虑换更合适的 embedding 模型或加关键词过滤前置。5.4 存储污染记忆越来越多反而越来越笨记忆不是越多越好。一旦存储里积累了互相矛盾、过时、重复的记忆模型就会混乱。典型的例子是用户第一次说“回答要简短”后来修改为“详细一点”系统把两条都存进去了模型不知道该听哪条。建议在写入前做一次冲突检测同一 type 的旧记忆要么覆盖要么标注过期。周期性运行清理任务把 expire_days 之外的临时状态删除把同一内容重复出现的条目合并。这样才能保证记忆库始终是“越用越准”而不是“越用越吵”。6. 边界与后续优化记忆不是越久越好也不是越多越好6.1 什么场景最适合先上记忆不是所有智能体都需要复杂记忆。如果你只是拿 Hermes 做一次性问答那直接依赖上下文窗口就够了。真正适合上记忆外挂的场景通常有这些特征用户会反复回来做相似任务并且希望你记得上次的设定。任务是多步骤的中间断了需要恢复。批量处理时重复失败会让成本快速上升。个人助理、客服问答、文档处理、自动化测试、运维巡检这类场景先上用户级和任务级记忆的收益最明显。如果你的场景基本是单轮、无状态、每次都是全新输入那记忆模块就容易变成负担反而不如不加。6.2 建立遗忘机制比无限存储更重要长期使用的记忆系统一定要有“忘掉”的能力。模型上下文窗口有限存储空间也有限更重要的是旧信息会误导新决策。可以用时间衰减让很久没访问的记忆权重下降可以用 LRU 策略优先淘汰最久没使用的条目也要允许用户手动删除或修改某条记忆。不要把所有事都交给模型自己判断。用户明确说“忘掉之前说的部署方式”系统就应该把对应记忆标记删除而不是再由模型决定是否保留。这点在做记忆外挂时最容易忽略也是影响体验的关键。6.3 数据安全与隐私控制记忆会保存用户偏好、项目信息、任务状态这里面的数据保护要提前设计。基本原则是能本地存储就不要外传能脱敏就不要存明文能最小化就不要整段保存。比如用户对话里提到账号密码、密钥一类的信息写入前就应该过滤掉。要给用户提供查看和删除记忆的入口。至少提供“清除全部记忆”和“清除单条记忆”两个操作。做接口化部署时记忆的读取和写入都要加权限控制不要让其他用户可以查到别人的历史记录。这一条不是锦上添花而是生产环境的基本要求。6.4 从单机到生产化的推进顺序我的建议是不要一上来就搭分布式向量库。先按最小闭环跑通本地 JSON 或 SQLite单用户验证。确认写入、检索、注入都正常后再考虑高并发和长期运行。生产化时关注四件事一是存储替换把 JSON 文件换成带索引的数据库避免并发写冲突二是唯一 ID 和审计日志每条记忆写入和删除都要可追踪三是检索性能当单用户记忆超过几百条时再引入向量检索四是测试覆盖准备一组固定场景做回归防止改 prompt 时把记忆链路改坏。踩过几次之后最大的感受是很多“记不住”的问题不是模型不够聪明而是记忆链路没有闭环。先把写入、读取、注入三步跑通再谈优化检索和参数。可以先从一条用户记录开始连续问三次看模型能不能记住你说过的偏好。能记住再扩展到批量任务记不住就回头查日志问题多半在前面三步里。