作者:toy
一、Agent 为什么需要记忆
无状态 LLM 的根本局限
把一个 LLM 想象成一位每隔五分钟就会彻底失忆的顾问。你在上午告诉他你的背景、偏好、当前项目的约束条件,五分钟后他已经一无所知,对话只能从头开始。这不是比喻,而是 LLM 的默认运行状态。每次 API 调用都是一次独立的无状态推理,模型没有持久化的”自我”,也不记得昨天发生了什么。
这对简单问答几乎没有影响。但 Agent 做的不是问答,它要完成持续多步、跨时间、依赖历史状态的任务。一个 Agent 在今天用工具搜集了竞品信息,明天需要在这个基础上生成分析报告,如果它不记得昨天搜到了什么,整个任务链就断了。无状态的推理引擎无法支撑有状态的工作流程,这是 LLM 成为 Agent 时必须解决的根本矛盾。
记忆让 Agent 得以延续任务、积累知识、认识用户。没有记忆的 Agent 每次都在重新发明轮子,每次都需要用户重新介绍背景,每次都无法从过去的错误中学习。记忆不是 Agent 的附加功能,而是 Agent 能称之为 Agent 的前提。
记忆的四种类型
认知科学区分了人类记忆的多种类型,Agent 记忆的分类与之高度对应。
工作记忆(Working Memory)对应 LLM 的 Context Window,是 Agent 当下正在处理的”工作台”,容量有限,读写最快。LLM 的所有推理都发生在这个工作台上,超出窗口的信息必须被明确写入或检索回来才能被使用。
情节记忆(Episodic Memory)记录”发生了什么”,是带时间戳的历史事件流。Agent 的对话历史、任务执行日志、用户的具体操作记录都属于这一类。情节记忆保留了背景和因果链,是回溯推理的基础。
语义记忆(Semantic Memory)存储抽象知识,是从具体事件中蒸馏出的通用概念。用户的偏好摘要、知识库中的文档、从历史对话中提取的事实,这些都是语义记忆。它不记录”什么时候发生的”,只记录”什么是真的”。
程序记忆(Procedural Memory)是”怎么做”的记忆,对应技能和操作流程。EverOS 的 Agent Skill 机制就是程序记忆的工程化表达:当一类任务被成功执行多次后,执行轨迹被蒸馏为可复用的 Skill,成熟度分数达到阈值后自动注入提示词。Agent 因此不需要每次面对同类任务都从头推理,而是直接调用内化的程序知识。
这四类记忆在工程上对应四种完全不同的存储和检索机制。理解这个分类,是理解后续所有技术选型的基础。
二、短期上下文:Context Window 的用法与局限
Context Window 是 Agent 的工作台
Context Window(上下文窗口)是 LLM 当前可以”看到”的全部信息。模型的推理、工具调用、回复生成,都基于这个窗口里的内容。任何不在窗口里的信息,对模型来说等同于不存在。
这个工作台的特点是:访问速度极快(没有检索延迟),但容量有限、成本高昂。GPT-4 的 128K token 窗口,处理一次大约消耗 0.01 美元;Claude 3.5 的 200K 窗口稍贵。长上下文不是免费的,每多一个 token,推理成本都在增加。对于一个需要连续运行数小时、处理数百轮对话的 Agent,把所有历史塞进上下文是不现实的。
上下文窗口的最优使用策略,是让它只承载当前推理步骤所需的最少充分信息。不是尽量多塞,而是精确装载。这需要 Agent 有主动管理上下文内容的能力,而不是被动地积累所有历史。
有效上下文管理
什么信息该放进上下文,什么该暂时丢掉?这个问题没有通用答案,但有几条判断原则。
当前任务的直接输入和约束条件必须在窗口里。如果 Agent 正在生成一份报告,报告的要求、格式规范、当前已完成的草稿都需要在上下文里。与当前步骤无关的历史对话可以暂时移出窗口,但需要通过摘要或外部存储保留关键信息。
工具调用的结果是上下文污染的主要来源。一次数据库查询可能返回几千行原始数据,而 Agent 真正需要的可能只是其中的几行。把原始结果直接塞进上下文是一种浪费。更好的做法是在工具层做预处理:只把关键结果传递给 Agent,把完整原始数据写入外部文件,并在上下文里留一个文件引用路径。分层符号化记忆的核心思路也是这样:底层保留证据,高层保留结构。
系统提示(System Prompt)是另一个重要的上下文消耗来源。一个完整的 Agent 系统提示可能包含角色定义、工具说明、操作规范,轻松超过 2000 token。对于长对话,系统提示会反复被计费。Anthropic 的 Prompt Caching 机制通过将静态系统提示缓存,可以把重复部分的成本降低约 90%。这是生产级 Agent 必须启用的优化。
Token 成本与信息密度的权衡
上下文管理的本质是信息密度优化。同样的语义信息,可以用 200 token 表达,也可以用 2000 token 表达。Agent 工程师的任务之一,是设计高密度的上下文表示。
Mermaid 图是一个典型例子。一个多步任务的当前状态,用自然语言描述可能需要 500 token,用 Mermaid 流程图可能只需要 80 token,但 LLM 对两者的理解深度几乎相同。结构化格式(JSON、YAML、Markdown 表格)通常比自然语言散文更紧凑,且 LLM 对结构化格式的解析更准确。
信息密度不等于信息压缩。有些细节丢失是可接受的(如对话中的客套话),但有些细节是关键的(如用户明确提到的约束条件)。区分两者需要领域知识和任务理解,这也是为什么好的上下文管理最终需要一定程度的 LLM 参与,让模型自己判断哪些内容值得保留。
长上下文模型真的解决了记忆问题吗
Gemini 1.5 Pro 的 1M token 窗口发布时,很多人认为记忆问题从此解决了。把所有历史对话、所有相关文档全部塞进上下文,让模型自己处理,不是更简单吗?
实验数据给出了否定的答案。斯坦福的研究发现,当关键信息被放置在长上下文的中间部分时,模型的回忆准确率显著下降,这被称为”Lost in the Middle”问题。模型的注意力在长上下文中不均匀分布,倾向于记住开头和结尾,中间部分容易被忽略。
稀疏注意力研究进一步揭示了机制层面的原因。标准 Transformer 的注意力复杂度是 O(L²),其中 L 是序列长度。100 万 token 的上下文意味着计算量约是 1000 token 上下文的 10 亿倍。即便通过工程优化降低了计算成本,模型在超长序列上的有效推理能力也会因”注意力稀释”而下降:信息太多,每个 token 分到的注意力权重变得极小。
1M token 上下文不是记忆的终点,而是工作台的扩大。更大的工作台让更多信息可以同时”可见”,但不能保证所有可见的信息都被”有效使用”。精确的记忆管理,知道什么时候需要什么信息、把正确的信息放到正确的位置,依然是必须解决的工程问题。
三、记忆压缩:让有限窗口装下无限历史
摘要压缩
最直接的记忆压缩策略是自动摘要。当对话历史超过阈值(通常是窗口容量的 70-80%),让 LLM 把历史对话压缩为一段摘要,用摘要替换原始历史,为新的对话轮次腾出空间。
这个方法实现简单,但有明显的信息损失风险。摘要必然会丢失一些细节,而哪些细节是”安全可丢的”,取决于未来的任务需求,而未来的任务在摘要时是未知的。一个常见的工程陷阱:把用户在第三轮说的一个技术约束条件摘要掉了,第二十轮 Agent 违反了这个约束,用户体验极差。
缓解这个问题的一个办法是分层摘要:保留最近 N 轮的完整历史,对更早的历史做逐级摘要。近期发生的事情细节完整,远期发生的事情保留概要。这符合人类记忆的运作方式:昨天的细节记得清楚,三个月前的只记得大概。
摘要质量本身也是一个需要关注的问题。自动摘要依赖 LLM 的理解和归纳能力,不同模型的摘要质量差异显著。在生产系统里,建议对摘要做质量校验:把摘要和原始历史都喂给 LLM,让它判断摘要是否遗漏了关键信息。这增加了成本,但避免了无声的信息损失。
实体提取
比全文摘要更精确的策略是结构化实体提取。从对话历史中抽取关键实体(人名、项目名、日期、技术约束、用户偏好),将其存储为结构化的事实列表,而不是保留整段对话原文。
# 实体提取示例:从对话轮次中抽取结构化事实
import json
from anthropic import Anthropic
client = Anthropic()
def extract_entities_from_turn(user_msg: str, assistant_msg: str) -> dict:
"""从单轮对话中提取结构化事实"""
prompt = f"""从以下对话中提取关键事实,输出 JSON 格式。
用户消息:{user_msg}
助手回复:{assistant_msg}
提取规则:
1. 只提取明确陈述的事实,不推断
2. 用户偏好/约束必须保留
3. 日期、版本号、技术规格等具体数值必须精确
输出格式:
{{
"entities": [{{"name": "...", "type": "person/project/constraint/preference", "value": "..."}}],
"facts": ["陈述句1", "陈述句2"],
"constraints": ["约束1", "约束2"]
}}"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
try:
return json.loads(response.content[0].text)
except json.JSONDecodeError:
return {"entities": [], "facts": [], "constraints": []}
实体提取的优点是精确:每个事实都是一条可以独立检索和验证的陈述句,不存在”摘要里没提到”的问题,因为重要的事实本来就不会进摘要,而是直接被提取出来。缺点是丢失了上下文背景,离开了原始对话,孤立的事实有时失去了它本来的意义。
EverMemOS 的 MemCell 三元结构是对这个问题的一个回答。它把每个记忆单元设计为三元组:Episode(叙事背景,保留上下文)、AtomicFact(可验证的离散事实)和 Foresight(带有效期的预测性状态)。三者共同构成一个记忆单元,在检索时作为整体返回,既有精确事实,也有解释事实所需的背景。
选择性遗忘
不是所有记忆都值得保留。一次对话里包含大量噪音:闲聊、过时的假设、被后续讨论推翻的决策。如果把所有内容都平等地存入记忆库,系统会被无关信息淹没,检索质量随时间单调下降。
选择性遗忘机制给每条记忆赋予重要性评分,低分记忆定期淘汰。评分维度通常包括:明确性(用户是否明确说”记住这个”)、访问频率(这条记忆被检索到了多少次)、时效性(信息是否已经过时)和相关性(与核心任务的关联度)。
MemOS 的 MemCube 使用 LRU(最近最少使用)和 LFU(最不常用)的组合淘汰策略,并定义了五态生命周期:Generated(刚创建)→ Activated(被访问激活)→ Merged(被合并到更高层)→ Archived(冷存储)→ Expired(过期删除)。这个生命周期管理让记忆系统具备了自动清洁的能力,不需要人工干预。
Foresight 机制是选择性遗忘的一种特殊形式。传统系统把所有事实都当作永久真理,”用户有乳糖不耐受”和”用户本周需要避免咖啡因”在系统里具有同等地位。Foresight 让每条状态性事实携带有效期,过期后自动转为 Archived 状态。这避免了临时状态被错误地长期保留,也避免了”用户本周戒咖啡因”在三个月后还在影响推荐结果。
会话分割与连贯
长任务无法在单次会话中完成。一个持续数天的项目调研任务,可能需要跨越几十次独立会话。如何在会话之间保持任务连贯,是 Agent 记忆工程的核心挑战之一。
分割点的选择比看起来更重要。在自然边界处分割(一个子任务完成后)比在硬性 token 限制处分割效果好得多。子任务完成意味着有了一个清晰的状态快照可以作为下一次会话的起点:任务目标、已完成的步骤、待处理的问题、当前的中间结果。
每次会话结束时生成一份结构化的”接力文件”是一种可靠的工程模式:
# 任务接力文件 - 竞品分析项目
## 当前状态
- 已完成:搜集了 A/B/C 三家产品的公开信息
- 进行中:对比 A 和 B 的定价策略
- 待处理:收集用户评价数据
## 关键发现
- A 产品 2025Q4 更新了企业定价,详见 refs/product-a-pricing.md
- B 产品专注中小企业,与我们的目标用户重叠度高
## 约束条件
- 报告截止日:2026-06-15
- 不考虑海外市场竞品
## 下一步
1. 搜索 B 产品的用户评价
2. 整理 A vs B 的功能对比表
这个文件在下一次会话开始时注入系统提示,让 Agent 立即恢复到正确的工作状态。它是短期上下文和长期记忆之间的桥接器:比完整历史小得多,但比零信息有效得多。
四、长期向量记忆:让 Agent 记住一切
向量存储的基本原理
向量存储的核心思想是把语义相似性转化为空间距离。文本被 Embedding 模型转换为高维向量(通常 512 到 3072 维),语义相近的文本在向量空间里距离较近。”苹果手机的价格”和”iPhone 的报价”会被映射到向量空间里相近的位置,即便它们没有共同的关键词。
这个机制让 Agent 可以用自然语言提问来检索记忆,不需要知道记忆的精确措辞。当 Agent 需要回忆”用户对我们产品的主要不满”时,它生成这个查询的向量,在记忆库里找到距离最近的 K 个向量,把对应的文本返回给上下文。整个过程不需要精确匹配,依赖的是语义级别的近似。
向量检索的时间复杂度理论上是 O(N),需要和库中所有向量计算距离。在小规模(几万条记忆)场景下,暴力计算完全可行。但当记忆条目达到百万甚至亿级别时,暴力计算变得不可接受,需要近似最近邻(ANN)算法:把向量空间划分为若干聚簇(IVF)或构建图结构(HNSW),把搜索范围限制在候选集里。这会引入一定的召回损失(找到的不一定是最近的,但通常在前几名),但速度提升可达数十倍。
Embedding 模型的选择
Embedding 模型是向量记忆系统的基础,模型质量直接决定检索质量。一个差的 Embedding 模型会把语义不相关的文本映射到相近的位置,导致召回结果充斥噪音。
商业选项里,OpenAI 的 text-embedding-3-small(1536 维,每百万 token 约 0.02 美元)和 text-embedding-3-large(3072 维,约 0.13 美元)是主流选择。text-embedding-3-large 在多语言和专业领域文本上有明显优势,对于中文 Agent 应该是首选。
开源选项里,nomic-embed-text-v1.5 是性能/体积比最优的选择之一:137M 参数,支持 8192 token 上下文,在 MTEB 中文测试集上表现与商业模型相当。BGE-M3 是另一个值得关注的选项,来自智源研究院,专门针对中文优化,同时支持密集向量、稀疏向量和多粒度表示三种检索模式。
选择 Embedding 模型时需要注意的是:模型一旦确定,所有历史记忆都用同一个模型生成向量。更换模型意味着需要对所有历史数据重新 Embedding,这在记忆条目达到百万级别时是一项不小的工程。选型应该考虑长期稳定性,不要频繁切换。
# Embedding 生成示例:使用 OpenAI API
from openai import OpenAI
import numpy as np
client = OpenAI()
def get_embedding(text: str, model: str = "text-embedding-3-small") -> list[float]:
"""生成文本向量,清理换行符避免影响质量"""
text = text.replace("n", " ")
response = client.embeddings.create(
input=[text],
model=model
)
return response.data[0].embedding
def cosine_similarity(vec1: list[float], vec2: list[float]) -> float:
"""计算余弦相似度"""
a = np.array(vec1)
b = np.array(vec2)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 存储时:生成并保存向量
memory_text = "用户偏好中文界面,不习惯英文操作"
memory_vector = get_embedding(memory_text)
# 检索时:查询向量与库中所有向量计算相似度
query = "用户的界面语言偏好"
query_vector = get_embedding(query)
similarity = cosine_similarity(query_vector, memory_vector)
# similarity ≈ 0.87,高度相关
向量数据库的实际部署
向量 Embedding 解决了表示问题,向量数据库解决的是存储和检索的工程问题。一个向量数据库需要提供:高效的 ANN 索引、持久化存储、元数据过滤(按时间/用户/类型过滤)、CRUD 操作,以及随着数据量增长的可扩展性。
什么时候需要专门的向量数据库?当记忆条目超过几千条,需要按元数据过滤,或者需要在多个 Agent 之间共享记忆时,专门的向量数据库开始有价值。小于几千条记忆的场景,把向量存进 SQLite 的 BLOB 字段,查询时加载到内存做暴力计算,是完全可行且更简单的方案。qmd 就是这个思路的一个实现:本地 SQLite 存储,Rust 实现,支持混合检索,零服务器成本,10 分钟配置完成。
五、FAISS vs Milvus:选型指南
FAISS:研究利器,工程短板
FAISS(Facebook AI Similarity Search)是 Meta 开源的向量检索库,C++ 实现,Python 接口。它的定位是检索算法库,不是完整的数据库系统。FAISS 提供了极其丰富的索引类型(Flat、IVF、HNSW、PQ、LSH 等),每种都有精确的性能-准确率权衡参数。
FAISS 的强项是速度和算法灵活性。在单机场景下,FAISS 的检索速度通常是其他方案中最快的,因为它直接暴露 CPU/GPU 层面的操作,没有网络开销,没有序列化开销。对于需要自定义索引结构或者在 GPU 上跑高性能向量检索的研究场景,FAISS 是首选。
但 FAISS 的局限也很明显。它没有内置持久化,索引存在内存里,程序退出后数据丢失,需要手动序列化到磁盘。它没有 CRUD 支持,删除和更新向量需要重建索引,对于持续增长的 Agent 记忆库来说是个严重限制。它没有元数据存储,只能存向量和整数 ID,需要另外维护 ID 到原始文本的映射。它也没有分布式能力,单机内存限制了规模上限。
# FAISS 基本使用示例
import faiss
import numpy as np
# 创建 L2 距离的平面索引(暴力搜索,适合小数据集)
dimension = 1536 # text-embedding-3-small 的维度
index = faiss.IndexFlatL2(dimension)
# 添加向量(必须是 float32 的 numpy 数组)
vectors = np.random.rand(1000, dimension).astype('float32')
index.add(vectors)
print(f"索引中的向量数量:{index.ntotal}") # 1000
# 搜索最近的 5 个向量
query = np.random.rand(1, dimension).astype('float32')
distances, indices = index.search(query, k=5)
# distances: 距离值,indices: 对应的向量 ID
# 持久化(需要手动处理)
faiss.write_index(index, "memory.faiss")
index = faiss.read_index("memory.faiss")
# 不支持按 ID 删除,更新需要重建整个索引
# 这是 FAISS 用于生产 Agent 记忆的主要瓶颈
FAISS 适合的场景:单机研究原型、数据集相对静态(不需要频繁删除更新)、对检索延迟有极端要求、团队有 C++/CUDA 能力可以深度定制。
Milvus:生产级向量数据库
Milvus 是专为生产环境设计的分布式向量数据库,由 Zilliz 主导开源。它把向量检索和传统数据库的工程能力结合:持久化存储、完整 CRUD、元数据过滤、水平扩展、高可用部署。
Milvus 的架构设计考虑了向量数据库的特殊需求。它把索引分为三层:新写入的数据先进入内存的 Growing Segment(支持实时查询),定期 Flush 为不可变的 Sealed Segment(转为高效只读索引),旧数据 Compact 后移到 Historical Storage。这个分层让 Milvus 同时支持实时写入和高效历史查询,不会因为频繁写入而使检索性能下降。
# Milvus 使用示例
from pymilvus import connections, Collection, CollectionSchema, FieldSchema, DataType, utility
# 连接 Milvus
connections.connect("default", host="localhost", port="19530")
# 定义 Schema
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="user_id", dtype=DataType.VARCHAR, max_length=64),
FieldSchema(name="memory_type", dtype=DataType.VARCHAR, max_length=32),
FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=4096),
FieldSchema(name="created_at", dtype=DataType.INT64), # Unix 时间戳
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1536),
]
schema = CollectionSchema(fields, "Agent 记忆库")
# 创建集合
if not utility.has_collection("agent_memory"):
collection = Collection("agent_memory", schema)
# 创建 HNSW 索引:M=16(每节点最大邻居数),efConstruction=200(构建时搜索深度)
index_params = {
"metric_type": "COSINE",
"index_type": "HNSW",
"params": {"M": 16, "efConstruction": 200}
}
collection.create_index("embedding", index_params)
else:
collection = Collection("agent_memory")
collection.load()
# 插入记忆
def add_memory(user_id: str, memory_type: str, content: str, embedding: list):
import time
data = {
"user_id": [user_id],
"memory_type": [memory_type],
"content": [content],
"created_at": [int(time.time())],
"embedding": [embedding],
}
collection.insert(data)
collection.flush()
# 检索:向量相似度 + 元数据过滤
def search_memory(user_id: str, query_embedding: list, memory_type: str = None, top_k: int = 5):
expr = f'user_id == "{user_id}"'
if memory_type:
expr += f' && memory_type == "{memory_type}"'
results = collection.search(
data=[query_embedding],
anns_field="embedding",
param={"metric_type": "COSINE", "params": {"ef": 64}},
limit=top_k,
expr=expr,
output_fields=["content", "memory_type", "created_at"]
)
return results[0]
关键对比维度
索引类型上,FAISS 和 Milvus 都支持 IVF(倒排文件)和 HNSW(分层导航小世界图)。IVF 把向量空间分为 N 个聚簇,查询时只搜索最近的 M 个聚簇,适合超大规模数据集(亿级别)。HNSW 构建多层跳表结构,查询复杂度 O(log N),在中等规模(千万级别)下召回率和速度都优于 IVF,是生产 Agent 记忆的主流选择。Milvus 还支持 DiskANN,可以把索引存在 SSD 上,支持超出内存容量的超大规模数据集。
查询延迟方面,FAISS 本地调用无网络开销,延迟可低至 1ms 以下。Milvus 通过 gRPC 通信,p99 延迟通常在 10-50ms 范围,取决于负载和索引参数。对于 Agent 的记忆召回场景,50ms 的延迟通常是可接受的,不会成为体验瓶颈。
扩展性上,FAISS 是单机库,单节点 RAM 决定上限。Milvus 2.x 支持水平扩展,通过 MinIO(对象存储)和 etcd(元数据)实现存算分离,可以扩展到数百亿向量。
部署成本方面,FAISS 零部署成本,pip install 即用。Milvus 完整生产部署需要 Milvus + MinIO + etcd,最简 Docker Compose 配置也需要数 GB 内存。对于个人项目或小团队,Milvus Lite(嵌入式模式,不需要外部服务)是一个折中选项。
其他选项的定位
Qdrant 是 Rust 实现的向量数据库,API 设计比 Milvus 更简洁,过滤能力特别强(支持嵌套过滤、地理位置过滤等)。MemOS 在其混合检索管道中使用 Qdrant,评价是过滤灵活性是主要优势。Qdrant Cloud 提供免费层,适合快速验证。
Chroma 是 Python 原生向量数据库,零配置启动,API 极简。适合快速原型和本地开发,但生产规模扩展能力有限。langchain 和 llamaindex 都内置了 Chroma 集成。
Weaviate 是图语义增强的向量数据库,除了向量检索还内置了 BM25 全文检索和 GraphQL 查询接口,适合需要混合检索且不想另外部署 Elasticsearch 的场景。
pgvector 是 PostgreSQL 的向量扩展,允许在现有 Postgres 数据库里存储和检索向量。如果团队已有 Postgres 基础设施,pgvector 让向量记忆的引入几乎零额外部署成本。GBrain 代理记忆系统就是 Postgres + pgvector 的技术路线。
六、记忆召回:如何让 Agent 找到正确记忆
纯向量召回的局限
向量召回的前提假设是:语义相似的文本包含相关的记忆。这个假设在很多情况下成立,但在几类典型场景下会失败。
精确匹配上,用户名、产品型号、版本号这类信息在向量空间里可能没有语义相似性。”用户使用 RTK-3000 型号的传感器”和”RTK-3000 的配置问题”在向量上未必近,但精确关键词匹配可以立即找到。纯向量召回在这类查询上表现差,因为它根本不理解”RTK-3000 是一个专有名词”。
时间性查询是另一个盲区:”上周的会议记录”不能通过语义向量找到,所有会议记录的语义可能都相似,但用户明确要的是特定时间段的内容。向量没有时间维度,时间过滤必须通过元数据实现。
逻辑否定也会让向量召回失效。用户问”有没有记录过用户投诉 A 功能的情况”,向量相似度会找到所有关于 A 功能的记录,不管是投诉还是表扬,因为向量不理解”否定”。
这些场景揭示了向量召回的本质局限:它找的是语义相邻,不是逻辑相关。
混合召回策略
成熟的 Agent 记忆系统都使用混合召回,而不是纯向量。混合的核心是把向量检索(密集检索)和关键词检索(稀疏检索)结合。
BM25 是稀疏检索的标准算法,基于词频(TF)和逆文档频率(IDF)的统计模型。BM25 对精确关键词匹配效果极好,但不理解语义,”手机”和”移动设备”对 BM25 来说是完全不同的词。向量检索正好相反:语义理解强,精确匹配弱。两者互补。
RRF(Reciprocal Rank Fusion)是融合多路检索结果的标准方法:把每路检索的结果按排名加权求和,最终排名最高的记录同时在多路检索中都靠前。这个方法不需要对不同路径的分数做归一化,实现简单,效果稳定。
# 混合召回:向量 + BM25 + RRF 融合
from rank_bm25 import BM25Okapi
import numpy as np
from typing import List, Dict
class HybridMemorySearch:
def __init__(self, memories: List[Dict]):
"""
memories: [{"id": ..., "content": ..., "embedding": ...}]
"""
self.memories = memories
# 构建 BM25 索引
tokenized = [m["content"].split() for m in memories]
self.bm25 = BM25Okapi(tokenized)
def search(self, query: str, query_embedding: List[float], top_k: int = 10) -> List[Dict]:
"""混合检索,返回融合排名后的 top_k 结果"""
n = len(self.memories)
# BM25 检索
bm25_scores = self.bm25.get_scores(query.split())
bm25_ranked = np.argsort(bm25_scores)[::-1]
# 向量检索
query_vec = np.array(query_embedding)
similarities = []
for mem in self.memories:
mem_vec = np.array(mem["embedding"])
sim = np.dot(query_vec, mem_vec) / (np.linalg.norm(query_vec) * np.linalg.norm(mem_vec))
similarities.append(sim)
vector_ranked = np.argsort(similarities)[::-1]
# RRF 融合:rank_bm25[i] = 1/(k + rank_bm25[i])
k = 60 # RRF 的平滑参数,通常取 60
rrf_scores = np.zeros(n)
for rank, idx in enumerate(bm25_ranked):
rrf_scores[idx] += 1.0 / (k + rank + 1)
for rank, idx in enumerate(vector_ranked):
rrf_scores[idx] += 1.0 / (k + rank + 1)
# 取 top_k
top_indices = np.argsort(rrf_scores)[::-1][:top_k]
return [self.memories[i] for i in top_indices]
qmd(Shopify 创始人 Tobi 的本地 Agent 记忆工具)的混合检索实现与此类似:BM25 + 向量 + LLM 重排序三阶段。LLM 重排序是第三阶段:先用混合检索拿到候选集(通常 20-50 条),再让 LLM 根据实际问题对候选集重新排序,过滤出真正相关的结果。LLM 重排序成本较高(每次召回都需要一次额外的 LLM 调用),但在关键查询上显著提升精度。
记忆排序:时间衰减与重要性权重
从记忆库里检索到候选结果后,还需要一个排序阶段来决定最终注入上下文的是哪些记忆。纯相似度排序存在一个问题:一条三年前的旧记忆可能在向量空间里比一条昨天的新记忆更接近查询,但对当前决策来说,新记忆可能更相关。
时间衰减函数给记忆加上时间权重。常见的设计是指数衰减:score = similarity * exp(-λ * days_old),其中 λ 控制衰减速率。λ 取 0.01 意味着 100 天前的记忆权重衰减到约 37%,λ 取 0.001 则几乎没有衰减。具体参数取决于应用场景:个人助理的用户偏好记忆衰减应该慢,新闻摘要的记忆衰减应该快。
访问频率权重是另一个有效维度。一条记忆被检索到并使用的次数越多,说明它在历史上经常被需要,未来被需要的概率也更高。这类似于 CPU 缓存的 LFU 策略,把”热点记忆”保持在高优先级。
MemOS 的 MemCube 热度计算综合了访问频率和上下文相关性:hotness = α * access_freq + β * context_relevance。其中 context_relevance 是当前 Agent 状态与该记忆的动态相关性,每次 Agent 状态变化时重新计算。
记忆冲突处理
记忆库里可能存在矛盾的记录。用户在四月说”我不用 Windows”,在六月换了新工作用上了 Windows 并告诉 Agent。现在有两条冲突的记忆,Agent 应该相信哪条?
最简单的策略是时间戳优先:新记录覆盖旧记录。这在大多数偏好类记忆上有效,但不总是正确的,有些状态是并行的,不是替代关系(用户同时拥有 Mac 个人机和 Windows 工作机)。
AtomicFact 机制(来自 MemCell)提供了更精确的冲突检测。每个事实是一条离散的可验证陈述句,新事实写入时先检查是否与现有事实逻辑冲突。如果冲突,不是直接覆盖,而是标记两条记录的冲突关系,保留两条记录,并记录发现冲突的时间。检索时把冲突标记一起返回给 Agent,让 Agent 根据当前上下文自己判断应该相信哪条。
这个设计把冲突解决推迟到推理时,而不是在写入时强制决策。这更接近人类处理矛盾信息的方式:保留所有证据,在具体场景下判断。
七、记忆操作系统:更高层的抽象
MemOS 的核心思想
把记忆做成操作系统,是 MemOS 最核心的比喻,也是它区别于其他 Agent 记忆方案的根本出发点。
传统 Agent 记忆方案把记忆当作”数据”来处理:往向量库里写,从向量库里读。MemOS 的论断是:记忆应该被当作”资源”来管理,可调度、有生命周期、需要权限控制,就像 OS 管理内存和进程一样。
这个比喻带来了一系列具体的设计决策。记忆有五态生命周期(Generated/Activated/Merged/Archived/Expired),OS 调度器负责记忆的形态转换,热点记忆升级为 KV-Cache 激活态以加速访问,冷记忆降级为归档态以节省存储,过期记忆自动清理。这些都是 OS 的资源管理逻辑,只是应用对象从进程和页面变成了记忆单元。
MemOS 把记忆分为三种形态:明文(Plaintext)、激活态(KV-Cache)和参数态(Parameters)。明文是最基础的形式,读写灵活但推理时需要被放入上下文。激活态是 KV-Cache 注入,直接绕过 prefill 阶段,TTFT 可以降低 91.4%(论文数据)。参数态是把记忆通过 LoRA 微调内化到模型权重里,模型”天然知道”这些信息,不需要任何检索。形态越高级,访问越快,但修改和删除越困难。
MemCube 三元结构
MemCube 是 MemOS 的最小调度单元,包含两部分:Memory Payload 和 Metadata Header。
Memory Payload 是记忆的语义内容,可以是三种形态之一(明文/激活态/参数增量)。Metadata Header 包含三类元数据:描述符(时间戳、来源签名、语义类型)、治理元数据(访问权限、版本跟踪)和行为元数据(访问频率、过期时间)。
把元数据内嵌到记忆单元本身(而不是存在外部表里),是 MemCube 和传统向量存储的一个关键区别。传统向量数据库通常把向量和元数据分开存储,过滤时做表连接。MemCube 的设计让每个记忆单元在传递和调度时携带完整的元数据,不需要额外查表,调度决策可以完全基于单个 MemCube 的信息做出。
这种设计在多智能体场景下特别有价值。当 MemCube 从一个 Agent 传递给另一个 Agent 时,权限信息、版本信息和访问历史都随之传递,不需要通过中央权限服务做额外验证。
分层符号化记忆的协同
MemOS 的分层(L0 原始 → L1 结构化 → L2 参数化 → L3 世界模型)与 TencentDB Agent Memory 的分层符号化记忆思路高度互补,但解决的是不同层面的问题。
MemOS 的分层描述的是记忆的存储形态和成熟度:原始数据经过处理,逐步结构化、参数化,最终内化为世界模型。这是记忆的演化路径,每一层代表更高程度的知识内化。
分层符号化记忆描述的是记忆的访问层级和信息密度:底层保留原始证据(完整可追溯),高层保留高密度结构(Mermaid 画布、Persona 摘要)。日常访问走高层(快、省 token),遇到细节问题沿层级下钻(准确、有据)。
两者结合起来,给出了一个完整的记忆架构视图:纵轴是成熟度(L0 到 L3 的演化),横轴是访问层级(底层原文到高层符号)。不同的记忆操作发生在不同的矩阵位置:新写入的原始对话在 L0 底层,经过多次访问提炼后升级为 L1 结构化事实,高频访问的知识进一步内化为 L2 参数增量,日常快速访问走高层 Persona 和 Mermaid 画布。
记忆 OS 与 Agent Runtime 的接口
记忆系统作为独立组件,需要定义清晰的接口来和 Agent Runtime 交互。接口设计的合理性直接影响整个系统的可维护性。
一个最小化的记忆 OS 接口包含五个基本操作:
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class Memory:
"""记忆单元"""
id: str
content: str
memory_type: str # episodic / semantic / procedural
user_id: str
created_at: int
accessed_at: int
access_count: int
importance: float # 0.0 - 1.0
expires_at: Optional[int] = None # None 表示永不过期
tags: List[str] = None
class MemoryOS(ABC):
"""记忆操作系统接口"""
@abstractmethod
def remember(self, user_id: str, content: str, memory_type: str,
importance: float = 0.5, expires_at: int = None) -> Memory:
"""写入新记忆"""
pass
@abstractmethod
def recall(self, user_id: str, query: str, top_k: int = 5,
memory_type: str = None) -> List[Memory]:
"""检索相关记忆,返回排好序的结果"""
pass
@abstractmethod
def forget(self, memory_id: str) -> bool:
"""主动删除记忆(用于用户请求删除、隐私合规等场景)"""
pass
@abstractmethod
def consolidate(self, user_id: str) -> int:
"""触发记忆巩固:合并相似记忆、更新重要性评分、清理过期记忆"""
pass
@abstractmethod
def get_context(self, user_id: str, task_description: str) -> str:
"""为 Agent 组装最优上下文:检索 + 排序 + 格式化,直接注入提示词"""
pass
get_context 是这个接口里最重要的方法,也是记忆 OS 对 Agent Runtime 暴露的最高层抽象。Agent 不需要理解记忆如何存储、如何检索、如何排序,只需要调用 get_context(user_id, task_description),得到一段可以直接注入提示词的文本。记忆 OS 负责处理所有复杂性:混合召回、时间衰减、冲突标记、格式化,这些实现细节对 Agent 完全透明。
这个接口设计遵循的原则是:把记忆操作系统的复杂度封装在组件内部,对外暴露高层次、语义清晰的接口。Agent Runtime 不应该关心向量数据库的索引类型,就像应用程序不关心操作系统的页面置换算法一样。
多智能体记忆共享
当多个 Agent 协同工作时,记忆共享变得复杂。不同 Agent 执行不同任务,有些记忆应该被所有 Agent 共享(用户基本偏好),有些记忆应该是 Agent 私有的(执行中的中间状态),有些记忆在 Agent 之间有读写权限差异。
MemOS 提出了 Hub-Client 架构:一个中央 Memory Hub 持有共享记忆,每个 Agent 作为 Client 连接 Hub,有自己的私有记忆空间,同时可以读写 Hub 里具有相应权限的共享记忆。这类似于 Git 的中央仓库 + 本地仓库架构:本地自由开发,推送时经过权限验证,关键共享状态由 Hub 统一管理。
权限粒度是设计难点。过粗的权限(要么全共享要么全私有)无法满足细粒度的业务需求。MemOS 的 MemCube 元数据里包含 access_permission 字段,支持按记忆类型、按来源 Agent、按时间范围设置读写权限。但这也带来了维护成本:权限矩阵越复杂,出错的可能性越大,调试越困难。实际工程中,建议从最简单的权限模型开始(用户级别的共享 vs 私有),只在明确有需求时才细化权限粒度。
论文自述的局限里,多用户并发写入冲突融合和多智能体大规模并发机制不足是两项明确承认的未解问题。这两个问题本质上都是分布式系统里的经典难题,只是把操作对象从数据库行换成了记忆单元。
八、工程实践:选什么、避什么
从小开始,不要过早抽象
实际工程里的记忆系统,应该跟着需求复杂度同步演进,而不是一上来就部署 MemOS 全家桶。一个可操作的演进路径:
阶段 1(单会话,对话历史 < 100 轮):把完整历史传给模型,不做任何记忆管理。这是大多数 chatbot 应用的合理起点,在这个阶段的用户规模下,管理复杂度的收益远不如直接实现功能。
阶段 2(跨会话,需要记住用户偏好):维护一个 memory.md 文件,每次会话结束时让 LLM 更新文件里的用户信息。文件注入系统提示。qmd 就是这个阶段的最佳工具:10 分钟配置,混合检索,零服务器成本,比手动维护 Markdown 文件更可靠。
阶段 3(高并发,多用户,记忆条目 > 万级):引入向量数据库。Qdrant Cloud 免费层是零部署成本的起点。设计 recall() 和 remember() 两个核心接口,隐藏存储细节。
阶段 4(生产级,需要分层和调度):根据数据量和延迟要求,在 Milvus 和 Qdrant 之间选型。引入 BM25 + 向量的混合召回。为语义记忆、情节记忆、程序记忆设计不同的存储和检索策略。
阶段 5(多智能体,企业级合规):考虑 MemOS 等记忆 OS 框架,或在阶段 4 的基础上自建调度和权限层。
工程实测最重要的发现
Kevin 对 MemOS 的工程实测得出了一个反直觉的结论:系统中最能提升性能的不是复杂的调度算法,而是事实提取的质量和时间格式的一致性。把 F1 从 0.25 提升到 0.56,靠的是把原始对话精确地提取为离散可验证的事实,以及让所有时间记录使用一致的格式。
这个发现指向了记忆系统瓶颈的具体位置。写入质量(记忆如何被提取和存储)才是瓶颈,而不是检索算法(如何被找回来)。垃圾进去,任何检索算法都搜不出高质量的结果。在投入工程资源优化检索之前,先保证写入质量:准确的实体提取、一致的时间格式、正确的重要性标注。
另一个反直觉的发现:对于大多数 Agent 场景,简单的 BM25 + 向量混合检索在准确率上和复杂的 LLM 重排序相差不大,但延迟和成本相差悬殊。LLM 重排序的价值集中在高模糊性查询(问题措辞和记忆措辞差异大)和高安全要求场景(不能有任何无关记忆进入上下文),不是所有召回都需要 LLM 重排序。
避免的常见陷阱
永久化临时状态是最常见的坑:不设有效期地存储所有事实,导致过时信息长期污染记忆库。Foresight 机制或者简单的时间戳 + 自动过期规则都可以缓解这个问题。
黑盒记忆是另一个工程隐患:纯向量存储没有可读的高层符号,调试困难,出问题时无从追溯。保持记忆内容的白盒可读性是可维护性的基本要求,高层摘要必须保留底层指针,任何压缩都要可逆。
一致性问题往往被忽视:多个 Agent 并发写入同一用户的记忆库,没有冲突检测机制。即使是简单的乐观锁(写入前检查最后修改时间)也好过什么都没有。
冷启动必须主动设计。新用户没有任何历史记忆时,recall() 返回空,Agent 行为退化。需要为冷启动状态设计专门的提示词和回退策略,而不是让 Agent 带着空上下文盲目推理。
九、从稀疏注意力看记忆的未来
模型层的另一条路
前面讨论的记忆方案(上下文管理、向量库、记忆 OS)都在模型外部工作,通过检索把记忆内容注入上下文。还有一条平行的技术路线:直接在模型架构层面解决长记忆问题,不依赖外部检索。
Memory Sparse Attention(MSA)是这个方向的代表性工作,来自 EverMind-AI 的 arXiv:2603.23516。它把”哪些 token 参与注意力”做成端到端可微的稀疏选择,把注意力复杂度从 O(L²) 降到 O(L),从根本上解耦了记忆容量和推理能力的矛盾。
标准 Transformer 的注意力是稠密的:每个 token 对所有其他 token 计算注意力权重。这意味着 100 万 token 的序列需要 10¹² 次乘法运算,不仅算力消耗巨大,而且大量无关 token 之间的注意力计算是纯粹的噪音。MSA 的核心洞察是:大多数查询只需要一小部分文档就能回答,没必要让所有文档都参与每次注意力计算。
MSA 的路由机制分两步。首先,每个文档的 K/V 通过均值池化被压缩为一个向量(路由键 K̄ᵣ)。查询时,用查询向量与所有文档的路由键计算余弦相似度,选出最相关的 top-k 文档。然后,只有被选中的文档的完整 K/V 才被加载参与注意力计算。
传统注意力:
Q [L×d] × K [L×d]ᵀ = 注意力矩阵 [L×L] → O(L²) 复杂度
MSA:
Q_r [1×d] × K̄_r [N×d]ᵀ = 路由分数 [1×N],选 top-k 文档
Q [L_q×d] × K_selected [L_k×d]ᵀ = 注意力矩阵 [L_q×L_k] → O(k) 复杂度
(k ≪ N,N 是文档总数)
基准测试数据令人印象深刻:在 RULER NIAH(大海捞针)1M token 测试中,MSA 准确率 94.84%,而原始骨干模型在同样任务上得分 24.69%,骨干完全崩溃,MSA 保持了稳定性。在 9 个 QA 任务的平均评分上,MSA 比同骨干的 RAG 高 16.0%,比 RAG+重排序高 11.5%。更关键的是,从 16K token 扩展到 1 亿 token,MSA 的性能下降不到 9%,这是真正的 O(L) 缩放,不是营销数字。
模型层方案的真实边界
MSA 的数据很好看,但实际应用面临真实的工程约束。
训练成本是最大的门槛。MSA 的训练包含 1589.5 亿 token 的持续预训练(带辅助路由损失)和两阶段课程 SFT(8K→64K),这对大多数团队是不可负担的成本。除非直接使用开源发布的 MSA-4B checkpoint,否则很难受益于这个技术。
另一个问题是部署复杂性。MSA 的 KV-Cache 分层存储设计(路由键在 GPU,内容 K/V 在 CPU)在工程上比标准模型复杂得多,需要定制化的推理框架支持。现有的推理框架(vLLM、TGI)还没有原生支持这种分层内存架构。
更根本的约束是:模型层方案和 Agent 层方案解决的不完全是同一个问题。MSA 很擅长跨长文档多跳问答,在 1 亿 token 里找到几个相关段落并推理。但 Agent 记忆系统的核心挑战不是”在大量文档里找信息”,而是”记住用户个性化状态、跨会话延续任务、管理记忆的生命周期”。这些需要写入、更新、删除、权限控制,而 MSA 的 KV-Cache 是只读的。
三层技术路线(模型层 / 检索层 / Agent 层)互补而不互斥。MSA 把”长上下文有效性”推回模型本身,RAG 在外部检索知识,Agent 记忆层管理个性化状态和任务连续性。成熟的 Agent 系统最终会同时使用三层:好的基础模型(能有效利用长上下文)+ 精准的检索(找到外部知识)+ 专门的记忆系统(维护用户状态)。
记忆压缩的极端形式:蒸馏为技能
有一类记忆压缩方式比摘要更彻底:把记忆蒸馏为可执行的技能。这是 EverOS Agent Skill 机制和分层符号化记忆”技能生成”路线的共同终点。
设想一个 Agent 在三个月内处理了数百次用户的数据可视化请求。每次请求、每次工具调用、每次报错和修复都记录在情节记忆里。这些记忆如果只是平铺在向量库里,会随时间积累成一个巨大的噪音堆,其中 90% 的细节对未来任务没有价值,有价值的是处理这类任务的通用模式。
技能蒸馏的目标就是提取这个通用模式。Agent 定期对同类型任务的执行轨迹做聚类,识别成功路径中的共性步骤,生成一段结构化的 Skill 文档:任务类型、前置条件、执行步骤、常见错误和修复方法。成熟度评分(EverOS 里 maturity_score ≥ 0.7 才注入提示词)保证只有经过充分验证的技能才会影响 Agent 行为。
这个机制意义重大:程序记忆从庞大的情节记忆中浮现,精确地捕捉了 Agent 在特定领域的能力。技能文档比原始轨迹小几个数量级,但保留了几乎所有的行为价值。这是记忆压缩的最高层次:不是摘要(保留信息),而是蒸馏(保留能力)。
它也说明了为什么记忆系统不只是一个存储问题,而是一个持续学习问题。能记住的 Agent 和能从记忆中学习的 Agent 之间,有一个质的差距。
十、实际系统设计:一个完整例子
场景描述
设计一个面向企业用户的技术支持 Agent,需要满足以下要求:能记住每个用户的产品版本和历史问题,能引用过去成功的解决方案,能识别出同一用户多次遇到同一问题(可能是产品 bug),支持数百名用户并发使用,所有记忆对用户可见可删除(GDPR 合规)。
记忆分层设计
这个场景需要三种类型的记忆并行运作。
用户语义记忆(稳定特质)存储用户的产品版本、操作系统类型、技术背景水平、沟通偏好(喜欢详细步骤还是简洁命令)。这类记忆变化慢,应该存在高层 Persona 文件里,每次会话都注入系统提示,不需要每次检索。
情节记忆(历史问题记录)记录每次技术支持会话的摘要,包含问题描述、解决步骤、最终结果。存入向量库,按用户 ID 过滤。每次新会话开始时,检索最近几条相关记录注入上下文,让 Agent 知道”这位用户上次遇到的问题和解决方案”。
语义记忆(知识库)是所有成功解决方案的结构化记录,按问题类型分类,不是用户专属的,而是跨用户共享的知识积累。当一种解决方法在多个用户身上验证有效,它的权重会上升,优先推荐给新遇到同类问题的用户。
技术栈选择
# 技术支持 Agent 记忆系统技术栈
组件:
向量数据库: Qdrant Cloud # 免费层起步,按需扩容
BM25索引: SQLite + FTS5 # 本地全文索引,零依赖
嵌入模型: text-embedding-3-small # 成本可控
用户Persona存储: PostgreSQL # 结构化用户画像
会话快照: PostgreSQL JSONB # 会话摘要,元数据丰富
数据流:
会话结束 → 实体提取 → [Qdrant向量 + SQLite FTS5]
会话结束 → Persona更新 → PostgreSQL用户画像
会话结束 → 成功方案识别 → 知识库权重更新
检索策略:
查询 → BM25召回(50条) + 向量召回(50条) → RRF融合 → top_15 → 注入上下文
权限:
用户只能读写自己的情节记忆
用户不能修改共享知识库(只能提交反馈)
用户可以删除自己的任意记忆(GDPR right to erasure)
冷启动处理
新用户第一次使用时没有任何历史记忆,recall() 返回空列表。这时 Agent 不能沉默,也不能随意假设用户背景,而应该把”没有记录”这个事实明确地体现在提示词里,引导 Agent 主动询问必要信息并立即写入 Persona:
def build_context_for_new_user(user_id: str, query: str) -> str:
"""新用户冷启动上下文"""
return f"""你是一个技术支持助手。
用户信息:这是该用户的第一次对话,目前没有历史记录。
操作指引:
1. 解决完问题后,询问用户确认使用的产品版本和操作系统
2. 如果用户透露了技术背景信息,在回复末尾告知用户你已记录
用户问题:{query}"""
第一次会话结束后,写入用户的基本 Persona,后续会话立即有了记忆基础。冷启动问题不是技术问题,是设计问题:设计好第一次会话的引导流程,比任何检索优化都更重要。
可观测性
记忆系统的最大工程风险是无声失败:检索到了错误的记忆,但 Agent 照常使用,生成了看起来合理但实际错误的回复,没有任何错误信息。这类问题在日志里不可见,只能通过用户反馈发现,修复成本极高。
最小可观测性配置:每次 recall() 调用都记录查询内容、返回的记忆 ID 列表和相似度分数,每次 remember() 都记录写入的内容和时间。这份日志让调试时能追溯”那次错误回复用的是哪条记忆,那条记忆是什么时候、怎么写进去的”。
更完整的可观测性还包括:每周对随机抽样的召回结果做人工评估(召回精度),定期检查记忆库里的冲突记录(写入质量),监控记忆条目增长率和过期淘汰率(记忆健康)。没有度量就没有改进,记忆系统也不例外。
行动建议
如果你的 Agent 目前没有任何记忆机制,最快的改进路径是:花一小时用 qmd 配置本地混合检索记忆,把所有会话笔记和用户信息纳入索引,通过 MCP 让 Agent 在回答前先搜索相关上下文。这一步投入小,但能立即把 Agent 的”遗忘”问题从完全失忆改善为有选择性的遗忘。
如果你已经有基础记忆,下一步瓶颈几乎必然是写入质量:设计精确的实体提取提示词,统一时间格式,给重要偏好和约束条件加上显式标注,给临时状态加上有效期。先跑一次端到端的 F1 评估,确认基线,再做其他优化。
记忆系统不是一次性工程,是随 Agent 使用深度持续演化的基础设施。好的起点是让它可观测、可调试、可迭代,而不是一开始就完整。
评估记忆质量的最简单方法:构造 10 个需要跨会话信息才能正确回答的问题,测试 Agent 的召回率。如果得分低于 70%,说明 Embedding 模型或分块策略需要优化;如果得分超过 70% 但用户仍然觉得 Agent”健忘”,问题大概率出在写入环节,要么信息根本没被记录,要么记录的粒度太粗无法精确召回。两种失败模式的修复路径完全不同,所以先测量,再改造。

IT资源栈
评论前必须登录!
立即登录 注册