Build agent memory with NVIDIA NeMo Agent Toolkit and Amazon S3 Vectors

TL;DR · AI 摘要
Build agent memory with NVIDIA NeMo Agent Toolkit and Amazon S3 Vectors Artificial Intelligence Build agent memory with...
核心要点
- 主题聚焦:Build agent memory with NVIDIA NeMo Agent Toolki
- 来源:AWS Machine Learning Blog,建议结合原文判断细节。
- AI 分析暂不可用,本条为保底评分与摘要。
使用 NVIDIA NeMo Agent Toolkit 和 Amazon S3 Vectors 构建智能体内存 | 人工智能
使用 NVIDIA NeMo Agent Toolkit 和 Amazon S3 Vectors 构建智能体内存
在之前的博文《使用 Amazon S3 Vectors 为多智能体 AI 系统构建持久化内存》中,我们探讨了为什么内存工程是生产级多智能体系统的基石学科。我们展示了 Amazon S3 Vectors(Amazon Simple Storage Service 的一项功能)如何满足智能体内存的架构需求:语义检索、丰富元数据、强一致性以及弹性扩展。
在本文中,我们将从架构设计转向具体实现。我们将向您展示如何在 Amazon Elastic Kubernetes Service(Amazon EKS)上部署 NVIDIA NeMo Agent Toolkit(NAT),将 Amazon S3 Vectors 用作 NAT 的持久化内存层,从而实现完整的操作控制。
在本文结束时,您将理解 NAT 的内存子系统工作原理,并学会如何将 Amazon S3 Vectors 实现为自定义内存提供者。您还将学习如何在 Amazon EKS 上部署该技术栈,使用多智能体投资研究用例作为运行示例。
什么是 NVIDIA NeMo Agent Toolkit?
NVIDIA NeMo Agent Toolkit(NAT)是一个用于构建、分析和优化 AI 智能体的开源框架。它与具体框架无关,可与 Strands Agents、LangChain、LlamaIndex、CrewAI 及自定义实现配合使用。NAT 为生产级智能体系统提供以下四项关键能力:
- 智能体编排 – 将智能体定义为可组合的工作流,支持可配置的大语言模型(LLMs)、工具和提示。您可以使用
nat run本地运行,或通过nat serve作为持久化服务运行。
- 性能分析 – 跟踪智能体和单个工具的令牌使用量、延迟、吞吐量和运行时间,以识别多智能体工作流中的瓶颈。
- 评估 – 内置评估器用于答案准确性、上下文相关性、响应真实性以及智能体轨迹分析,支持自定义评估器。
- 优化 – 自动超参数调优(温度、top_p、max_tokens),在保证质量的同时最小化成本和延迟。
NAT 的内存模块
NAT 包含一个专用的内存子系统,用于存储和检索跨智能体调用的对话历史、用户偏好和长期知识。该内存模块具有可扩展性:通过实现 NAT 的插件接口,您可以创建自定义内存提供者(后端)。关键组件包括:
- MemoryEditor – 所有内存后端必须实现的抽象接口。它定义了三个方法:
add_items()、search()和remove_items()。
- MemoryItem – 表示内存片段的数据模型,包含对话历史、标签、元数据、user_id 字段以及一个可选的文本内存字符串。
- MemoryBaseConfig – 自定义内存配置扩展的 Pydantic 基类。NAT 通过 YAML 配置中的
_type字段发现提供者。
- 自动内存封装器 – 一种名为
auto_memory_agent的工作流类型,可自动封装智能体,实现内存捕获和检索,无需 LLM 显式调用内存工具。
目前 NAT 内置了这些内存提供者:Mem0、MemMachine、Redis 和 Zep。这些方案可覆盖常见用例。然而,对于需要弹性向量存储、强写一致性以及高效扩展至数十亿向量的生产级多智能体系统,基于 Amazon S3 Vectors 的自定义提供者是更优选择。
为什么选择Amazon S3 Vectors作为持久化内存后端
使用Amazon S3 Vectors构建多智能体AI系统的持久化内存,深入探讨了架构设计原理。下表总结了使S3 Vectors成为NAT内存层理想选择的特性:
| 需求 | S3 Vectors能力 | |------|----------------| | 语义检索 | 支持可配置的距离度量(余弦、欧几里得)的向量相似性搜索 | | 范围查询 | 每个向量可过滤的元数据(字符串、数字、布尔值、列表) | | 多智能体协作 | 强写一致性。插入后内存立即可见 | | 可扩展性 | 每个索引最多支持20亿个向量,无需容量规划 | | 成本效率 | 仅按存储、写入和查询计费,无需闲置计算资源 | | 访问控制 | 每个存储桶和索引的AWS IAM策略。按租户划分的索引实现硬隔离 |
先决条件
要跟随本文的实现步骤,您需要:
- 具有创建Amazon S3 Vectors资源和Amazon EKS集群权限的AWS账户。
- 现有的Amazon EKS集群(参见Amazon EKS入门指南)。
- 安装NVIDIA NeMo Agent Toolkit(已测试版本1.6)。
- Python 3.11或3.12(NAT要求>=3.11,<3.14)。
- 用于生成向量表示的嵌入模型(本文使用Amazon Titan Text Embeddings V2)。
- 用于容器和集群操作的kubectl和Docker。
将S3 Vectors实现为NAT内存提供者
实现包含三个步骤:
步骤1:创建S3 Vectors基础设施 步骤2:实现自定义MemoryEditor插件 步骤3:配置代理工作流
图1:将Amazon S3 Vectors实现为NAT内存提供者的三个步骤
步骤1:创建Amazon S3 Vectors基础设施
以下代码创建了一个向量存储桶和一个专为智能体内存设计的元数据架构的索引。该索引使用1024个维度以匹配Amazon Titan Text Embeddings V2的输出,并将大内容字段标记为不可过滤元数据:
import boto3
REGION = "us-west-2"
VECTOR_BUCKET = "amzn-s3-demo-research-agent-memory"
INDEX_NAME = "agent-long-term-memory"
s3vectors = boto3.client("s3vectors", region_name=REGION)
# 创建向量存储桶
s3vectors.create_vector_bucket(vectorBucketName=VECTOR_BUCKET)
# 创建索引(1024维度匹配Amazon Titan Text Embeddings V2)
s3vectors.create_index(
vectorBucketName=VECTOR_BUCKET,
indexName=INDEX_NAME,
dataType="float32",
dimension=1024,
distanceMetric="cosine",
metadataConfiguration={"nonFilterableMetadataKeys": ["content"]},
)创建存储桶和索引后,您可以实现读取和写入它们的内存提供者。
步骤2:实现自定义MemoryEditor插件
以下代码将MemoryEditor接口实现为Amazon S3 Vectors后端,并进行注册以便NAT可以发现它:
import boto3
import json
import uuid
from datetime import datetime, timezone
from nat.plugin_api import MemoryBaseConfig, MemoryEditor, MemoryItem, register_memory
from nat.builder.builder import Builder
class S3VectorsMemoryConfig(MemoryBaseConfig, name="s3vectors_memory"):
"""Amazon S3 Vectors的NAT内存提供者配置。"""
vector_bucket: str
index_name: str
aws_region: str = "us-west-2"
default_top_k: int = 5
class S3VectorsMemoryEditor(MemoryEditor):
"""由Amazon S3 Vectors支持的NAT MemoryEditor。"""def __init__(self, config: S3VectorsMemoryConfig):
self._vector_bucket = config.vector_bucket
self._index_name = config.index_name
self._default_top_k = config.default_top_k
self._s3vectors = boto3.client('s3vectors', region_name=config.aws_region)
self._bedrock = boto3.client('bedrock-runtime', region_name=config.aws_region)
def _get_embedding(self, text: str) -> list[float]:
"""使用Amazon Titan Text Embeddings V2生成嵌入向量。"""
response = self._bedrock.invoke_model(
modelId='amazon.titan-embed-text-v2:0',
contentType='application/json',
accept='application/json',
body=json.dumps({
'inputText': text,
'dimensions': 1024,
'normalize': True
})
)
return json.loads(response['body'].read())['embedding']
async def add_items(self, items: list[MemoryItem], **kwargs) -> None:
"""将记忆项作为向量存储在Amazon S3 Vectors中。"""
vectors = []
for item in items:
# 从记忆内容构建要嵌入的文本
text = item.memory or json.dumps(item.conversation)
embedding = self._get_embedding(text)
# 使用uuid4防止同一秒内出现键冲突
key = f"mem_{item.user_id}_{uuid.uuid4().hex[:12]}"
# 注意:S3 Vectors元数据值有大小限制
# 在生产环境中,将内容截断为1024个字符
content_for_metadata = text[:1024]
mem_metadata = {
'user_id': item.user_id,
'memory_type': item.metadata.get('memory_type', 'episodic'),
'agent_id': item.metadata.get('agent_id', ''),
'team_id': item.metadata.get('team_id', ''),
'task_id': item.metadata.get('task_id', ''),
'confidence': item.metadata.get('confidence', 0.8),
'created_at_epoch': int(datetime.now(timezone.utc).timestamp()),
'is_shared': item.metadata.get('is_shared', True),
'source': item.metadata.get('source', 'agent'),
'content': content_for_metadata,
}
# 如果存在,添加领域特定元数据
if 'ticker' in item.metadata:
mem_metadata['ticker'] = item.metadata['ticker']
vectors.append({
'key': key,
'data': {'float32': embedding},
'metadata': mem_metadata
})
self._s3vectors.put_vectors(
vectorBucketName=self._vector_bucket,
indexName=self._index_name,
vectors=vectors
) async def search(self, query: str, top_k: int = None, **kwargs) -> list[MemoryItem]:
"""从 Amazon S3 Vectors 中检索语义相关记忆"""
query_embedding = self._get_embedding(query)
effective_top_k = top_k or self._default_top_k
# 从 kwargs 构建元数据过滤器
filter_expr = {}
for field in ('agent_id', 'memory_type', 'ticker', 'team_id', 'user_id'):
if field in kwargs:
filter_expr[field] = {'$eq': kwargs[field]}
# 支持 is_shared 的布尔过滤
if 'is_shared' in kwargs:
filter_expr['is_shared'] = {'$eq': kwargs['is_shared']}
response = self._s3vectors.query_vectors(
vectorBucketName=self._vector_bucket,
indexName=self._index_name,
queryVector={'float32': query_embedding},
topK=effective_top_k,
filter=filter_expr if filter_expr else None,
returnMetadata=True
)
results = []
for vec in response.get('vectors', []):
results.append(MemoryItem(
conversation=[],
tags=[vec['metadata'].get('memory_type', '')],
metadata=vec['metadata'],
user_id=vec['metadata'].get('user_id', ''),
memory=vec['metadata'].get('content', '')
))
return results
async def remove_items(self, **kwargs) -> None:
"""从 Amazon S3 Vectors 中移除记忆项"""
keys = kwargs.get('keys', [])
if keys:
self._s3vectors.delete_vectors(
vectorBucketName=self._vector_bucket,
indexName=self._index_name,
keys=keys
)
# 注册记忆提供者以便 NAT 可以发现它
@register_memory(config_type=S3VectorsMemoryConfig)
async def build_s3vectors_memory(config: S3VectorsMemoryConfig, builder: Builder):
yield S3VectorsMemoryEditor(config)该插件使用 Amazon Titan Text Embeddings V2 生成嵌入向量,将每条记忆作为带作用域元数据的向量进行存储,并将搜索过滤器转换为 Amazon S3 Vectors 元数据查询。
第3步:配置 NAT 代理工作流
在定义插件后,需要在 NAT 的 YAML 配置中进行配置,并将其集成到代理工作流中:
# config.yml - 带 Amazon S3 Vectors 记忆的 NAT 代理配置
memory:
agent_memory:
_type: s3vectors_memory
vector_bucket: "amzn-s3-demo-research-agent-memory"
index_name: "agent-long-term-memory"
aws_region: "us-west-2"
functions:
add_memory:
_type: add_memory
memory: agent_memory
description: |
存储重要的发现、模式或事实到长期记忆中。
在研究过程中发现新信息后使用此功能。
get_memory:
_type: get_memory
memory: agent_memory
description: |
在开始研究前检索相关先验知识。
使用当前研究主题进行查询以回忆相关发现。
web_search:
_type: web_search
financial_data:
_type: financial_data_api
workflow:
_type: auto_memory_agent
inner_agent_name: research_agent
memory_name: agent_memory
llm_name: bedrock_llm
save_user_messages_to_memory: true
retrieve_memory_for_every_response: true
save_ai_messages_to_memory: true
llm:
bedrock_llm:
_type: bedrock
model_id: "anthropic.claude-sonnet-4-20250514"
temperature: 0.3使用 auto_memory_agent 包装器,您可以自动捕获和检索记忆。用户消息和代理响应会被存储,并在每次代理调用前注入相关上下文。这种设计消除了大语言模型(LLM)显式调用记忆工具的需要。
负责任的AI与数据处理考量
由于该解决方案会持久化对话历史和用户记忆,请规划数据的存储和访问方式。为存储的记忆设置保留策略,并使用本文档中描述的记忆整合和清除路径来过期不再需要的数据。避免在记忆元数据中存储个人可识别信息(PII),在嵌入前对敏感字段进行脱敏或标记化处理。通过租户级索引和最小权限IAM策略限制访问范围,确保每个代理仅读写属于自己的记忆。
亚马逊S3向量的实际应用:多代理投资研究
以下用例展示了该模式的实际应用。考虑三个专业代理在投资研究工具上的协作:
- 研究代理 – 收集市场数据、财报和新闻
- 分析代理 – 执行量化分析并识别模式
- 综合代理 – 整合两个代理的发现生成报告
通过持久化记忆,每个代理都能基于先前工作进行构建。研究代理可回忆之前收集的数据,避免重复的API调用。分析代理可基于先前会话识别的模式进行分析。综合代理可访问累积发现,生成内容更丰富的报告。
多代理记忆协调
每个代理使用相同的S3向量索引,但通过元数据中的agent_id进行区分。代理通过元数据过滤器检索共享知识:
# 分析代理检索研究代理的共享发现
research_findings = await memory_client.search(
query=f"Recent research findings for {ticker}",
top_k=10,
team_id='investment-research',
is_shared=True
)
# 综合代理查询所有团队知识
all_team_knowledge = await memory_client.search(
query=f"Complete analysis and research for {ticker} Q2 2026",
top_k=20,
team_id='investment-research'
)NAT内置的多租户记忆隔离通过user_id实现按用户划分记忆。对于多代理团队协作,team_id元数据字段在相同S3向量索引内提供了额外的分组维度。
记忆整合
随着时间推移,情景记忆会不断累积。定期将它们整合为语义记忆(通用知识)可保持检索的精确性和效率。您可以通过计划任务、向量数量阈值或在可配置数量的研究周期后由代理发起信号来触发整合:
async def consolidate_memories(ticker: str, memory_client, workflow):
"""将情景记忆提炼为持久的语义知识。"""
episodes = await memory_client.search(
query=f"All findings about {ticker}",
top_k=50,
memory_type='episodic',
ticker=ticker
)
episode_texts = [e.memory for e in episodes if e.memory]
consolidation_prompt = f"""根据以下关于{ticker}的{len(episode_texts)}个观察结果
识别持久模式和通用知识。
{chr(10).join(episode_texts)}
返回一个包含洞察字符串的 JSON 数组。不要包含具体日期或一次性事件。""" response = await workflow.run(consolidation_prompt)
将响应解析为洞察列表
insights = json.loads(response)
将每个整合后的洞察存储为语义记忆
await memory_client.add_items([ MemoryItem( conversation=[], tags=['semantic'], metadata={ 'memory_type': 'semantic', 'confidence': 0.9, 'ticker': ticker, 'source': 'consolidation', 'is_shared': True, 'team_id': 'investment-research' }, user_id='system', memory=insight ) for insight in insights ])
## 在 Amazon EKS 上部署代理工作负载
对于需要对代理工作负载进行完全操作控制的团队来说,Amazon EKS 是一个明确的选择。使用 Amazon EKS,您可以控制扩展、网络和生命周期管理,并且可以与 AWS Identity and Access Management (IAM) 本机集成以访问 Amazon S3 向量。如果您更倾向于完全托管的运行时,Amazon Bedrock AgentCore 提供了一个托管替代方案,可以消除这种操作开销。您使用 nat serve 将 NAT 代理作为容器化服务运行。每种代理类型都是一个 Kubernetes 部署,通过服务账户的 IAM 角色(IRSA)授予 IAM 访问权限。
### 步骤 1:构建代理容器
以下 Dockerfile 将代理与其配置和内存插件打包在一起:
FROM python:3.12-slim RUN pip install nvidia-nat[langchain] boto3 COPY config.yml /app/config.yml COPY s3vectors_memory.py /app/s3vectors_memory.py WORKDIR /app CMD ["nat", "serve", "--config_file", "config.yml"]
### 步骤 2:使用 Kubernetes 清单部署
以下清单使用自动扩展部署研究代理:
apiVersion: apps/v1 kind: Deployment metadata: name: research-agent namespace: agent-team spec: replicas: 2 selector: matchLabels: app: research-agent template: metadata: labels: app: research-agent team: investment-research spec: serviceAccountName: agent-sa containers:
- name: agent
image: ${ECR_REGISTRY}/research-agent:latest # Amazon 弹性容器注册表(Amazon ECR) ports:
- containerPort: 8000
env:
- name: VECTOR_BUCKET
value: "amzn-s3-demo-research-agent-memory"
- name: INDEX_NAME
value: "agent-long-term-memory"
- name: AWS_REGION
value: "us-west-2" resources: requests: cpu: "500m" memory: "1Gi" limits: cpu: "2000m" memory: "4Gi"
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: research-agent-hpa namespace: agent-team spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: research-agent minReplicas: 1 maxReplicas: 10 metrics:
- type: Resource
resource: name: cpu target: type: Utilization averageUtilization: 70
### S3 向量访问的 IAM 策略
以下 IAM 策略授予对内存层的限定访问权限。通过 IRSA 将其附加到与 agent-sa 服务账户关联的 IAM 角色:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3vectors:PutVectors", "s3vectors:QueryVectors", "s3vectors:GetVectors", "s3vectors:DeleteVectors" ], "Resource": "arn:aws:s3vectors:us-west-2:${ACCOUNT_ID}:vector-bucket/amzn-s3-demo-research-agent-memory/*" } ] }
### 部署与验证
构建并推送容器镜像后,部署代理:
创建命名空间
kubectl create namespace agent-team
应用清单文件
kubectl apply -f kubernetes/research-agent-deployment.yaml
验证Pod是否运行
kubectl get pods -n agent-team
测试代理端点
kubectl port-forward -n agent-team svc/research-agent 8000:8000 curl -X POST http://localhost:8000/generate \ -H "Content-Type: application/json" \ -d '{"inputs": "What are the latest AAPL earnings trends?"}'
S3 Vectors 提供强写一致性。Pod 和代理类型可以立即看到代理 Pod 存储的记忆,无需缓存失效。HorizontalPodAutoscaler 根据 CPU 使用情况扩展代理副本。每个副本通过 IRSA 连接到相同的 S3 Vectors 索引,无论哪个 Pod 处理请求,均可实现一致的记忆访问。
## 评估记忆影响
使用 NAT 的评估工具(nat eval),可以量化记忆对代理性能的影响。使用相同的数据集配置两次评估运行:一次启用记忆,一次不启用:
启用记忆进行评估
nat eval --config_file config_with_memory.yml \ --dataset eval_dataset.jsonl \ --metrics accuracy,groundedness,token_usage,latency
不启用记忆(基准测试)
nat eval --config_file config_no_memory.yml \ --dataset eval_dataset.jsonl \ --metrics accuracy,groundedness,token_usage,latency
基于该设计的架构特性,比较这些运行时可以预期以下定性结果。这些是方向性预期,而非基准测量:
- 可靠性提高 – 回忆的记忆为代理提供了可验证的来源上下文,代理可以引用这些内容,而不仅仅是依赖参数化知识。
- 令牌使用量减少 – 代理跳过重新推导已在先前会话中确定的事实。减少的程度取决于工作流中重复上下文的数量。
- 延迟适度增加 – 每次记忆回忆都会增加一个 S3 Vectors 查询(标准访问模式下为亚秒级),通常只占整体 LLM 推理时间的一小部分。
- 重复工作减少 – 通过共享记忆,代理可以避免重复已完成的队友的研究或分析。
这些影响的幅度取决于您的工作负载:会话之间重复的上下文数量、协调的代理数量以及检索预算(top-k)的大小。NAT 的超参数优化器可以系统地调整 top_k 和相似度阈值,以找到最适合您用例的成本-质量平衡点。
## 清理资源
为了避免本演练中创建的资源产生持续费用,请删除以下内容:
- 删除 Amazon EKS 部署及相关资源:kubectl delete namespace agent-team- 删除 S3 向量索引和向量存储桶:s3vectors.delete_index(vectorBucketName='amzn-s3-demo-research-agent-memory', indexName='agent-long-term-memory') s3vectors.delete_vector_bucket(vectorBucketName='amzn-s3-demo-research-agent-memory')
- 如果不再需要,删除为 IRSA 创建的 IAM 角色和策略。
结论
本文展示了如何使用 Amazon S3 向量实现自定义 NAT 内存提供程序,并在 Amazon EKS 上进行部署。关键构建模块包括:
- NAT 的 MemoryEditor 接口为自定义内存后端提供了插件契约。
- auto_memory_agent 封装器自动处理捕获和检索操作。
- Amazon S3 向量提供持久化、语义可查询的后端,支持多代理协调的强一致性。
- Amazon EKS 提供对代理生命周期、扩展和网络隔离的完全操作控制。
这些模式适用于代理能从累积知识中获益的领域,包括将内存分类为情景记忆、语义记忆或程序性记忆,通过元数据过滤在代理间共享内存,以及使用大语言模型(LLM)整合内存。应用场景包括客户支持、DevOps 自动化、法律研究和科学发现。
如需了解本文提及的内存架构基础,请参阅《使用 Amazon S3 向量为多代理 AI 系统构建持久化内存》。要开始使用 NAT,请参阅 NVIDIA NeMo Agent 工具包文档和《添加内存提供程序》指南。
关于作者
'"`