生产级智能体的难点,往往不在于完成一次推理,而在于同时承载大量生命周期不同的会话。Amazon Bedrock AgentCore 新运行时针对这一问题进行了调整:会话释放内存后,运行时能够回收这些资源;同时,即使容器镜像大小和并发水平发生变化,也能保持更一致的冷启动表现。
这两项能力直接影响容量规划、延迟尾部和运行成本,但“更一致”并不等于业务天然获得固定延迟。模型调用、工具链、网络连接和应用初始化仍然需要单独测量。
从常驻内存转向会话级弹性
智能体通常比普通 HTTP 接口拥有更复杂的状态:对话上下文、工具执行结果、临时文件以及检索数据都可能跟随会话存在。如果已结束会话占用的内存不能及时释放,服务会逐渐出现几个问题:
- 单个实例能够承载的活跃会话数下降;
- 突发流量更容易触发扩容;
- 长时间运行后,内存水位与实际活跃会话数不再匹配;
- 容量估算不得不为历史峰值保留较大余量。
新运行时会随着会话释放内存,使资源使用更贴近当前工作集。对团队而言,这意味着容量模型应从“启动多少个长期驻留进程”转向“当前有多少活跃会话,每个会话持有多少资源”。
不过,运行时能够回收会话内存,并不代表应用代码可以忽略资源生命周期。全局缓存、未关闭的文件、后台线程以及 SDK 连接池仍可能持续占用资源。应用层资源和运行时托管的会话资源应分开观察。
冷启动一致性比单次最快更有价值
在交互式智能体中,偶尔出现一次很慢的启动,通常比平均延迟略高更容易破坏体验。新运行时强调在不同镜像大小和并发条件下保持一致的冷启动,这有助于减少部署包增长或流量突发带来的延迟波动。
这里需要区分三段时间:
- 运行时启动时间:由 AgentCore 运行环境负责;
- 应用初始化时间:例如导入依赖、读取配置和创建客户端;
- 首次任务时间:包括模型推理、知识库检索和外部工具调用。
运行时优化主要解决第一段的可预测性。若应用在模块加载阶段下载模型、扫描大目录或同步连接多个外部系统,最终用户仍可能看到较高的首次响应延迟。因此,镜像大小可以不再是冷启动波动的主要变量,但应用初始化路径依旧值得精简。
用自己的工作负载验证冷启动与并发
公告没有给出适用于所有智能体的统一延迟数字,生产团队应使用自己的镜像、提示词、工具和模型做基准测试。下面的 Python 脚本会创建新的会话 ID,并发调用一个 HTTP 入口,统计成功率以及 p50、p95、p99 延迟。
示例假设接口接受 sessionId 和 input 字段。运行前请将它们改成实际 AgentCore 入口要求的请求格式,并按部署方式补充认证;如果入口不是 Bearer Token 认证,则需要替换脚本中的请求签名逻辑。
#!/usr/bin/env python3
import json
import os
import statistics
import time
import uuid
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
ENDPOINT = os.environ['AGENT_ENDPOINT']
TOKEN = os.getenv('AUTH_TOKEN')
REQUESTS = int(os.getenv('REQUESTS', '40'))
CONCURRENCY = int(os.getenv('CONCURRENCY', '10'))
TIMEOUT = float(os.getenv('TIMEOUT', '60'))
def invoke(index):
payload = json.dumps({
'sessionId': f'benchmark-{uuid.uuid4()}',
'input': f'Reply with pong. Request number: {index}'
}).encode('utf-8')
headers = {'Content-Type': 'application/json'}
if TOKEN:
headers['Authorization'] = f'Bearer {TOKEN}'
request = Request(ENDPOINT, data=payload, headers=headers, method='POST')
started = time.perf_counter()
try:
with urlopen(request, timeout=TIMEOUT) as response:
response.read()
elapsed_ms = (time.perf_counter() - started) * 1000
return response.status, elapsed_ms, None
except HTTPError as exc:
return exc.code, (time.perf_counter() - started) * 1000, str(exc)
except URLError as exc:
return 0, (time.perf_counter() - started) * 1000, str(exc)
def percentile(values, percentile_value):
ordered = sorted(values)
position = round((len(ordered) - 1) * percentile_value)
return ordered[position]
with ThreadPoolExecutor(max_workers=CONCURRENCY) as pool:
futures = [pool.submit(invoke, i) for i in range(REQUESTS)]
results = [future.result() for future in as_completed(futures)]
successful = [latency for status, latency, _ in results if 200 <= status < 300]
failed = [(status, error) for status, _, error in results if not 200 <= status < 300]
print(f'requests={REQUESTS} concurrency={CONCURRENCY}')
print(f'succeeded={len(successful)} failed={len(failed)}')
if successful:
print(f'mean_ms={statistics.mean(successful):.1f}')
print(f'p50_ms={percentile(successful, 0.50):.1f}')
print(f'p95_ms={percentile(successful, 0.95):.1f}')
print(f'p99_ms={percentile(successful, 0.99):.1f}')
if failed:
print('sample_failures=', failed[:5])
保存为 benchmark_agent.py 后运行:
export AGENT_ENDPOINT='https://your-agent-endpoint.example/invoke'
export AUTH_TOKEN='replace-if-required'
REQUESTS=20 CONCURRENCY=1 python3 benchmark_agent.py
REQUESTS=100 CONCURRENCY=10 python3 benchmark_agent.py
REQUESTS=200 CONCURRENCY=50 python3 benchmark_agent.py
每次请求都使用新的会话 ID,但这并不自动保证每次都是底层冷启动。要验证冷启动,应按照实际部署的空闲回收策略安排测试窗口,并结合服务端日志或指标确认实例确实经历了启动。建议分别测试小镜像与大镜像、低并发与高并发,并比较延迟分布,而不是只看平均值。
上线前值得确认的事项
采用新运行时时,可以按以下清单推进:
- 记录 p50、p95、p99,而不是只记录平均响应时间;
- 将运行时启动、应用初始化和首次模型调用分别打点;
- 观察会话结束后内存是否下降,并排查应用自身的全局缓存;
- 使用真实工具调用和检索链路测试,不要只发送空提示词;
- 在突发并发下同时检查延迟、错误率和限流情况;
- 不要因为运行时对镜像大小不敏感,就停止清理无用依赖和构建层。
AgentCore 新运行时提供的核心价值,是让智能体基础设施更贴近真实会话负载:会话结束后释放资源,扩展时获得更稳定的启动表现。团队仍需控制应用初始化和外部依赖,但容量规划不必再过度围绕镜像大小和不可预测的冷启动波动展开。