首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >AI Agent 记忆:使用 Elasticsearch 管理记忆,打造智能 Agent

AI Agent 记忆:使用 Elasticsearch 管理记忆,打造智能 Agent

原创
作者头像
点火三周
发布2026-04-29 22:20:42
发布2026-04-29 22:20:42
1190
举报
文章被收录于专栏:Elastic Stack专栏Elastic Stack专栏

在本文中,我们将学习如何使用记忆技术,以 Elasticsearch 作为记忆和知识的数据库,让 Agent 变得更智能。

理解大语言模型(LLM)中的记忆

有一个概念常常让人困惑:与 LLM 的对话是完全无状态的。每次发送消息时,你都需要包含整个对话历史,以“提醒”模型之前发生了什么。在单个对话会话中,能够追踪提问和回答的能力,我们称之为短期记忆

但有趣的地方在于:我们完全可以操控这段对话历史,而不仅仅是简单地存储它。例如,当我们想要跨不同对话持久保存用户偏好等记忆时,我们可以在需要时将这些记忆注入到新的对话中,这被称为长期记忆

为什么要修改对话历史?

有三个令人信服的理由,让我们不仅仅是简单地将每条新消息和响应追加到一个不断增长的列表中,然后每次请求都发送给 LLM:

  • 注入有用的上下文: 添加关于先前交互的信息,例如用户偏好,而不会使当前对话变得杂乱。
  • 总结并移除数据: 清理模型已经使用过的信息,以避免混淆(即上下文污染),并保持模型的专注度。
  • 节省 Token: 移除不必要的数据,防止填满上下文窗口,从而实现更长、更有意义的对话。

这带来了一些科幻般的可能性。想象一下,一个 Agent 能够根据其环境或对话对象,有选择性地记住事物,就像电视剧 《人生切割术》(Severance) 一样。剧中主角马克(Mark)的大脑中被植入了一颗芯片,根据他是在办公室内(“内我”)还是办公室外(“外我”),芯片会创造出两个拥有不同记忆的独立身份,并根据地点进行切换。

Agent 中的记忆类型与选择性检索:使用 Elasticsearch 管理记忆来创建智能 Agent

并非所有记忆都服务于相同的目的,如果将它们视为可互换的对话历史,将会限制 Agent 的扩展能力。现代 Agent 架构,包括像 语言 Agent 的认知架构(CoALA) 这样的框架,区分了程序性记忆情景性记忆语义性记忆。这些架构并非将所有上下文视为一个不断增长的单一缓冲区,而是认识到每种记忆类型都需要不同的存储、检索和整合策略。

程序性记忆:Agent 如何运作

程序性记忆 定义了 Agent 的行为方式,而非它知道或记住什么。

在实践中,这包括:

  • 何时存储记忆。
  • 何时检索记忆。
  • 如何总结对话。
  • 如何使用工具。

在我们的系统中,程序性记忆主要存在于应用程序代码和提示词(Prompt)中,并不存储在 Elasticsearch 中。相反,Elasticsearch 是由程序性记忆所使用的。

程序性记忆决定了记忆如何使用,而非存储了什么内容。

情景性记忆:发生了什么

情景性记忆 捕捉与某个实体和上下文相关的特定经历。

例如:

  • “彼得的生日是明天,他想吃牛排。”
  • “珍妮丝有一份报告需要在早上 9 点前提交。”

这是最动态、最个人化的记忆形式,也是最容易因处理不当而导致上下文污染的一种。

在我们的架构中:

  • 情景性记忆作为文档存储在 Elasticsearch 中。
  • 每条记忆都包含元数据(用户、角色、时间戳、Innie 或 Outie)。
  • 检索是基于提问者及其上下文进行选择性检索的。

这就是 Innie/Outie 模型作为情景性记忆隔离示例的体现。

语义性记忆:客观事实

语义性记忆 代表关于世界的抽象化、通用化知识,独立于任何单一交互或个人背景。与情景性记忆(与谁说了什么、何时说的相关)不同,语义性记忆捕捉的是普遍为真的事实。

在我们的类比中,关于卢蒙公司(Lumon) 的知识——即剧中马克工作的公司——是 Innie 和 Outie 共享的客观事实。

像公司手册和规则这类内容,就是作为语义性记忆的知识的一部分。

虽然情景性记忆检索优先考虑精确性和强大的上下文过滤器(如身份、角色和时间),但语义性记忆则偏向于高召回率、概念层面的检索。它的设计目的是呈现普遍为真的信息,这些信息可以为推理提供基础,而不是与特定情境相关的个人经历。

让我们转向架构部分,看看这些想法如何转化为我们 Agent 的记忆系统。

前置条件

  • 一个 Elasticsearch Elastic Cloud Hosted (ECH) 或自托管 9.1+ 实例。
  • Python 3.x。
  • OpenAI API Key

此应用程序的完整 Python 笔记本可在此处找到:点击这里

为什么选择 Elasticsearch?

Elasticsearch 是存储知识和记忆的理想解决方案,因为它是一个原生向量数据库,并且已准备好进行扩展。它为我们提供了管理选择性记忆所需的一切:

  • 向量数据库,具备混合搜索功能,可以根据上下文(而不仅仅是关键词)找到记忆。
  • 多种数据类型,包括文本、数字、日期和地理位置。
  • 元数据过滤器,用于跨不同字段进行复杂查询。
  • 文档级安全功能,可以根据提问者是谁来过滤记忆。

为什么选择性记忆能改善延迟和推理能力

选择性记忆不仅关乎正确性和隔离性,它还对延迟和模型性能有直接影响。通过在运行语义检索之前,使用结构化过滤器(如记忆类型、用户或时间)来缩小搜索空间,Elasticsearch 减少了需要评分的向量数量以及需要注入到 LLM 的上下文数量。这带来了更快的检索速度、更小的提示词,以及模型更集中的注意力。在实践中,这转化为更低的延迟、更少的 Token 消耗和更准确的响应

情景性记忆本质上是时间性的:最近的经历通常比旧的经历更相关,并且并非所有记忆都应永远以相同的详细程度保留。在人类认知中,经历会逐渐被遗忘、总结或整合成更抽象的知识。

记忆压缩是一个完全不同的话题,但你可以实施一些策略来总结和存储旧的记忆,同时完整地检索新的记忆。

系统设置

遵循《人生切割术》 的概念,我们创建一个名为马克(Mark)的 Agent,他拥有两套独立的记忆集:

  • Innie 记忆:与同事的工作相关对话。
  • Outie 记忆:与朋友和家人的个人对话。

当马克与 Innie 交谈时,他不应该记得与 Outie 的对话,反之亦然。

流程图展示 Agent 如何通过 Elasticsearch 的安全索引检索和存储记忆,同时与 OpenAI 工具协调以回答用户问题。
流程图展示 Agent 如何通过 Elasticsearch 的安全索引检索和存储记忆,同时与 OpenAI 工具协调以回答用户问题。

构建记忆系统

记忆索引结构

首先,我们定义记忆模式(Schema):

代码语言:python
复制
mappings = {
    "properties": {
        "user_id": {"type": "keyword"},
        "memory_type": {"type": "keyword"},
        "created_at": {"type": "date"},
        "memory_text": {
            "type": "text",
            "fields": {
                "semantic": {
                    "type": "semantic_text"
                }
            }
        }
    }
}

请注意,我们对 memory_text 使用了多字段,这样我们就可以对同一字段内容进行全文搜索语义搜索(使用默认的 Elastic Learned Sparse EncodeR (ELSER) 模型)。

这为我们提供了语义搜索能力,同时保留了用于过滤的结构化元数据。

设置文档级安全

这是实现选择性记忆的关键部分。我们创建两个独立的角色:一个用于 Innie,一个用于 Outie,每个角色都内置了查询级别的过滤器。当具有 Innie 角色的用户查询记忆索引时,Elasticsearch 会自动应用一个过滤器,只返回 memory_type 等于 "innie" 的记忆。

你可以在此处找到更多关于访问控制的说明性示例,以及关于角色管理的文档

以下是 Innie 角色的配置:

代码语言:python
复制
innie_role_descriptor = {
    "indices": [
        {
            "names": ["memories"],
            "privileges": ["read", "write"],
            "query": {
                "bool": {
                    "filter": [
                        {"term": {"memory_type": "innie"}}
                    ]
                }
            }
        }
    ]
}

我们为 Outie 创建一个类似的角色,只需将过滤器改为 "memory_type": "outie" 即可。

Elasticsearch 角色配置界面,显示一个对 memories 索引具有读写权限的角色,并使用文档级安全来限制对特定记忆文档的访问,用于管理记忆的智能 Agent。
Elasticsearch 角色配置界面,显示一个对 memories 索引具有读写权限的角色,并使用文档级安全来限制对特定记忆文档的访问,用于管理记忆的智能 Agent。

然后,我们创建用户并将其分配给这些角色。例如:

  • 彼得(Outie):只能访问标记为 "outie" 的记忆。
  • 珍妮丝(Innie):只能访问标记为 "innie" 的记忆。

当马克(我们的 Agent)收到查询时,他使用提问者的凭据。如果彼得提问,马克就使用彼得的凭据,这意味着 Elasticsearch 会自动过滤,只显示 Outie 的记忆。如果珍妮丝提问,则只显示 Innie 的记忆。

应用程序代码不需要处理用户管理过滤,它与应用程序逻辑完全解耦。Elasticsearch 自动处理所有安全事务。

创建 Agent 工具

我们为 Agent 定义了三个关键函数:

代码语言:python
复制
def get_memory(query: str):
    es_query = {
        "retriever": {
            "rrf": {
                "retrievers": [
                    {
                        "standard": {
                            "query": {
                                "semantic": {
                                    "field": "semantic_field",
                                    "query": query
                                }
                            }
                        }
                    },
                    {
                        "standard": {
                            "query": {
                                "multi_match": {
                                    "query": query,
                                    "fields": ["memory_text"]
                                }
                            }
                        }
                    }
                ],
                "rank_window_size": 50,
                "rank_constant": 20
            }
        }
    }
    
    response = user_es_client.search(index="memories", body=es_query)
    return response

请注意,我们没有在查询中应用安全过滤器;Elasticsearch 会根据用户的凭据自动处理。

  • SetMemory:存储新的记忆(其实现使用 LLM 将对话转换为结构化的记忆记录)。

Agent 如何使用这些工具

当用户向马克提问时,流程如下:

  1. 用户提问: “我最喜欢的家庭度假目的地是什么?”
  2. LLM 决定使用工具: OpenAI 的 Response API 配合函数调用(Function Calling),让 LLM 决定需要调用 GetMemories,查询参数为“最喜欢的家庭度假目的地”。
  3. 我们执行函数: 我们的代码使用用户的凭据(这里是彼得的凭据)调用 get_memory("最喜欢的家庭度假目的地")
  4. Elasticsearch 自动过滤: 因为我们使用了彼得的凭据,所以只返回 Outie 的记忆:记忆 peter125: (用户名是彼得·约翰逊。他最喜欢的家庭度假目的地是迪士尼乐园。)
  5. 我们将结果发送回 LLM: 该记忆被添加到对话上下文中。
  6. LLM 生成回答: “你最喜欢的家庭度假目的地是迪士尼乐园。”

以下是处理此循环的实际代码:

代码语言:python
复制
# 初始调用,提供可用工具
response = client.responses.create(
    model="gpt-4.1-mini",
    input=messages,
    tools=tools,
    parallel_tool_calls=True
)

# 执行 LLM 请求的任何工具调用
for tool_call in response.output:
    if tool_call.name == "GetMemories":
        result = get_memory(tool_call.arguments["query"])
        # 将结果添加到消息中
        # 再次调用 LLM,传入工具结果以生成最终答案
final_response = client.responses.create(
    model="gpt-4.1-mini",
    input=messages  # 现在包含工具结果
)

关键点在于:应用程序不决定检索哪些记忆或何时检索。LLM 根据用户的问题来决定,而 Elasticsearch 则确保只有正确的记忆可以被访问。

测试选择性记忆

让我们看看实际效果:

Outie 对话(彼得):

代码语言:txt
复制
彼得:嘿,马克,明天是我的生日!我晚餐想吃牛排。
马克:太好了!(记忆已存储)

马克将此存储为与彼得关联的 Outie 记忆。以下是该记忆在 Elasticsearch 中的样子:

代码语言:json
复制
{
    "user_id": "peter125",
    "memory_type": "outie",
    "created_at": "2025-10-11T18:02:52.182780",
    "memory_text": "彼得的生日是明天。他晚餐想吃牛排。"
}

Innie 对话(珍妮丝):

代码语言:txt
复制
珍妮丝:嘿,马克,记得我们明天早上 9 点必须完成年终报告。
马克:谢谢你提醒我!(记忆已存储)

这会创建一个独立的 Innie 记忆:

代码语言:json
复制
{
    "user_id": "janice456",
    "memory_type": "innie", 
    "created_at": "2025-10-11T19:15:33.445821",
    "memory_text": "明天早上 9 点与珍妮丝一起提交年终报告的截止日期。"
}

假设彼得也在卢蒙公司工作。一位同事存储了一条与他相关的工作记忆:

代码语言:json
复制
{
    "user_id": "innie-peter",
    "memory_type": "innie",
    "created_at": "2025-10-11T20:30:00.000000",
    "memory_text": "彼得需要在周五之前审查第四季度的预算电子表格。"
}

这条记忆存在于 Elasticsearch 中,但彼得当前的凭据只授予他 Outie 角色。当他向马克询问工作任务时,这条记忆对他来说是不可见的;Elasticsearch 的文档级安全确保它永远不会被返回。

注意:为了允许与这些记忆进行交互,你需要为彼得创建一个单独的用户(或分配一个额外的角色),赋予其“Innie”访问权限。这留作练习,但它展示了同一个人可以拥有隔离的记忆上下文,而访问权限完全由安全层控制。

记忆隔离测试

现在彼得开始一个新的对话:

代码语言:txt
复制
彼得:嘿,马克,你记得我生日想要什么吗?
马克:记得!你想要牛排。

彼得:你什么时候需要完成年终报告?
马克:你在说什么?

完美!马克在与彼得交谈时,只访问 Outie 记忆。Agent 的“大脑”真正实现了分裂,就像剧中一样。

完整实现

完整的可工作实现可在此笔记本中找到,你可以:

  • 设置 Elasticsearch 索引。
  • 创建具有文档级安全的角色和用户。
  • 使用 OpenAI 的 Response API 构建 Agent。
  • 测试选择性记忆系统。

结论

记忆不仅仅是存储过去对话的地方。它是 Agent 架构的一部分。通过超越原始的对话历史,分离程序性、情景性和语义性记忆,我们可以构建出推理更清晰、扩展性更好、能在长时间交互中保持专注的 Agent。

选择性检索减少了上下文污染,降低了延迟,并提高了发送给 LLM 的信息质量。情景性记忆可以按用户和时间进行过滤,语义性记忆可用于基于共享知识来支撑答案,而程序性记忆则控制着如何以及何时使用所有这些。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 理解大语言模型(LLM)中的记忆
  • 为什么要修改对话历史?
  • Agent 中的记忆类型与选择性检索:使用 Elasticsearch 管理记忆来创建智能 Agent
    • 程序性记忆:Agent 如何运作
    • 情景性记忆:发生了什么
    • 语义性记忆:客观事实
  • 前置条件
  • 为什么选择 Elasticsearch?
    • 为什么选择性记忆能改善延迟和推理能力
  • 系统设置
  • 构建记忆系统
    • 记忆索引结构
    • 设置文档级安全
    • 创建 Agent 工具
    • Agent 如何使用这些工具
  • 测试选择性记忆
    • 记忆隔离测试
  • 完整实现
  • 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档