它把我说过的话全忘了——Hermes Agent 中文跨会话记忆失灵的完整修复指南
它把我说过的话全忘了——Hermes Agent 中文跨会话记忆失灵的完整修复指南
昨天你花了两小时,用中文跟 Agent 聊清楚了你的项目背景、偏好、工作流。
今天重开会话,它用一句话把你打回原点:
"抱歉,我没有关于这方面的相关记忆。"
---
你没做错任何事。配置是对的,存储路径是对的,调用格式也是对的。但它就是忘了。
这不是偶发 bug,是中文用户在 Hermes Agent 上高频踩坑的系统性缺陷。我在自己的项目里连续踩了三次,最后用两个方案彻底修掉了它。
本文把根因和修复方案一次说清楚。
---
一、先区分两种"遗忘"
在展开之前,必须先把问题范围钉准。
Hermes Agent 的"遗忘"有两种,解法完全不同:
- 同会话遗忘:在同一次对话中,Agent 对几轮前说过的话失忆。这是上下文窗口截断问题,本文不讲。
- 跨会话遗忘:关闭会话、重新打开后,上一次对话的所有内容全部消失。这是记忆持久化的问题,本文专攻这个。
跨会话遗忘的典型症状:
1. 英文会话关闭重开,Agent 能正常召回上次内容
2. 中文会话做同样操作,召回成功率骤降
3. 日志显示记忆写入成功,但读取时返回空结果或不相关内容
这个"英文好好的,中文就不行"的现象,是定位根因的关键线索。
---
二、为什么偏偏中文会失灵?——三层根因拆解
我做了一个对比实验:用相同的问题分别以英文和中文写入记忆,再用语义相近的问题召回,统计 20 组样本的召回成功率。
| 语言 | 测试组数 | 召回成功 | 召回成功率 | | 英文 | 20 | 18 | 90% | | 中文 | 20 | 7 | 35% |35% vs 90%,差距不是一点点。
原因出在写入→存储→召回的三个环节,每一层都有问题。
写入层 存储层 召回层
│ │ │
▼ ▼ ▼
中文 token 编码不一致 相似度阈值
切分偏移 UTF-8 vs 对中文语义
导致向量 系统默认编码 不友好
质量差 产生乱码 key
│ │ │
└───────────────┴───────────────┘
│
记忆召回失败
根因 ① 中文 token 切分导致 embedding 向量偏移
Hermes Agent 默认使用的向量化模型针对英文优化,中文分词粒度粗糙。
举个例子:"我在做一个电商数据分析项目"这句话,英文向量模型可能把它切成 ["我", "在做", "一个", "电商", "数据分析", "项目"] 这样的粗粒度 token,而专为中文设计的模型会切成更细的语义单元,向量质量差异显著。
向量质量差 → 存入数据库的特征表示不准确 → 召回时余弦相似度低于阈值 → 记忆找不到。
根因 ② 存储时 key 编码不一致
这是最隐蔽的问题。Hermes Agent 在写入记忆时,会用用户输入生成一个索引 key。
在 Windows 系统上,Python 的默认编码是 cp936(GBK),而向量数据库期望的是 UTF-8。中文字符在两种编码下的字节序列完全不同,导致:
- 写入时:用 GBK 编码生成 key,存入数据库
- 读取时:用 UTF-8 解码 key,匹配失败,返回空
日志显示"写入成功"是真的,但那条记录永远找不到。
根因 ③ 召回阶段相似度阈值对中文不友好
默认的余弦相似度阈值通常设在 0.75 左右,这对英文语义空间是合理的。
但由于中文向量质量本身偏低(根因①),即使是语义高度相关的两句中文,其向量相似度也可能只有 0.6~0.7,直接被阈值过滤掉。
三个根因叠加,35% 的召回率已经算是运气好了。
---
三、修复方案一:强制 UTF-8 + 中文向量模型替换
这是改动最小、见效最快的方案。适合已有项目的快速修复。
💡 本文代码示例基于标准 OpenAI API 格式调用。如果你还没有顺手的 API 接入点,可以试试 [api.884819.xyz](https://api.884819.xyz)——支持多模型切换,中文场景下的向量模型也能直接调用,方案一的替换成本会低很多。新用户注册即送体验 token,国产模型完全免费。
核心思路:
1. 强制所有字符串操作使用 UTF-8 编码(解决根因②)
2. 替换向量化模型为 text2vec-large-chinese(解决根因①)
3. 降低中文场景的相似度阈值至 0.60(解决根因③)
# memory_fix_v1.py
方案一:UTF-8 强制编码 + 中文向量模型替换
解决根因 ①②③
import os
import json
import hashlib
from sentence_transformers import SentenceTransformer
import numpy as np
---- 根因② 修复:强制设置 Python 默认编码 ----
import sys
if sys.stdout.encoding != 'utf-8':
sys.stdout.reconfigure(encoding='utf-8')
---- 根因① 修复:替换为中文友好的向量模型 ----
原始配置通常是 'all-MiniLM-L6-v2'(英文优化)
替换为 text2vec-large-chinese
EMBEDDING_MODEL = "GanymedeNil/text2vec-large-chinese"
embedder = SentenceTransformer(EMBEDDING_MODEL)
---- 根因③ 修复:调低中文场景相似度阈值 ----
SIMILARITY_THRESHOLD = 0.60 # 原始默认值通常为 0.75
def encode_key(text: str) -> str:
"""
生成记忆 key:强制 UTF-8 编码后做 hash
解决根因②:避免系统默认编码(GBK)污染 key
"""
# 关键:encode 时显式指定 utf-8
text_bytes = text.encode('utf-8')
return hashlib.md5(text_bytes).hexdigest()
def write_memory(memory_store: dict, user_input: str, content: str) -> None:
"""
写入记忆:向量 + key 双写
"""
key = encode_key(user_input)
# 根因① 修复:用中文向量模型生成高质量 embedding
vector = embedder.encode(user_input, normalize_embeddings=True).tolist()
memory_store[key] = {
"raw_input": user_input, # 原始中文文本,UTF-8 存储
"content": content,
"vector": vector
}
print(f"[写入成功] key={key[:8]}... | 文本={user_input[:20]}")
def recall_memory(memory_store: dict, query: str, top_k: int = 3) -> list:
"""
召回记忆:余弦相似度 + 调整后的阈值
"""
if not memory_store:
return []
query_vector = embedder.encode(query, normalize_embeddings=True)
results = []
for key, mem in memory_store.items():
stored_vector = np.array(mem["vector"])
# 余弦相似度(已 normalize,直接点积)
similarity = float(np.dot(query_vector, stored_vector))
# 根因③ 修复:使用调整后的阈值 0.60
if similarity >= SIMILARITY_THRESHOLD:
results.append({
"content": mem["content"],
"similarity": similarity,
"raw_input": mem["raw_input"]
})
# 按相似度降序排列,返回 top_k
results.sort(key=lambda x: x["similarity"], reverse=True)
return results[:top_k]
---- 测试验证 ----
if __name__ == "__main__":
memory_store = {}
# 模拟第一次会话写入
write_memory(memory_store, "我在做一个电商数据分析项目", "用户正在开发电商销售数据分析系统")
write_memory(memory_store, "我偏好用 Python 处理数据", "用户技术栈:Python,数据处理")
# 模拟第二次会话召回
query = "帮我写一段数据分析的代码"
recalled = recall_memory(memory_store, query)
print(f"\n[召回结果] 查询:{query}")
for r in recalled:
print(f" 相似度={r['similarity']:.3f} | {r['content']}")
修复前后对比:
| 指标 | 修复前 | 修复后 |
| 中文召回成功率 | 35% | 82% |
| 编码错误导致的空结果 | 频繁 | 消除 |
| 跨会话记忆恢复 | 基本失效 | 正常工作 |
方案一能解决大部分场景,但它依赖语义相似度,当用户的查询措辞和写入时差异较大时,仍然可能漏召回。这时候需要方案二。
---
四、修复方案二:结构化记忆 + 显式 Key 索引
这个方案更稳健:不依赖语义相似度,改用结构化 JSON + 显式中文 key 索引。
核心思路:把记忆拆成有明确类别的结构化数据,召回时先走 key 精确匹配,再走语义兜底。
适合场景:用户信息、项目背景、偏好设置等高价值、需要精准召回的记忆。
# memory_fix_v2.py
方案二:结构化 JSON 记忆 + 显式 key 索引
适合高精度召回场景
import json
import os
from datetime import datetime
from typing import Optional
预定义记忆类别(中文 key,UTF-8 存储)
MEMORY_CATEGORIES = {
"项目背景": "project_context",
"技术偏好": "tech_preference",
"用户信息": "user_profile",
"工作习惯": "work_habit",
"历史决策": "past_decision"
}
MEMORY_FILE = "structured_memory.json"
def load_memory() -> dict:
"""从文件加载记忆,强制 UTF-8"""
if not os.path.exists(MEMORY_FILE):
return {cat: [] for cat in MEMORY_CATEGORIES}
with open(MEMORY_FILE, "r", encoding="utf-8") as f:
return json.load(f)
def save_memory(memory: dict) -> None:
"""持久化到文件,强制 UTF-8,ensure_ascii=False 保留中文"""
with open(MEMORY_FILE, "w", encoding="utf-8") as f:
json.dump(memory, f, ensure_ascii=False, indent=2)
def write_structured_memory(
category: str,
content: str,
tags: list[str] = None
) -> bool:
"""
写入结构化记忆
category: 必须是 MEMORY_CATEGORIES 中的中文 key
tags: 可选标签,用于辅助检索
"""
if category not in MEMORY_CATEGORIES:
print(f"[错误] 未知类别: {category},可用类别: {list(MEMORY_CATEGORIES.keys())}")
return False
memory = load_memory()
entry = {
"id": f"{MEMORY_CATEGORIES[category]}_{int(datetime.now().timestamp())}",
"content": content,
"tags": tags or [],
"created_at": datetime.now().isoformat(),
"category_cn": category
}
memory[category].append(entry)
save_memory(memory)
print(f"[写入成功] 类别={category} | 内容={content[:30]}...")
return True
def recall_by_category(category: str) -> list:
"""精确按类别召回,100% 成功率"""
memory = load_memory()
return memory.get(category, [])
def recall_by_tag(tag: str) -> list:
"""按标签召回,跨类别检索"""
memory = load_memory()
results = []
for cat_entries in memory.values():
for entry in cat_entries:
if tag in entry.get("tags", []):
results.append(entry)
return results
def recall_all_context() -> str:
"""
召回全部记忆,格式化为 Agent 可直接使用的上下文字符串
在每次会话开始时调用,注入系统 prompt
"""
memory = load_memory()
context_parts = []
for category, entries in memory.items():
if entries:
context_parts.append(f"【{category}】")
for entry in entries[-3:]: # 每类别取最近 3 条
context_parts.append(f" - {entry['content']}")
if not context_parts:
return "(暂无历史记忆)"
return "\n".join(context_parts)
---- 测试验证 ----
if __name__ == "__main__":
# 第一次会话:写入记忆
write_structured_memory("项目背景", "用户正在开发电商销售数据分析系统,使用 Python + Pandas", tags=["电商", "Python", "数据分析"])
write_structured_memory("技术偏好", "偏好 Python,不喜欢 Java,习惯用 Jupyter Notebook", tags=["Python", "工具"])
write_structured_memory("用户信息", "数据分析师,3年经验,擅长可视化", tags=["职业"])
# 第二次会话:召回所有上下文
print("\n=== 第二次会话开始,注入历史记忆 ===")
context = recall_all_context()
print(context)
# 这段 context 直接拼入 system prompt:
system_prompt = f"""你是用户的专属 AI 助手。
以下是用户的历史背景信息,请在回答时充分参考:
{context}
请基于以上背景,回答用户的问题。"""
print("\n[System Prompt 预览]")
print(system_prompt[:200] + "...")
---
五、两个方案怎么选?
| 维度 | 方案一(向量召回) | 方案二(结构化索引) | | 适用场景 | 自然对话记忆、模糊查询 | 用户画像、项目配置、精准信息 | | 实现复杂度 | 中(需要替换向量模型) | 低(纯 JSON 操作) | | 召回稳定性 | 中(依赖语义相似度) | 高(精确匹配) | | 扩展性 | 高(支持海量记忆) | 中(类别固定) | | 推荐指数 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 我的实践建议:两个方案组合用。结构化索引处理"用户基本信息、项目背景"等核心记忆,向量召回处理"历史对话片段"等非结构化内容。两路并行,互为兜底。---
六、中文 Agent 记忆的 5 条通用避坑原则
踩完这些坑之后,我整理了一份通用原则,适用于 LangChain、AutoGen 等其他 Agent 框架——这些框架存在完全相同的中文记忆问题。
原则 1:永远显式指定 UTF-8 编码所有涉及中文字符串的文件读写,无论是 open()、json.dumps() 还是数据库写入,必须显式声明 encoding="utf-8" 和 ensure_ascii=False。不要信任系统默认值。
英文向量模型处理中文就像用英汉词典翻译日文。text2vec-large-chinese、bge-large-zh 是目前中文效果最好的开源选择,换掉默认的 all-MiniLM-L6-v2。
这不是降低标准,是适配中文向量空间的现实分布。英文阈值直接套用在中文上会过滤掉大量有效记忆。
原则 4:高价值记忆用结构化存储,不要全靠语义召回语义召回是概率游戏,用户的姓名、项目名、关键偏好这类信息不能靠运气。结构化 + 显式 key 是保底策略。
原则 5:每次会话开始时主动注入历史上下文不要等 Agent 自己去"想起来",在 system prompt 里直接塞入格式化的历史记忆。主动注入比被动召回可靠一个数量级。
延伸思考:LangChain 的 ConversationSummaryMemory 和 AutoGen 的记忆模块存在完全相同的中文适配问题,根因相同,修复思路也相通。如果你在用这两个框架,上面的原则同样适用,代码层面的适配改动不大。
---
写在最后
修好记忆只是第一步。
当 Agent 记住的东西越来越多——你的项目、你的习惯、你说过的每一句话——一个新问题会浮出水面:
它怎么决定"哪些该忘掉"?无限增长的记忆库会让召回越来越慢,相互矛盾的旧记忆会干扰新判断。中文场景下,这个问题因为向量空间的分布特性,会比英文更早爆发。
记忆压缩、主动遗忘、记忆合并——我正在测试几套策略,数据跑完了就来写。
下篇见。
---
本文由8848AI原创,转载请注明出处。关注8848AI,带你从零开始学AI。#AI教程 #Agent开发 #中文AI #Hermes #记忆管理 #Python #8848AI #AI踩坑实录