我们在 Elasticsearch 上构建了一个持久化代理内存层,其召回率为0.89
Elastic 把这套代理记忆架构连同评估数据一次性放出来,三种记忆类型、混合召回、衰减和隔离全挤在一个查询里,做 Agent 持久记忆的开发者可以直接抄,召回 0.89 的工程决策讲得清楚。
Agent Builder 正式上市(GA)。基于 Elasticsearch 的持久化内存层将记忆分为情景、语义、程序三类,分别存入独立索引,各设不同写速率与过期规则。召回采用 BM25 与 Jina v5 稠密向量的 RRF 融合,再经交叉编码器重排序。在 168 道 QA 题评估中,R@10 平均 0.89,零跨租户泄漏。该层可通过支持 MCP 协议的客户端访问,不绑定特定运行时,已开源至 GitHub。
Agent Builder 现已正式发布(GA)。可免费试用 Elastic Cloud 快速上手,并在此处查看 Agent Builder 的文档。
在 Elasticsearch 上构建智能体记忆
三个索引、结合重排序器的混合召回、替代机制、衰减机制与 DLS。支撑智能体持久化记忆层的架构设计与数字实践。

莎拉的智能灯泡只能显示白光。她的智能家居助手建议重置中枢网关。她三月重置过一次,上周又重置了一次;两次重置都没修好。智能体不知道这些情况,也不知道那条狗咬断了她传感器线缆。那些重要的历史记录——什么有效、什么无效、莎拉是谁——随着每次对话结束就消失了。
常规的变通方法是将先前的上下文塞进上下文窗口。但这会在成本、延迟以及有据可查的“中间迷失”效应上出问题——模型会忽略离提示词两端较远的事实。100 万 token 的上下文窗口只是一块草稿纸。它不是记忆系统。
上下文窗口是短期记忆:单次推理时活跃的思考空间。真正缺失的是长期记忆:一个持久化的存储,能跨越对话会话存活,可扩展至多年的交互,并允许你按内容、按时间、按用户检索事实。
本文介绍一个真实智能体记忆系统的架构,该系统基于 Elasticsearch 构建,围绕认知科学中的三个类别进行组织,采用带有 RRF 和交叉编码器重排序器的混合召回查询、用于矛盾信息的替代机制,以及基于用户的 DLS 隔离。在包含 168 道题的问答风格评估中,R@10 平均值为 0.89,且零跨租户泄露。
完整实现代码已在 GitHub 上开源;本文旨在解释为何其结构如此设计。
智能体记忆存储需要具备哪些能力
有用户问"我们上次试了什么修复方法?"——这是个带精确匹配约束的时间查询。或者"为什么我的智能灯泡只显示白色?"——这需要将个人记忆与共享目录融合。记忆本身并非一刀切:用户亲身经历的事件、关于他们的稳定事实、以及逐步的操作手册,都有不同的写入频率和老化规则,因此存储系统必须识别类型并区别对待。而且在任何多用户部署中,每个用户的记忆必须对其他用户完全不可见。新事件积累速度很快,必须整合为持久化类型,否则索引会变成一堆乱麻。当用户与某个已存事实矛盾时,旧版本应被取代而非删除,这样才能保留审计记录。旧事实不应比新事实权重更高,用户频繁接触的事实也不应沉底。整个记忆层应该能被任何支持 MCP 协议的客户端访问,而不绑定到某个特定的智能体运行时。
将这些功能拆散到向量存储、关键词引擎、审计层和独立的身份验证服务中,意味着有四个可能出错的环节,并且每次回忆调用都要多出额外的往返。这些需求描述的本质上就是一个搜索引擎,所以这个实现就用了搜索引擎。本文剩余部分将逐一讲解。
三种智能体记忆类型:情景记忆、语义记忆、程序记忆
第一个设计决策是要存储哪些记忆类别。如果什么都保存,结果就是一堆没有信号的乱麻。认知心理学中情景记忆、语义记忆和程序记忆的划分——经由 COALA 框架引入大语言模型智能体领域——已经给出了恰当的类别,而且它们能干净地映射到三个 Elasticsearch 索引上。
- 情景记忆:带时间戳的事件——每个用户轮次的原样记录,不做任何提取或解读。这类记忆大多生命周期短,不一定值得长期保留。少数条目会成为后续持久事实的证据。
- 语义记忆:提炼过的、关于用户的稳定断言。比如:Sarah 拥有一台 Lumio Hub v2;Sarah 使用的是 iOS 17.4;Sarah 的 hub 在三月重置过。这些断言跨会话存活,也是智能体赖以推理的基础。
- 程序性记忆。多步骤操作手册。如何排查 Zigbee 断开连接的问题。是流程,而不是事实。每个流程都带有 success_count 和 failure_count,当用户确认某个修复方法有效或无效时,通过合并机制递增这些计数。这些计数会作为上下文呈现给合并 LLM,供其考虑是否要优化或替换某个操作手册。
每个类别都有不同的生命周期。情景记忆被持续写入并逐渐衰减。语义记忆经过策展、去重,并随着用户变化而被取代。程序性记忆则累积结果反馈(success_count、failure_count),为合并提供依据。单一存储桶无法建模这种差异。三个索引,每种记忆类型一个,让各自遵循自己的写入速率、老化规则和更新规则,彼此解耦。
与这三类记忆并列的是第四种检索表面:已在 Elasticsearch 中的世界数据(目录、知识库)。从认知角度看,它并非“记忆”,但智能体通过同样的混合检索管道(在下节介绍)读取它,因此应放在同一体系中。
回忆管道:带 RRF 和重排序器的混合检索
记忆的回忆采用两阶段混合搜索:基于 BM25 + Jina v5 稠密向量的 RRF,然后对合并候选项进行交叉编码器重排序。每个文档通过一次写入以两种方式索引:原始文本存入 BM25 倒排索引,同时 copy_to 将同一值路由到自动生成 Jina v5 向量的 semantic_text 字段。对同一内容进行两次索引可保持存储占用持平:一次真实源写入即可产生两个检索分支(索引映射)。每个分支解决不同问题。BM25 锚定文字级 token 匹配——这些匹配在智能体释义中会丢失,例如版本号、错误代码、专有名词如“Lumio Hub v2”。稠密向量捕捉问题的语义形状,即使答案使用不同词汇。单独任何一个分支都会漏掉另一个分支所能处理的案例,而 RRF 融合它们的排序结果,无需校准 BM25 分数与余弦相似度。
过度获取。重排序器只能对其所见的内容进行重新排序,因此候选池必须足够宽。混合检索器每路获取 80 个候选结果,并使用 rank_constant=30(比 ES 默认的 60 更紧,因此排名靠前的项占据更大主导地位)进行 RRF 融合。(_rrf_fetch)
重排序器。一个 Jina v2 交叉编码器将合并后的候选结果与用户查询进行评分。BM25 和双编码器稠密检索都是独立地对查询和文档进行评分,而交叉编码器则是对它们进行联合评分,通过两者之间的完全注意力机制,这是一种更强的相关性信号,但每对的计算成本更高。这正是采用两阶段流程的动机:先用混合检索器低成本地过度获取,然后用成本更高的评分器对一个小型候选池进行重排序。(_rerank)
如上图所示,有一个微妙之处。智能体的工具包中包含 recall_memory(在 tools.py 中定义),模型在单个回合中会调用它。一次调用会同时遍历所有三个记忆索引和目录:智能体不会选择记忆类型,因为检索器的排序和每个索引的衰减机制会替它处理路由。第二个微妙之处在于释义改写。智能体在调用该工具之前几乎总是会重写用户的消息,在 BM25 看到查询之前,就从中剥离掉字面意义上的版本号、错误代码和专有名词。因此,每个回合都以自动预召回原始用户消息开始,并将该消息注入到对话中,就像智能体自己调用了一样(agent.py)。
编写和整合智能体记忆。
两个操作将记忆从“刚刚发生的事情”转移到“关于该用户有哪些持久信息”。
每个用户轮次在LLM回应前,会写一条情景事件(包含ID、确切消息、时间戳等)。ID由Elasticsearch在写入时分配,对Sarah API密钥的DLS查询确保后续每次召回时文档仅限定在她本人,时间戳则供时间衰减函数(如下)读取,以根据新事件对当前事件进行排序。智能体的回复不存储。对话历史已将其带入下一次调用,而它们的长度会淹没用户说的简短且富含事实的内容。热路径写入是一个刻意的选择。乍看之下有两种替代方案似乎可行。让上下文窗口携带新事实对于开放会话的剩余部分有效,但一旦会话结束或崩溃,上下文中的状态就会消失;跨会话记忆才是关键。
在会话结束时批量写入可以保留跨会话状态,但它破坏了本实现依赖的两个同轮模式。用户在一条消息中提到一个新设备并询问其设备列表时,需要新事实对同一轮后续运行的召回可见,因为工具调用查询的是索引,而不是对话历史。而替换流程会在一个工具调用批次内写入修正后的事实并针对它进行召回。这两种模式在延迟写入下都会静默地产生错误行为。我们付出的代价是每个用户消息一次Elasticsearch写入,对于单个对话产生的数据量而言,耗时低于100毫秒。
哪些建议有效是通过程序性索引上的success_count / failure_count分别记录的,而非存储回复文本。近期包含用户确认(“谢谢,搞定了”)的情景事件触发success_count++;明确拒绝(“那不管用”)触发failure_count++。对话本身构成了反馈信号,由整合LLM充当分类器。无需点赞控件。当出现分歧时,还会产生一个refined_steps字段,供LLM写回到剧本中。
整合。片段日志积累得很快。整合将它们提升为语义事实和程序化剧本,这些内容会在对话历史消失后依然留存。该实现每轮对话都会运行一次整合,因此你可以实时观察检查器更新;在生产环境中,合理的节奏是作为后台任务运行:每24小时一次,或者当用户的片段索引超过N个新事件时执行一次。每轮整合会使每条消息的大语言模型调用次数翻倍。
在一次调用(提示词)中,整合大语言模型被输入最近的片段以及现有的事实和剧本,并请求输出三样东西:
- 新的语义事实,附带用于溯源的 support_episode_ids。
- 新的程序化剧本——当某个多步骤解决方案不匹配任何现有触发器时。
- 程序化更新——根据用户是否确认修复方案,相应地增加 success_count++ 或 failure_count++,并在用户不同意时提供 refined_steps。
该提示词要求每个输出都附带 support_episode_ids,因此稀疏轮次会返回空列表且不写入任何内容。
去重使用了与智能体相同的混合检索器进行召回:对于每个候选事实,针对用户的语义索引进行 top-K 混合搜索,缩小比较集合,只有这些候选事实才会被送入大语言模型进行含义判断。还有两道后续守卫对输出进行约束:低于置信度阈值的候选事实被丢弃;对于已接受的事实,如果其最高相似度命中值≥0.90,则被视为重复。在当前实现中,去重更简单:将最近约50个事实传递给整合大语言模型,并附带“不得重复”的指令,而大语言模型后的置信度和相似度守卫尚未接入。混合召回路径和后续守卫是生产架构;这一快照版本依赖大语言模型直接进行比较,因为语料库较小,足以容纳。
success_count 和 failure_count 对剧本形成了反馈闭环:在足够多的对话中,记录“此方法有效”的同一字段,将成为“下次优先展示此方案”的信号。目前,计数已写入,但尚未用于偏差化检索排序。在少数已解决的工单上,这种提升还只是统计噪声。接入生产后,一旦部署数据密度足够大,该信号才能变得有意义。
智能体记忆如何处理矛盾与取代关系
只增不删的记忆终将出错。用户说“我搬到爱丁堡了”,智能体写入一条新事实。六个月后,那条旧的“住在布里斯托尔”的事实仍留在索引中。每次召回时两者都浮现出来,智能体要么选错,要么含糊其辞。信任感很快消失。
解决方法是在系统提示词(完整提示词)中增加一条规则,无需新增工具。智能体采用取代而非删除的方式:

一个具体示例。Sarah 上一次访问时记录了 id=abc,“Sarah 住在布里斯托尔”存储在语义索引中。三个月后,她打开聊天窗口说:“我们离开了布里斯托尔,现在在爱丁堡。”
1. 召回。对 Sarah 的消息进行预召回,返回的命中结果包括 {id: "abc", text: "Sarah 住在布里斯托尔", memory_type: "semantic"}。
2. 检测。智能体发现召回的事实与新消息之间存在冲突。
3. 分类。“我们离开了布里斯托尔,现在在爱丁堡”是一个自然的更新,而非否定。智能体将矛盾类型判定为“自然”。
4. 写入。智能体调用 write_memory(text="Sarah 住在爱丁堡", supersedes_id="abc", contradiction="natural")。两步操作在一次调用中完成:
- 写入一条新文档 id=xyz,置信度为完全可信(无惩罚,因为矛盾是自然发生的)。
- 旧文档 abc 被更新,添加 superseded_by=xyz 和 superseded_at=<now>。
5. 召回时隐藏旧记录。每次召回都应用一个过滤器:must_not exists field=superseded_by。abc 从智能体的视图中隐藏,xyz 正常浮现。
6. 审计保留。文档 abc 仍留在索引中。查询 superseded_by=xyz 即可重建整个链。
注意:如果 Sarah 后来问“我住过哪些地方?”,智能体会调用 recall_memory(query="places sarah has lived", include_superseded=True)。DLS 作用域下的召回会同时返回 xyz(爱丁堡)和 abc(布里斯托尔)。带有 superseded_at 设置的命中结果属于已归档状态;智能体的回答会区分两者:“您现在住在爱丁堡;您之前住在布里斯托尔(直到今年早些时候)。”
如果萨拉当时说的是“我从来没住在布里斯托尔,那是我姐姐”,那么步骤 3 会将其归类为尖锐矛盾。同样的写入操作会发生,但新事实的置信度会减去 SUPERSEDE_CONFIDENCE_PENALTY。系统会略微谨慎,直到新状态通过后续对话得到强化。
边缘情况遵循相同的模式:已经被取代的事实可以再次被取代(abc → xyz → pqr);一个低风险的偏好(“我现在更喜欢深色模式”)以矛盾类型="自然"被取代。forget_memory 是硬删除;仅在客户明确说“忘记 X”时使用。它不是矛盾处理工具。
一个微妙之处。召回可能会弹出多个被同一新陈述矛盾的事实。萨拉的位置存在于“萨拉住在一栋布里斯托尔的维多利亚式公寓里”(语义)、存在于“萨拉在布里斯托尔有一间公寓,里面放着她的 Hub v2”(语义),可能还存在于之前某次对话中的一个情景事件中。智能体必须取代所有这些事实,而不仅仅是它看到的第一个。扫描召回中每一个被新陈述变为假的事实,并为每个旧 ID 调用一次 write_memory(supersedes_id=…)。那些仅提及布里斯托尔但依然为真的事实(“布里斯托尔的维多利亚式公寓有厚墙,会减弱 Zigbee 信号”)则不被取代。萨拉搬家并不会改变布里斯托尔的建筑结构。
被取代的文档会累积,但仅在智能体通过 include_superseded=True 明确请求时才会出现在召回中。在生产环境中,定期的重新索引会将这些文档移到一个单独的归档索引中,由 Elasticsearch 的索引生命周期管理(ILM)将其经过冷层和冻层(可搜索快照)老化。审计链在冷存储上保持可查询;活跃的语义索引保持热且小。
确保同一次轮次写入在 Elasticsearch 智能体记忆中的可见性。
Elasticsearch 默认的异步刷新间隔会在智能体于同一轮对话中写入并召回记忆时产生传播间隙。当用户在一条消息中说“我有一个从未设置过的 Lumio 信号扩展器。现在我完整的设备列表是什么?”时,智能体写入该 Range Extender 事实,然后立即执行召回——在同一轮对话内,有时甚至发生在同一迭代的工具调用批次中。默认的 Elasticsearch 刷新间隔加上 semantic_text 推理开销,可能引发亚秒级的传播间隙,导致刚写入的文档对召回请求尚不可见。
修复方案位于存储层。智能体触发的每次 write_memory 都传入 refresh=True,强制分片在调用返回前刷新(并使内联推理处理器生成的 Jina v5 embedding 落地)。后续的工具调用能看到新文档。Range Extender 出现在最终回复中,正是因为写入后立即执行的召回看到了它。
在写入量较大时,refresh=True 会成为吞吐量成本。生产环境部署可能希望转向异步索引,并配合智能体层的“刚写入”寄存器——将写入内容保留在 LLM 的上下文中,直到索引追赶上进度。目前,更简单的选择仍有用武之地。
用于智能体记忆检索的时间衰减和使用次数评分
迄今为止的检索设置对所有事实一视同仁,不论其创建时间或最后使用时间。这是错误的默认值。过去一周内被召回两次的事实,几乎肯定比两年前被提及一次的事实更具相关性。
我们将每个结果的分数乘以两个乘数:一个主要的新近度信号和一个次要的频率修正因子。新近度信号采用时间衰减:一个通过 Painless 脚本在每个索引的日期字段上计算的高斯形乘数(详见下文)。频率修正因子是一个使用次数提升(1 + log10(1 + use_count) * weight),因此被召回十次的事实约提升 1.2 倍,被召回一百次的事实约提升 1.4 倍。
两者回答的是不同问题:时间衰减反映事实最近被触碰的时间;使用计数反映事实被触碰的频率。当多个事实共享同一个 last_used_at 时间戳时,两者便会分道扬镳:衰减无法区分“被回忆过一次”和“被回忆过四十次”,但使用计数可以。时间衰减是主力机制,使用计数则是精化手段——只有当每个事实的回忆量足够高、能承载信号时,它才有用武之地。
每种记忆类型对应的日期字段
情景记忆和语义记忆的衰减机制使用不同的日期字段。情景记忆使用时间戳(事件发生时间);语义记忆使用 last_used_at(写入时设置,回忆时更新)。ES 原生的高斯函数无法同时覆盖两者,因为该函数只接受一个字段名,而这个字段必须存在于搜索涉及的每一个索引上。因此,时间衰减被放在一个 Painless 脚本中实现,该脚本会根据索引选择正确的字段,并在原地计算高斯形状的乘数:
程序记忆被有意排除在时间衰减之外。last_used_at 会在每次回忆时更新(无论成功与否),因此纯衰减乘数会奖励“最近尝试过”而非“最近有效过”。正确的配对方式是使用 last_success_at 字段,再加上 success_count / failure_count 影响排序;在这两者都到位之前,仅凭时效性对于程序记忆的检索来说过于粗糙。
语义记忆中的“回忆时更新”是核心承载机制。它将“旧事实权重更低”转变为“智能体近期未用过的事实权重更低”。这是相关性衰减,而非真实性衰减。真实性衰减由替换机制(上文所述)处理。一个五年之久的事实,只要智能体每周都回忆它,它就会保持在最顶部,因为 last_used_at 是新鲜的。
这与三桶分类法背后的认知科学脉络相同。检索练习(即回忆某个事实的行为)会增强其可访问性,而长期不用则使其消退。在 last_used_at 上做“回忆时更新”正是同一种效果在工程层面的体现。
检索时的乘数
两个因子共同存在于一个 function_score 块中,该块包裹着每个 RRF 分支:
在代码中,两个函数共同存在于一个 Painless 脚本中,该脚本按索引进行分支(数学运算相同,但 function_score 条目更少)。
两个 _index 过滤器承担了双重职责。它们将每个函数限定到其应影响的内存类型:时间衰减适用于情景记忆和语义记忆,使用次数提升仅适用于语义记忆。同时它们将程序性记忆和目录记忆排除在外:如果函数的过滤器不匹配,则返回中性值 1.0,这样包含这些索引的跨索引查询就能正确评分,无需解析器处理。完整函数位于 operations.py 中。
有两个参数控制高斯曲线:

- offset(180天):平坦区。不足 180 天的文档无论确切年龄如何,都获得乘数 1.0。没有这个平坦区,新事实会因亚日时间噪音而相互竞争。
- scale(1825天,约5年):超出平坦区后,乘数衰减至 0.5 的距离。实际上是平坦区结束后的半衰期。
衰减是一种刻意的权衡。当语料库中的每个事实都是唯一的且随时间保持正确时,应用任何衰减都会牺牲一些召回率:即使旧事实仍然正确,它们也会受到惩罚。衰减发挥作用的地方在于现实情况:关于同一事物存在多个竞争事实,你希望排名最高的是最新或最常用的那个。出于这个原因,默认的 scale(1825天)是保守的。对于事实快速过时的领域(如客户支持中产品快速更迭),可以收紧它。对于个人助手记忆(事实多年保持相关),可以放宽它;两者只需在 constants.py 中修改一行代码。
使用 Elasticsearch DLS 实现多租户隔离
文档级安全(DLS)将隔离规则移动到集群本身。每个用户获得一个 API 密钥,其角色描述符携带一个 DLS 查询,该查询只允许属于该用户的文档(以及共享目录,该目录没有 user_id 字段)。使用该密钥的智能体可以运行任何它想执行的查询,但永远不会看到其他用户的文档。集群根本不会返回它们。这就是生产环境的隔离保证,在每次使用该密钥发起的查询中,在服务端强制执行。
检索器还在代码中附带了一个 user_id 过滤器,作为对抗配置漂移的防御性检查:一个没有 DLS 的新索引模板落下来、某个角色描述被修改后子句被静默丢弃、管理密钥被误复用等情况。DLS 是架构层面的防护;这个代码级别的检查在查询时几乎不产生成本。
将共享目录数据集成到智能体记忆检索中
记忆查找就是一次 Elasticsearch 查询。针对 Sarah 的 API 密钥的 DLS 查询会筛选出 user_id == "sarah" 的文档。目录和其他共享索引根本没有 user_id 字段;它们旨在对所有人可见。为了包含它们,DLS 查询从"必须等于 sarah"放宽为"等于 sarah 或者没有 user_id":使用一个 bool.should 条件,接受 user_id == "sarah" 或者 must_not exists: user_id。检索器、RRF 融合和衰减函数保持不变:目录和个人记忆落在同一次召回过程中。
一个启动脚本会生成每个用户的 DLS 密钥,并将放宽后的查询内置其中。
现在,针对"只显示白色的智能灯泡"的查找,会同时返回 Sarah 存储的约束条件和关于灯泡兼容性的目录条目,并按相关性一起排序。
用户记忆和目录可能落在同一次、同一主题的召回中,并且互相矛盾。检索器在处理时间衰减的同一脚本(多出一个 _index 分支,没有新机制)内,应用了一个小的来源先验权重(CATALOG_SOURCE_PRIOR, 0.85),因此在接近平局时用户记忆胜出。这是一种软倾斜,而非路由规则:当目录具有明显更强的相关性匹配(如产品规格、技术查询)时,重排序器仍然会选择目录。硬边界情况("规格查询永远信任目录"或相反地,个人偏好永远信任用户记忆)由智能体的系统提示词处理,而非检索器。
通过 MCP 连接任意智能体
记忆层在不受限于某个单一智能体时最为有用。模型上下文协议(MCP)天然提供了这一点。端点是 /api/atlas/mcp/{user_id},因此任何支持 MCP 的客户端(Claude Desktop、Cursor、你自己的智能体)都可以通过将 mcp.py 中的 JSON 片段粘贴到其配置中来完成接入。
对于 Claude Desktop,配置文件位于 ~/Library/Application Support/Claude/claude_desktop_config.json(macOS)或 %APPDATA%\Claude\claude_desktop_config.json(Windows)。对于 Cursor,将配置粘贴到设置 → MCP 下。重启客户端后,三个 Atlas 工具(recall_memory、write_memory 和 forget_memory)会出现在工具抽屉中,调用与 FastAPI 应用相同的 Elasticsearch 索引。相同的内存层,任何智能体,无需重写。三个工具契约定义在 tools.py 中。
衡量智能体内存召回质量
关于“召回”的说明:本文其他地方指的是记忆召回(智能体检索存储的事实),但这里指的是不相干的信息检索指标 Recall@K:正确的文档是否出现在前 K 个结果中。
内存架构难以验证。此处的评估是问答式段落检索,即标准的 RAG 基准。对于每个采样的文档,大语言模型会生成两个用户可能提出的合理问题,其答案就是该文档。例如,“我宝宝的睡眠很脆弱,设置自动化时有什么需要记住的吗?”指向 Sarah 的育儿室安静时间事实。然后检索器必须在前 k 个结果中定位到源文档。
存在像 LoCoMo 这样的通用内存专项基准,可以使数字跨系统可比。采用特定语料库的问答模式有两个原因。首先,它针对每个角色测试实际部署的语料库,因此召回率数字反映了真实对话中可能出现的情况。其次,它隔离了混合+衰减+重排序流水线正在迭代的检索部分(源文档是否出现在前 K 个结果中);LoCoMo 的对话连贯性指标衡量的是更下游的内容。后续文章将运行完整的 LoCoMo 基准,并分离检索性能与大语言模型选择及提示工程带来的混淆因素。

泄漏数字是任何多租户内存系统的准入门槛;其余部分则是质量故事。评估在 CI(eval_recall.py)中设定阈值为 R@10 ≥ 0.85,R@5 ≥ 0.75,泄漏 = 0。这些数字是近似值,因为重排序器存在服务端差异:在连续四次运行中,R@10 分别为 0.85、0.88、0.89、0.893。

语义事实是更困难的情况(R@10 ≈ 0.81);情景记忆平均为0.98,程序性记忆命中率则为1.0。原因是“兄弟碰撞”:一个关于Sarah集线器断连的问题在语料库中有多个看似正确的事实,检索器有时会选错一个。值得注意的是:兄弟碰撞通常不会降低智能体的回答质量(它仍然会得到一个相关且真实的事实),所以R@10对于语义事实而言显得比较保守。
智能体记忆架构:关键决策
智能体记忆由几个问题组成,每个问题只有一步解法:
- 记忆并非单一事物。三个索引,每个对应一个生命周期:情景记忆(发生了什么)、语义记忆(什么是真的)、程序性记忆(什么有效)。
- 大语言模型通过改述消除了关键词的精确性。每一轮对话都以检索原始消息的逐字内容开始;检索是混合式的,然后进行重排序。
- 仅追加的记忆会腐烂。巩固过程将情节提升为持久的事实;取代过程则会淘汰用户反驳过的那些事实。
- 旧事实不应与新鲜事实排名相同。评分会随时间衰减,而一次召回会将某个事实重新提升上来。
- 租户之间绝不能互相看到。隔离是通过DLS在集群层面实现的,而不是用一个你可能忘记添加的过滤器。
上述所有功能都不是独立的系统:目录、隔离和衰减都组合成一个Elasticsearch查询。
只要把这些做对了,那个一直告诉Sarah重置集线器的助手终于记住了:她三月份就试过了,狗会咬坏传感器线缆,而且家现在在爱丁堡。
这些内容对您有多大帮助?
没有帮助
略有帮助
非常有帮助