对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。
See how MongoDB 9.0 delivers up to 2x higher throughput.
MongoDB Branding Shape
Register now >
Docs 菜单

Agent-engine-sdk-memory

适用于Atlas助手引擎内存的独立运行Python SDK — 用于AI助手的长期内存,可在Atlas助手引擎内部或外部使用。

它为代理提供了一个对象Memory,用于记录对话轮次并在以后回忆相关上下文:所学的事实(语义)、过去的对话(情景)、领域知识(分类)和可重用过程。该包自行安装,仅依赖于 pydantic 和 httpx,因此它会放入任何代理中,而不会拉入平台堆栈。

平台部署的代理也使用相同的 SDK。他们并不构造它 — 运行时会给代理一个 agent_engine_sdk_memory.Memory,它已经连接到集群内传输及其正在处理的调用的标识。相同的类和相同的方法签名;只有底层的传输方式不同。因此,当您将外部代理部署到平台上时,您针对外部代理写入的代码将保持不变,并且您从本自述文件学习;了解的知识也适用于这两个地方。

有两点不同:身份的来源(请参阅 连接 ),以及应用程序绑定的传输无法提供服务且引发 MemoryNotSupportedError 的少数调用 — 在每个连接支持的内容中列出。

如果这对您来说是新的,请从内存的工作原理开始 — 系统的结构解释了大部分API。集成分为三个步骤:安装、连接和使用。配置内存服务器中介绍了服务器端设置。关于身份、错误和模型的参考资料如下,最后是包内部信息。

内存为代理提供了重要的事实、对话、过程和词汇的持久存储,并通过从代理与法学硕士的对话中提取持久性的学习事实来填充该存储。提取本身由您配置的 LLM 完成。

对于平台部署的代理,这些都不需要连接。当代理工作流程运行时,运行时会将每个对话轮次记录到短期内存中,然后升级和提取会在背景异步进行,因此在提取事实时,没有任何东西会阻止响应。已经知道某些持久性的代理不必等待提取才能找到它 — 它可以直接使用 save_semantic 和其他类型的写入写入事实。

无论哪种方式读回它都是有效的。代理可以一次追踪一种事实 — search_semantic 表示所学知识,search_episodes 表示之前发生的情况,search_taxonomic 表示术语的含义,discover_procedures 表示某事是如何完成的 — 并处理结果本身。

或者它可以移交整个作业。 build_context_from_sources 为每个来源获取一个规范,每个来源声明自己的检索模式、过滤和候选计数,然后根据自己的条件从每个来源检索,对结果一起进行排名和去重,将其修剪到词元预算,并返回要放入下一个提示中的单个上下文区块。 build_context 对每个源使用一种模式和一个过滤执行相同的操作;当它们需要不同时,请访问每个源的表单。

记忆会根据事物的寿命和形状来分割。

短期记忆 (STM) 是原始对话:每回合一条记录,范围为一个会话,在发生时写入。写入和完成它的成本都很低 — 一切都按顺序进行。

长期记忆是在对话中幸存下来的记忆,可分为四种形状,因为它们回答不同的问题:

类型
持有
答案

semantic

带标签的事实

“我对此了解多少?”

episodic

摘要事件

“之前发生了什么?”

procedural

可重用的过程

“我该怎么做?”

taxonomic

术语及其定义

“这个词在这里是什么平均值?”

项目还可以为四种形状不适合的域记录声明自定义类型。

虽然可以手写,但不能写入长期记忆。正常路径是:

record_turn(...) you write turns as the conversation happens
│
▼ turns accumulate into a session
snapshot a contiguous run of turns, summarised
│
▼ an LLM reads the snapshot and extracts what is durable
semantic · episodic · procedural · taxonomic

提取是异步的并在背景运行,因此 record_turn 保持快速写入。因此,在一次对话中了解到的事实可用于下一次对话,而不是下一回合。

值得设计的结果是:通过 STM 可以立即看到最近的回合,提取的数据稍后才会出现。当您需要刚才所说的内容时,请询问 stm。

后台提取依赖于嵌入服务、提取 LLM 和数据库,其中任何一个都可能失败。您的写入不会受到以下影响:无论下游发生什么,record_turn 返回时都会成功。

在幕后,失败的步骤按一个问题进行分类:条件是否可以在请求不改变的情况下发生变化?环境故障(网络错误、提供商中断、速率限制、 API密钥轮换中)会使用退避功能自动重试,并根据故障的进度进行调整:网络 blip 在几秒钟内重试,凭证问题则要慢一些,因为密钥是由人工轮换的。内容确定的失败不会重试,因为重试无法更改结果;它们记录在服务器端,操作员可以看到它们,而不是永远循环。

这适用于整个提取路径,而不仅仅是其外边缘。失败的步骤不再默默地产生空结果:它要么重试,要么被记录在操作符可以对其采取行动的位置。

一个不可用的项目不会丢弃其余项目。如果无法嵌入单个内存(其内容对于模型来说太长,或者提供商拒绝它),则仍会存储该内存,并且同一批处理中的其他内存不受影响。存储的内存缺少的是向量。在实践中:

  • 它仍然可以通过 text搜索和 hybrid 找到 — 混合结合了两个排名,因此文本侧仍然显示它。

  • semantic搜索仅比较向量,未找到此值。

这是选择 hybrid 作为默认检索模式的充分理由。对于精确标识符和稀有词,它已经是更好的选择,并且这也意味着无法嵌入的记忆仍然可以到达您的身边。

这对代理意味着什么:

  • 提供商中断延迟了提取的知识;它不会失去轮次。 STM 是同步写入的,不受影响 — 当您需要刚才所说的内容时,请继续询问 stm。

  • 重试是有限制的:超过重试时间的依赖项中断会停止重试,而不是无限循环,并且会记录失败而不是丢弃失败。

  • 这些都不会通过 SDK 显示为错误。提取失败是服务器端关注的问题; SDK 可见信号提取尚未出现在检索中的记忆,或出现在文本和混合语义搜索的记忆。

汇编的散文,还是文件本身——两个不同的问题。

``build_context_from_sources(...)`` 是默认使用的方法。你给它一个查询和一个令牌预算;它会检索您指定的内存类型,对结果进行排名,删除接近重复的项,缩减预算,然后返回可直接放入提示中的内容。它是一次调用背后的整个检索管道,并且每个源使用一个规范,因此模式和过滤器是针对每个源设立的,而不是为所有源设置一次。

检索可以通过三种方式进行匹配。 semantic 比较嵌入并查找平均值相同的内容; text 匹配词语并找到表达相同内容的事物; hybrid 运行两者并融合排名。混合通常是正确的默认— 仅使用向量搜索会错过确切的标识符和稀有单词,仅使用文本搜索会错过释义。

``build_context(...)`` 是更简单的构建器:相同的管道,但它读取的每个源都应用一种模式和一个过滤。

``搜索(...)`` 和按类型搜索(search_semantic、search_episodes 等)返回排名记录,而不是汇编的散文,以便您想自己检查或后处理它们。

每条记录都带有写入时使用的身份,并且每次读取都在数据库查询中(而不是事后)按该身份进行筛选。这些字段包括: “组织”、“用户”、 “项目”、“会话”和“代理”(可选),以及决定记录是用户私有还是具有更广泛可读性的可见性。

这具有实际意义:作用域为用户的搜索或 build_context 不会显示其他用户的私有内存,因为约束是查询的一部分,而不是应用于其结果的过滤。因此,bind(...) 并不是一种方便,它关系到如何声明后续读取和写入操作的范围,如果错误,则会以另一个用户的身份写入一个用户的内存。

user_id 是一条记录在谁的内存中。 visibility 是其到达的距离:

可见性
谁可以阅读

private

仅限于以下名称中指定的用户: user_id

shared

项目中的任何用户 — 已弃用,请参阅下文

org

项目中的任何用户

可见性值不会到达写入记录的项目之外。内存是按项目存储的,因此可见性无法跨越项目边界 — org 是历史名称,意思是“不限于一个用户”,而不是“对其他项目可见”。

警告

⚠️ ``shared`` 已弃用,并将在未来的发布中删除。对于超出单个用户范围的数据,请使用 org,对于仅限其所有者的任何数据,请使用 private。由于 shared 和 org 已解析为相同的范围,因此将现有记录从 shared 切换到 org 不会改变可以读取该记录的人员。已写入 shared 的记录暂时继续读回。

两者是独立的,读取时它们作为“与”结合,而不是作为“或”结合。您提供的每个字段都会为查询添加一个相等条件;您省略的每个字段都会使该维度不受约束:

确定读取范围
你会回来

user_id only

用户在任何可见性下拥有的所有内容

visibility only

具有该可见性的每条记录,无论其所有者是谁

两者

仅匹配两者的记录 — 最窄读取

两者都不是

项目中的所有内容

第三排是让人感到惊讶的一排。 search_semantic(query, user_id="user_1", visibility="org") 并不平均值“user_1 的内存加上组织的内存”。它的意思是“user_1 拥有的组织可见内存”,小于任何一个单独的约束。没有并集:要读取共享知识和用户自己的私有内存,请进行两次调用并自行合并结果。

pip install agent-engine-sdk-memory
from agent_engine_sdk_memory import Memory, MemoryRequestContext

代理的运行位置决定了它的连接方式,区别主要在于谁提供身份。

你的代理运行
你构建
身份来自
请参阅

在平台上

无 — 运行时注入 memory

每次调用的运行时

其他任何地方

Memory(service_account_token=..., project_id=...)

您的服务帐户令牌,加上您 bind

本地、开发中

Memory(base_url=...)

无论你 bind

这三个版本的API是相同的。针对外部部署的代理编写的代码在移动到平台上时运行不变 — 您删除构造函数调用,运行时会提供对象。

平台部署的代理免费获得身份,这是本质上的区别。运行时已经知道它正在处理的调用的“组织”、 “项目”、“用户”和“会话”,因此它会为您绑定这些内容。外部代理只知道其服务帐户令牌意味着什么(项目),因此它必须告诉内存每个调用属于哪个用户和会话。如果错误,您将以另一个用户的身份写入一个用户的内存,这就是为什么外部路径要求您是显式的。

托管服务。传递服务帐户访问权限令牌和您的项目ID。

使用 agentengine CLI创建令牌。一次性创建服务帐户 —客户端密钥仅显示一次,因此请立即保存:

agentengine service-account create my-agent --project-id <your-project-id> --role AGENT_DEVELOPER

然后用客户端ID和密钥换取短期(1 小时)访问权限令牌(curl 提示输入客户端密钥,使其不出现在Shell历史记录中):

ACCESS_TOKEN=$(curl --fail-with-body --silent --show-error --user <client-id> --data grant_type=client_credentials https://agentengine.mongodb.com/api/v1/oauth/token | jq -er .access_token)
memory = Memory(service_account_token="<your-access-token>", project_id="<your-project-id>")
输入
回落至
注意

service_account_token

AGENTIC_MEMORY_SERVICE_ACCOUNT_TOKEN

服务帐户访问权限令牌,作为不记名凭证发送。过期后重新铸造。

project_id

AGENTIC_MEMORY_PROJECT_ID

要读取和写入的项目。对于托管服务是必需的。

base_url

AGENTIC_MEMORY_BASE_URL

可选。覆盖托管以定位非生产堆栈。

平台会将您的档案固定到其项目,因此不匹配的 project_id 会被拒绝。空白 service_account_token 引发 ValueError。

api_key (和 AGENTIC_MEMORY_API_KEY)仍然被接受为已弃用的别名并发出 DeprecationWarning;同时传递新输入和旧输入会引发 ValueError。无法再通过HTTP创建项目API密钥,因此新的集成必须使用服务帐户令牌。

指向您自己运行的后端,例如 agentengine dev up 启动的堆栈。

memory = Memory(base_url="http://localhost:8080")
输入
回落至
注意

base_url

AGENTIC_MEMORY_BASE_URL

后端的URL。

service_account_token

AGENTIC_MEMORY_SERVICE_ACCOUNT_TOKEN

可选。对于本地开发,请省略它。 (api_key / AGENTIC_MEMORY_API_KEY 为已弃用的别名。)

将 project_id 留空,以便本地开发。

当您的代理在Atlas Agent Engine 上运行时,您根本不需要构建或连接 Memory — 该平台会注入一个准备就绪的预连接实例,其中包含已绑定的身份、租赁和传输。应用程序代码不传递上述任何输入;它使用运行时传递给它的处理。身份(用户、会话、组织、项目)从环境运行时上下文中解析,因此可以直接调用操作:

# `memory` is supplied by the platform runtime — do not construct it.
memory.record_turn(role="user", content="I'm allergic to penicillin.")
context = memory.build_context(query="What medications should I avoid?")
# bind(...) is still available to scope a call chain to a specific identity.

受应用程序限制的路径存在一些功能差距(工具调用/模型转向元数据和一些列表式读取);请参阅每个连接支持的内容中的应用程序绑定列和 `docs/capability-matrix.md `__。

当您省略该参数时,每个输入还会回退到 AGENTIC_MEMORY_* 环境变量。显式参数总是获胜。不带参数的 Memory() 会从环境中读取所有三个值。

project_id 决定呼叫的去向。身份验证从不这样做。

  • 设置 project_id,SDK 将调用项目的路由 /api/v1/projects/{project_id}/memory/*。

  • 将其留空,SDK 会直接调用后端/api/v1/memory/*。

如果设立了 project_id 但后端没有匹配的路由(例如本地后端),则调用会引发 `MemoryRouteNotFoundError <#errors>`__ 并提示取消设置。反之亦然。以不带 project_id 且带提示的调用 404 的托管服务为目标进行设立。

身份验证和路由是独立的,因此您可以传递 project_id 为空的 service_account_token。然后,SDK 在直接路由上将经过身份验证的调用直接发送到 base_url 的后端,从而跳过项目路径。这适合直接访问的托管编排引擎 (OE)。

功能遵循呼叫路由的位置,而不是身份验证。

操作
托管(project_id 设立)
直接(project_id 为空)
应用程序绑定

record_turn, build_context

✓

✓

✓

build_context_from_sources

✓

✓

✓

record_turn 带有工具调用/模型元数据

✓

✓

✗

search_episodes (以及基于情节的 search)

✓

✓

✓

search_semantic, discover_procedures

✓

✓

✓

search_taxonomic

✓

✓

✓

特定于类型的增删改查 (save_*, get_*, list_*)

✓

✓

✓ 有间隙

自定义类型 save / retrieve

✓(标记门控)

✗ 无执行上下文

✓

不支持的调用会引发 `MemoryNotSupportedError <#errors>`__ — 在针对每个后端已知差距的任何网络请求之前,或者在只有平台可以报告的功能差距响应之后(请参阅错误 ) — 绝不是静默故障或原始错误HTTP错误。每个后端的完整参考(包括特定类型增删改查上的应用程序绑定间隙)位于 `docs/capability-matrix.md `__ 中。

应用程序绑定助手是上述所有情况的例外。在已部署的 平台代理内部,平台会注入一个就绪的 runtime,因此应用程序代码不会传递任何输入。

完整的往返:构建内存、绑定会话身份、记录回合和检索上下文。

from agent_engine_sdk_memory import Memory, MemoryRequestContext
memory = Memory(service_account_token="<your-access-token>", project_id="<your-project-id>")
# Scope every call to a user and conversation.
session = memory.bind(MemoryRequestContext(user_id="user_1", session_id="thread_123"))
# Record what happened.
session.record_turn(role="user", content="I'm allergic to penicillin.")
session.record_turn(role="assistant", content="Noted — I'll avoid it.")
# Later, pull back the relevant context for a new prompt. Include "stm"
# to surface the turns just recorded (the default is episodic, semantic).
# max_tokens is an optional gross context-construction budget.
context = session.build_context(
query="What medications should I avoid?",
enabled_sources={"stm", "episodic", "semantic"},
max_tokens=2048,
)
# context is a ContextResponse — inject its content into the next prompt.

bind(ctx) 返回范围为 ctx 的新处理,而不改变原始句柄,因此一个 Memory 可以同时为许多用户和会话提供服务。

``build_context``默认源。省略的 enabled_sources 默认为 episodic 和 semantic; stm、taxonomic 和 procedural 需要显式设立。

``max_tokens``。可选的正上下文构建总预算。它不是获取费用或承诺的输出大小:在检索和排名后,服务器减去 500-token 格式保留,然后贪心地选择适合余数的整个内存块。等于或低于 500 的正值不会留下内存预算。当没有适合的数据块时,高于 500 的值仍可能产生空上下文。 metadata.token_count 仅报告格式化输出,不包括保留。省略 max_tokens 以保持先前的行为。

``format_style`` 和 ``include_memories``。 build_context 和 build_context_from_sources 接受两个响应调整选项。 format_style("openai"、"claude" 或 "jinja2";为类型注解导出 FormatStyle枚举)选择 formatted_context 的格式,无效值会在本地引发 ValueError。省略,服务器从其配置的模型中推断格式。需要注意的是:当前,当显式值与其默认模型类型匹配时,服务器会重新推断,因此仅在配置了 OpenAI 系列模型的服务器上才会逐字遵循显式 "openai"; "claude" 和 "jinja2" 始终受到尊重。 include_memories=True 使用预算后 MemoryChunk 列表填充响应的 selected_memories,因此您可以准确检查选择了哪些内存。两者都需要托管或直接HTTP连接;当设立其中之一时,应用程序绑定模式会引发 MemoryNotSupportedError。

每个源上下文 — ``build_context_from_sources``。在 build_context 对每个源应用一个过滤和语义检索的情况下,该方法允许每个源通过以下方式声明自己的检索 mode(text、semantic 或 hybrid)、metadata_filter 和 top_k:一个 SourceSpec。跨源对结果进行合并和去重,可以选择按相关性 rerank 重新排序,然后像 build_context 一样进行格式化和预算。 metadata.ranking_strategy 和 metadata.source_outcomes 报告最终订单是如何生成的以及每个来源的表现。 sources 必须为非空,且每个源最多可列出一次,且每个源的 top_k 必须介于 1 和 200 之间;仅当包含 stm 源时才需要 session_id。

对于带有文本的来源,混合通常是正确的默认:单独的向量搜索会错过确切的标识符和稀有单词,单独的文本搜索会错过释义。当规范省略时,mode 默认为 semantic。

声明可筛选元元数据。 metadata_filter 只能命名项目已声明的元数据,因为过滤将应用于搜索索引内部,而不是其结果。在 project-config.yaml 中声明这些属性以及必须为这些属性提供服务的索引腿:

metadata_partition_key: # Filterable metadata attributes (max 10)
- name: tier
type: string # string | number | boolean | date
- name: confidence
type: number
metadata_partition_index:
semantic: [both] # both legs, so a hybrid source can filter
short_term: [both] # stm is searchable only once listed here
short_term:
embed_on_write: true # required to give stm a vector leg

注意

需要内存服务器 0.0.81 或更高版本。分区键只有支持它们的运行时才会生效,内存服务器会在初创企业时读取其配置 — 存储配置并不表示应用配置。编辑 project-config.yaml 后,使用 agentengine memory configure 上传,然后将运行时滚动到当前映像上:

agentengine memory apply --upgrade

--upgrade 将运行时移动到环境当前固定的映像,这就是您选择更新的内存服务器的方式;如果没有它,运行时将保留已有的图像。如果您想在更改任何内容之前进行检查,agentengine memory apply --dry-run 会报告正在使用的映像,并且 --wait 会阻塞,直到运行时报告准备就绪。

在写入过滤之前,有四个值得了解的结果:

  • 过滤键是限定的索引路径,而不是声明的名称。声明为 tier 的属性在 metadata.tier 建立索引,这是过滤必须说的 — 没有任何前缀。

  • 键只能针对您列出的边进行筛选。 metadata_partition_index 将源映射到 [vector]、[text] 或 [both],并且 hybrid 源需要 [both]:如果其文本支路无法提供服务,则在运行查询之前拒绝过滤,而不是静默返回较少值。

  • 错误的密钥会导致很大的失败。与 build_context 的单个顶级 metadata_filter 不同,每个源的密钥会根据声明的设立进行验证。未声明或拼写错误的路径会引发 MemoryBadRequestError ,其消息会列出已接受的路径,因此拼写错误会导致调用失败,而不是默默地返回缩小的结果设立。

  • 短期记忆没有自己的索引。只有当项目在 metadata_partition_index 中列出 short_term 时,才可以对其进行筛选,并且完全可以通过此方法进行搜索。在此之前,stm 源会失败而不是返回任何内容:搜索错误,并且会在 metadata.source_outcomes 中使用 error 报告源。如果 stm 是唯一请求的源,则每个源都失败,调用返回 503 而不是空上下文,因为空结果与对空语料库的健康搜索无法区分。为其提供向量分支([vector] 或 [both])还需要 short_term.embed_on_write: true,没有它,配置将被拒绝,因为从未嵌入的向量索引将是自重。文本腿不需要嵌入。

声明的 type 设置匹配语义。 string 键作为词元进行索引,因此匹配为整数值且区分大小写:"gold" 既不匹配 Gold,也不匹配 gold-tier。范围操作符需要 number 或 date 键。

支持的子句:相等; $gt / $gte / $lt / $lte,合并边界融合到一个范围中; $in / $nin 用于集合; $ne; $exists; $and / $or / $nor 用于组合。

agent_id 无需声明即可过滤。租户字段 org_id、user_id、project_id、session_id、visibility、deleted、is_latest 和 has_embedding 保留用于访问权限控制,并且不能由调用者过滤。

将它们放在一起。使用上述配置:

from agent_engine_sdk_memory import SourceSpec
context = session.build_context_from_sources(
query="what did we decide about the refund policy?",
sources=[
# Exact match on a string key, and a range on a numeric one.
SourceSpec(
source=MemorySource.SEMANTIC,
mode=RetrievalMode.HYBRID,
metadata_filter={
"metadata.tier": "gold",
"metadata.confidence": {"$gt": 0.8},
},
top_k=20,
),
# Sources without a filter are unrestricted.
SourceSpec(source=MemorySource.EPISODIC, mode=RetrievalMode.HYBRID, top_k=5),
SourceSpec(source=MemorySource.STM, mode=RetrievalMode.TEXT, top_k=10),
],
rerank=True,
)

此方法可在所有三种连接模式下到达后端。在托管项目路由 (project_id 设立) 上,网关代理相同的每源处理程序,从经过身份验证的会话中为组织/项目打上标记;在直接路由(project_id 空)上,OE 代理会转发该路由;在平台内(应用程序绑定)运行时,平台会标记租户并通过持久性执行传回完整响应。

除了 record_turn 和 build_context 之外,Memory对象还公开:

  • 搜索 — search(query, sources=[...]) 在内存类型之间展开,并返回排名靠前的 list[MemoryChunk]; search_semantic、search_episodes、search_taxonomic 和 discover_procedures 以单一类型为目标。

  • 特定类型的写入和读取(其中连接支持增删改查) — save_semantic / get_semantic、save_episode / list_episodes、save_taxonomic / get_taxonomic_term / list_domains 和 save_procedure / get_procedure 。

  • 自定义内存类型 — save(memory_type, content, tags=...) 和 retrieve(memory_type, query, tags=..., top_k=...) 对项目内存配置中声明的类型进行操作。内置类型名称将被拒绝 — 请使用上述专用方法。这些调用不包含身份字段;平台会为请求中的 org、 项目和 user 标记 org、project 和 user。在不提供服务自定义类型路由的平台上,或者禁用了该功能的平台上,调用会引发 `MemoryNotSupportedError <#errors>`__。

使用上述绑定 session 的简短示例—写入事实,按标签读回,然后跨类型搜索:

# Save a semantic fact (user_id is inherited from the bound session).
session.save_semantic(text="Prefers window seats on flights.", label="seat-preference")
# Read it straight back by label.
fact = session.get_semantic("seat-preference")
# Search across memory types; returns one ranked list[MemoryChunk].
hits = session.search("travel preferences", sources=["semantic", "episodic"], top_k=5)

Memory 也是上下文管理器; with Memory(service_account_token=...) as memory: 在退出时释放根本的传输。

SDK 读写内存;它不会配置服务器。设置 — 嵌入、提取 LLM、提取哪些内存类型、可筛选元元数据— 位于项目 project-config.yaml 的 memory:区块中,并与CLI一起应用。仅上传该子文档;文件的其余部分将被忽略。

agentengine memory configure # store the memory: block for this project
agentengine memory apply --wait # roll the memory server so it takes effect

存储不是应用。 configure 保存配置,平台自行将其传递到项目的内存服务器,但服务器仅在初创企业时读取其配置。在 Pod 重新启动之前,新设置会以未读取状态保存在磁盘上。 agentengine memory apply 执行重启 — 并首先预配内存运行时(如果项目还没有内存运行时)。

agentengine memory apply --upgrade 此外,还将运行时移动到环境当前固定的内存服务器映像,这就是您选择较新服务器的方式。如果没有 --upgrade,该图像将保持不变。 agentengine memory status 随时报告运行时状态; apply 区块上的 --wait,直到准备就绪。

配置的某些部分实际上是一次性写入的,因此在开始写入内存之前先决定这些部分:

  • 元数据分区键 — 声明的可筛选属性。密钥一旦声明就无法删除或更改其类型。

  • 自定义内存类型声明 — 一旦接受类型,就无法通过配置API编辑或删除其集合和标签集;请改为声明一个新的类型名称。

  • 搜索索引 — 仅当索引尚不存在时才进行预配。在服务器启动后添加分区键不会重建现有索引;偏差会被记录下来,然后新键上的过滤会在数据库中失败,而不是被默默地忽略。重新创建索引是一项手动操作。

日志级别、提取 LLM 和启用的提取类型可以更改和重新应用。更改嵌入模型或维度需要迁移和重新嵌入现有记忆;维度更改还需要重建向量搜索索引。

内存操作的范围由 user_id、agent_id 和 session_id 限定,并在 MemoryRequestContext 中承载。对于每次调用,每个字段都通过三个层级进行解析,优先级最高:

  1. 调用参数 — 直接传递给方法的值(例如 search_semantic(query, user_id="user_2"))。

  2. 绑定上下文 — 传递给 bind(...) 的 MemoryRequestContext。

  3. 运行时上下文 — 运行时提供的环境标识,由应用程序绑定路径使用。

Blank or whitespace-only values count as unset at every tier; agent_id is always optional. Identity is validated client-side only for the type-specific writes: save_semantic, save_taxonomic, and save_procedure require a user_id, and save_episode requires both user_id and session_id — each raises MemoryIdentityError when the field cannot be resolved. The workflow operations (record_turn, build_context, the searches) and the get_* / list_* reads do not enforce identity locally; they forward whatever resolves to the backend, which may reject the request as a transport error.

``session_id`` is scoped by operation. It identifies a conversation, so it is inherited (from bind/runtime) only for conversation I/O: record_turn and the short-term-memory leg of build_context. Episodic and semantic search do not inherit it — search_episodes, list_episodes, and the episodic leg of search() resolve session_id from the call argument only. Episodic memory is stored session-unscoped (consolidated episodes carry session_id: null), so a bound session would otherwise silently filter out every durable memory and return an empty list with no error. Pass session_id= explicitly on the search call when you do want a session-scoped episodic read. (This brings the SDK in line with the platform agent-engine-sdk-langgraph / TenantRuntime path, which already requires an explicit session_id on episodic search.) user_id and agent_id are unaffected and still inherit from bind/runtime on reads.

每次写入都需要一个 visibility。默认因内存类型而异,因为类型的使用方式不同:

写
默认可见性

save_semantic, save_episode , save_procedure

private

save_taxonomic

org

分类内存默认为 org,因为领域词汇表根据定义是共享的 —术语及其含义很少是某个用户的私事。其他所有内容均默认为 private,因此,除非您另有说明,否则您保存的事实仅限于从中获知该事实的用户。

record_turn 是个例外:它不需要 visibility,也不需要 user_id。对话轮次始终写入绑定用户和会话下,因此在记录轮次之前使用 bind(...) — 没有针对每次调用的方法将用户设立为轮次。

# Private to user_1 — the default.
session.save_semantic(text="Prefers window seats.", label="seat-preference")
# Readable across the whole organization.
session.save_semantic(
text="Refunds over $500 need manager approval.",
label="refund-policy",
visibility="org",
)

读取不会设置 visibility,因此它们仅受解析的身份约束 — 通常是绑定的 user_id。这是个性化的正确默认:您可以在任何可见性下获得用户拥有的所有内容。

要获得共享知识,您需要传递可见性,以及此处的规则咬合。绑定到 user_id="user_1" 的处理仍然会影响 user_id,因此这只会读取 user_1 拥有的组织可见记录:

session = memory.bind(MemoryRequestContext(user_id="user_1", session_id="thread_123"))
session.search_semantic("refund policy", visibility="org") # user_1 AND org

传递 user_id=None 不会使其变宽。 None 或空白参数在每个层级都算作“未提供”,因此会下降到边界值。读取处理未绑定 user_id 的所有用户:

# The original handle is unbound, so it carries no user_id.
memory.search_semantic("refund policy", visibility="org")
# Or bind only the parts you want.
thread = memory.bind(MemoryRequestContext(session_id="thread_123"))
thread.search_semantic("refund policy", visibility="org")

因此,同时需要用户自己的历史记录和团队共享知识的助手会发出两个读取并将其合并:

personal = session.search_semantic(query, top_k=5) # user_1, any visibility
shared = memory.search_semantic(query, visibility="org", top_k=5) # org-wide, any owner

错误分为两类。客户端错误是 ValueError 的子类,通常在任何网络调用之前引发:

  • MemoryClientError — 使用错误的基础。

  • MemoryIdentityError — 无法解析必填身份字段。直接对 ValueError 进行子类化(MemoryClientError 的同级,因此不会被 except MemoryClientError 捕获)。

  • MemoryNotSupportedError — 该操作不适用于活动连接(请参阅连接)。该消息会列出操作名称和原因。对于自定义类型方法 (save / retrieve),当响应显示平台缺乏此功能时,也可在HTTP调用后引发此问题:裸露的 404/405(平台太旧,无法提供服务路由)或网关的结构化 400报告部署中禁用的自定义内存类型。结构化未知类型 404 是请求错误,而不是功能差距,会引发MemoryBadRequestError。

传输错误派生自 MemoryAPIError 并携带HTTP status 和响应正文:

  • MemoryAuthError —身份验证或授权失败 (401/403)。

  • MemoryBadRequestError —请求被拒绝(除 auth 或 not-provisioned 之外的 4xx)。

  • MemoryRouteNotFoundError — 核心循环请求404,因此路由结构可能与后端不匹配。该消息提供设立或取消设置 project_id 的定向提示。 MemoryBadRequestError 的子类。

  • MemoryNotProvisionedError — 尚无法访问项目的内存运行时间。

  • MemoryServerError —后端出错 (5xx) 或返回无法解析或意外的正文。

  • MemoryConnectionError — 无法访问后端。

请求和响应类型从包根目录导出并从 agent_engine_sdk_memory.models 重新导出。检索返回 MemoryChunk 和 ContextResponse;写入会返回类型化结果,例如 WriteTurnResult、CreateSemanticResult 和 CreateEpisodicResult。 SearchSource 枚举可搜索类型。上下文构建是使用 Memory.build_context(示例enabled_sources 和 max_tokens)上的 kwargs 配置的,而不是单独的配置模型。从 agent_engine_sdk_memory(而不是 agent_engine_sdk)导入这些模型 — 模型曾经住在那里,现在已经搬到了这里。

record_turn 接受一个可选的 metadata 字典,短期检索将其返回到数据块上自己的槽中:chunk.metadata["metadata"]。您的密钥与平台的轮次字段(session_id、role、turn_seq ...)分开存放,而不是与它们混合在一起,因此您的密钥永远不会与他们的密钥发生冲突 — 您可以命名一个密钥 role 或 session_id 并读回您自己的值。

碰撞安全未涵盖的两个限制。值必须在往返过程中保持不变,因此它们必须是JSON可序列化的并且具有合理的大小。要实现可筛选,键还必须可声明为元数据分区键:每个点分隔的段必须匹配 ^[a-z][a-z0-9_]{0,63}$,最多允许一个点,并且保留一些名称(org_id、project_id、 user_id、agent_id、content、embedding、embedding_model_id、created_at)。该形状之外的键(Tier、$foo、a.b.c)仍会存储并返回,只是无法进行过滤。

空字典被视为没有元数据:metadata={} 在数据块上记录没有 metadata 槽的回合,因此如果调用者可能未提供任何元数据,请使用 chunk.metadata.get("metadata", {}) 进行读取。语义记忆的行为相同,因此一个读取路径可以覆盖两者。情节、分类和程序的行为方式相同:您写入的字典会在 chunk.metadata["metadata"] 下返回,空字典意味着根本没有插槽。情节是一个例外:如果字典恰好同时包含 citation_score、llm_confidence_score 和 combined_score,则兼容性过滤会删除整个字典(包括不相关的键),因为该形状也与迁移前的内部 blob 匹配。检索还可能带有 chunk.metadata["contextual_metadata"] — 这是平台自己的提取工件,而不是您的数据,并且它不是您应该依赖的任何合同的一部分。

短期记忆的范围仅限于会话,因此写入和读取必须命名相同的名称 — 绑定一次并传递:

from agent_engine_sdk_memory import (
Memory,
MemoryRequestContext,
MemorySource,
RetrievalMode,
SourceSpec,
)
session_id = "session-42"
memory = Memory(base_url="http://localhost:8080").bind(
MemoryRequestContext(user_id="user-1", session_id=session_id)
)
memory.record_turn(
role="user",
content="The engagement is green and the rollout continues.",
metadata={"engagement_id": "eng-alpha", "tier": 2},
)
context = memory.build_context_from_sources(
query="what is the engagement status?",
sources=[
SourceSpec(
source=MemorySource.STM,
mode=RetrievalMode.HYBRID,
metadata_filter={"metadata.engagement_id": "eng-alpha"},
)
],
session_id=session_id,
include_memories=True,
)
for chunk in context.selected_memories or []:
print(chunk.metadata["metadata"]["engagement_id"])

Atlas Search会对写入进行异步索引,因此紧随其后发出的读取操作可能还看不到轮次。

需要了解一个拼写差异:您读取了嵌套字典 chunk.metadata["metadata"]["engagement_id"] 中的裸名称,但过滤了限定的索引路径 {"metadata.engagement_id": ...}。两者都确认嵌套 —过滤对存储的文档进行寻址,而您的字典确实位于 metadata 下。

对裸名称的筛选被拒绝,并显示列出已接受路径的错误,因此该错误会告诉您回答。

必须首先为项目声明可筛选元元数据:上面的 engagement_id 必须显示在 metadata_partition_key 下,short_term 必须列在 metadata_partition_index 中。请参阅“使用”下的“声明可筛选元元数据” 。

以下部分描述了如何构建该包。他们不需要使用它。

agent_engine_sdk_memory/
├── memory.py # Memory — the public, transport-free facade
├── protocol.py # MemoryRequestContext, MemoryRuntime + MemoryCrudClient seams
├── identity.py # resolve_identity — call arg > bind ctx > runtime ctx
├── errors.py # MemoryIdentityError + the typed transport-error family
├── models.py # Pydantic models for memory requests/responses
├── validation.py # require_positive_max_tokens — public build_context guard
├── _transport.py # _HttpTransport — shared retry / error-mapping / lifecycle
├── _http_runtime.py # _HttpMemoryRuntime — the workflow ops + per-backend profiles
├── _direct_crud.py # empty-tenancy MemoryCrudClient over the shared transport
├── _wire.py # custom-type request-body builders shared by the CRUD clients
├── _tag_syntax.py # client-side custom-type name + tag-syntax checks
├── _client.py # MemoryClient — internal HTTP client for the Memory Server API
└── _denylist.py # do-not-add dependency denylist (see below)

Memory 是传输无关的:它将工作流操作(record_turn、build_context、搜索、discover_procedures)委托给注入的 MemoryRuntime,并将特定类型的增删改查委托给注入的 MemoryCrudClient。公共表面通过 tests/test_package_contract.py 中的快照测试被冻结; MemoryClient 保持内部状态。每次调用租户(org_id,正文级 project_id)永远不会出现在公共签名中;公共表面上唯一的 project_id 是可选的构造函数路由形状选择器,它进入URL,而不是请求正文。

仅 pydantic + httpx 约束是强制的,而不是期望的。 _denylist.py 列出了已知的重度发行版和导入名称(langchain、fastapi、pymongo 等),如果导入包将其中任何一个拉取到 sys.modules 中,tests/test_package_contract.py 则会失败。

agent-engine-runner-shared 将此包声明为工作区依赖项,并在 agent_engine_runner_shared/memory.py 中构造内部 MemoryClient,将其执行上下文查找作为 execution_id_provider 可调用函数传递,以便按请求执行 ID 标头正常工作,而无需此包导入任何平台代码。

给本页内容打分