跨 Region GPU 动态调度系统,基于 Strands Agent SDK + Bedrock Claude 构建。
系统解决 AWS GPU 实例(G5/G6/G6e)启动时 InsufficientInstanceCapacity 容量不足问题,通过 Probe-and-Fill 策略在多个候选 Region 间按就近优先级逐个尝试启动,直到满足需求或候选耗尽。
- 本地开发 — 环境搭建、运行测试
- AgentCore 部署指南 — 详细部署文档
- 运维手册 — 日常运维、监控、故障排查
- 审批机制 — Human-in-the-loop 审批说明
用户输入 → Controller Agent (Strands/Bedrock Claude)
↓
Orchestrator (状态机循环)
↓
Tools (EC2 Launch / Describe / Delete / DynamoDB / Offerings)
↓
Agent State (plan / remaining / cursor / results)
核心策略 Probe-and-Fill:
- 分批启动(chunked launch,默认 batch_max=4)
- 二分退让(InsufficientCapacity 时 batch 减半)
- 多 AZ/Subnet 轮转
- 跨 Region 逐级回退
# 安装依赖
pip install -e ".[dev]"
# 运行测试
python -m pytest tests/ -v
# 部署到 AgentCore(dev 环境)
./scripts/deploy_agentcore.sh dev| 工具 | 安装方式 |
|---|---|
| Python 3.11+ | https://www.python.org/downloads/ |
| AWS CLI 2.x | brew install awscli |
| AgentCore CLI | pip install bedrock-agentcore-starter-toolkit |
注意:AgentCore CLI 的包名是
bedrock-agentcore-starter-toolkit,不是bedrock-agentcore(后者只是 SDK)。
# 1. 配置 AgentCore(指定入口文件、区域、运行时)
agentcore configure \
--entrypoint agent_entrypoint.py \
--region us-west-2 \
--runtime PYTHON_3_12 \
--disable-memory \
--non-interactive
# 2. 创建 Memory 资源(可选,用于保存对话历史)
agentcore memory create gpu_scheduler_memory \
--region us-west-2 \
--event-expiry-days 30 \
--wait
# 3. 部署启动(带 Memory)
agentcore launch \
--env SCHEDULER_ENV=dev \
--env SSM_PARAMETER=/gpu-scheduler/dev/regions \
--env DYNAMODB_TABLE=GpuProvisioningInstances-dev \
--env BEDROCK_MODEL_ID=us.anthropic.claude-sonnet-4-20250514-v1:0 \
--env BEDROCK_REGION=us-west-2 \
--env AUTH_SECRET_KEY=<your-jwt-secret> \
--env MEMORY_ID=gpu_scheduler_memory-1az3i38LW2 \
--env MEMORY_REGION=us-west-2Memory 名称只能包含字母、数字和下划线,不能用连字符。 如果不需要 Memory,省略
MEMORY_ID和MEMORY_REGION环境变量即可。
代码修改后,使用 --auto-update-on-conflict 避免冲突:
agentcore launch --auto-update-on-conflict \
--env SCHEDULER_ENV=dev \
--env SSM_PARAMETER=/gpu-scheduler/dev/regions \
--env DYNAMODB_TABLE=GpuProvisioningInstances-dev \
--env BEDROCK_MODEL_ID=us.anthropic.claude-sonnet-4-20250514-v1:0 \
--env BEDROCK_REGION=us-west-2 \
--env AUTH_SECRET_KEY=<your-jwt-secret> \
--env MEMORY_ID=gpu_scheduler_memory-1az3i38LW2 \
--env MEMORY_REGION=us-west-2AgentCore 自动创建执行角色(如 AmazonBedrockAgentCoreSDKRuntime-us-west-2-xxxxxxxx),需要手动添加业务权限:
aws iam put-role-policy \
--role-name <AgentCore执行角色名> \
--policy-name GpuSchedulerResourcesPolicy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EC2Operations",
"Effect": "Allow",
"Action": [
"ec2:RunInstances",
"ec2:DescribeInstances",
"ec2:TerminateInstances",
"ec2:DescribeInstanceTypeOfferings",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:CreateTags"
],
"Resource": "*"
},
{
"Sid": "DynamoDB",
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:GetItem",
"dynamodb:BatchWriteItem",
"dynamodb:DescribeTable"
],
"Resource": "arn:aws:dynamodb:*:<account-id>:table/GpuProvisioningInstances-*"
},
{
"Sid": "SSM",
"Effect": "Allow",
"Action": ["ssm:GetParameter"],
"Resource": "arn:aws:ssm:*:<account-id>:parameter/gpu-scheduler/*"
},
{
"Sid": "AgentCoreMemory",
"Effect": "Allow",
"Action": [
"bedrock-agentcore:CreateEvent",
"bedrock-agentcore:ListEvents",
"bedrock-agentcore:GetMemory",
"bedrock-agentcore:RetrieveMemories",
"bedrock-agentcore:SearchMemories",
"bedrock-agentcore:ListBranches",
"bedrock-agentcore:ListBranchEvents"
],
"Resource": "arn:aws:bedrock-agentcore:<region>:<account-id>:memory/<memory-id>"
}
]
}'系统使用 HS256 JWT 进行认证,Token 中包含 user_id、username、roles 字段,由 AUTH_SECRET_KEY 签名。src/agent/auth.py 中的 validate_token() 负责验证。
使用 scripts/generate_token.py 脚本生成:
# 设置密钥(必须与 agentcore launch 时的 AUTH_SECRET_KEY 一致)
export AUTH_SECRET_KEY="your-jwt-secret"
# 使用默认值生成(user_id=default_user,有效期 24 小时)
python scripts/generate_token.py
# 自定义用户信息和有效期
python scripts/generate_token.py --user-id admin01 --username "Li Jing" --roles admin,operator --expires 72
# 也可以直接通过 --secret 参数指定密钥
python scripts/generate_token.py --secret your-jwt-secret --user-id test_user
# 纯 token 输出(方便赋值给变量或脚本调用)
TOKEN=$(python scripts/generate_token.py --raw)参数说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
--user-id |
default_user |
用户 ID,对应 Token 中的 user_id / sub |
--username |
Default User |
用户名 |
--roles |
operator |
角色列表,逗号分隔(如 admin,operator) |
--expires |
24 |
有效期(小时) |
--secret |
读 AUTH_SECRET_KEY 环境变量 |
HMAC 签名密钥 |
--raw |
否 | 只输出 token 字符串,不输出其他信息 |
注意:
--secret或AUTH_SECRET_KEY必须与agentcore launch --env AUTH_SECRET_KEY=...中设置的值一致,否则 Agent 会拒绝该 Token。
查询实例:
agentcore invoke '{"prompt": "查询当前所有运行中的GPU实例", "token": "<token>"}'启动实例:
agentcore invoke '{"prompt": "在东京区域启动2台g6.xlarge GPU实例", "token": "<token>"}'删除实例(两步审批流程):
# 第一步:发起删除请求,返回 approval_required + interrupt_id
agentcore invoke '{"prompt": "请删除所有当前正在运行的GPU实例", "token": "<token>"}'
# 第二步:用返回的 interrupt_id 确认删除
agentcore invoke '{"approval_responses":[{"interrupt_id":"<返回的interrupt_id>","decision":"approved"}],"token":"<token>"}'查看日志:
# 实时日志
aws logs tail /aws/bedrock-agentcore/runtimes/<agent-name>-DEFAULT \
--log-stream-name-prefix "$(date +%Y/%m/%d)/[runtime-logs" --follow
# 最近1小时日志
aws logs tail /aws/bedrock-agentcore/runtimes/<agent-name>-DEFAULT \
--log-stream-name-prefix "$(date +%Y/%m/%d)/[runtime-logs" --since 1h# 名称只能包含字母、数字、下划线,不能用连字符
agentcore memory create gpu_scheduler_memory \
--region us-west-2 \
--event-expiry-days 30 \
--wait创建完成后返回 memory_id(如 gpu_scheduler_memory-1az3i38LW2)。
# 查看 Memory 状态
agentcore memory get <memory-id> --region us-west-2
# 列出所有 Memory 资源
agentcore memory list
# 删除 Memory(慎用,不可恢复)
agentcore memory delete <memory-id> --wait使用 scripts/query_memory.py 脚本查询:
# 列出账号下所有 Memory 资源
python scripts/query_memory.py list
# 查询指定 session 的对话记录(user 和 session 都是必填的)
python scripts/query_memory.py events --user test_user --session <session_id>
# 限制返回数量
python scripts/query_memory.py events --user test_user --session <session_id> --limit 10
session_id就是每次agentcore invoke返回的 Session 值。list_eventsAPI 要求actor_id和session_id都是必填参数。
以下是从首次部署到功能完全可用过程中遇到的问题及解决方案:
问题:pip install bedrock-agentcore 安装的是 SDK,不包含 agentcore CLI 命令。
解决:正确的包名是 bedrock-agentcore-starter-toolkit:
pip install bedrock-agentcore-starter-toolkit问题:strands-agents-tools 包引入了大量不需要的依赖(sympy、pillow 等),导致部署包过大且可能引发兼容性问题。
解决:从 requirements.txt 中移除 strands-agents-tools。项目源码中并未直接使用该包的工具,所有工具都是自定义的 @tool 函数。
问题:DynamoDB 表部署在 ap-northeast-1,但 IAM policy 的 Resource 只限定了 us-west-2 区域的表,导致跨区域访问被拒绝。
解决:将 DynamoDB 权限的 Resource 改为 arn:aws:dynamodb:*:<account-id>:table/GpuProvisioningInstances-*,使用 * 通配所有区域。
问题:删除实例后,更新 DynamoDB 记录状态为 terminated 时失败。IAM policy 中有 PutItem/Query/Scan 但缺少 UpdateItem。
解决:在 IAM policy 的 DynamoDB Statement 中添加 dynamodb:UpdateItem 和 dynamodb:DeleteItem。
问题:删除操作的两步审批流程中,第一步返回 approval_required,第二步发送 approval_responses 时,Agent 返回"删除操作已取消"而非执行删除。
根因:Strands SDK 的 _InterruptState 在 AgentCore 的两次 HTTP 请求之间可能丢失 activated=True 状态。resume() 方法检查 self.activated,如果为 False 则直接跳过,导致审批响应无法传递到工具。
解决:在 agent_entrypoint.py 中实现中断状态的保存/恢复机制:
- 返回
approval_required时,将agent._interrupt_state.to_dict()序列化保存到_session_interrupt_cache - 收到
approval_responses时,检查 agent 的_interrupt_state.activated,如果为False,从缓存中用_InterruptState.from_dict()恢复 - 如果状态仍然存活(
activated=True),跳过恢复,走正常 resume 流程
问题:delete tool 检查 str(approval).strip().lower() not in ("y", "yes"),但 API 传入的是 decision: "approved"。
解决:在 entrypoint 中将 decision=="approved" 映射为 "y",decision!="approved" 映射为 "n"。
问题:agentcore memory create gpu-scheduler-memory 报 ValidationException,名称不符合正则 [a-zA-Z][a-zA-Z0-9_]{0,47}。
解决:Memory 名称只能用字母、数字和下划线,改为 gpu_scheduler_memory:
agentcore memory create gpu_scheduler_memory --region us-west-2 --event-expiry-days 30 --wait问题:启用 Memory 后,对话记录未保存。CloudWatch 日志显示 AccessDeniedException: bedrock-agentcore:CreateEvent。
根因:IAM policy 中使用了错误的 action 名称(如 CreateMemoryEvent),实际 API 使用的是 CreateEvent、ListEvents 等不带 Memory 前缀的名称。
解决:IAM policy 中 Memory 相关的 action 应为:
{
"Sid": "AgentCoreMemory",
"Effect": "Allow",
"Action": [
"bedrock-agentcore:CreateEvent",
"bedrock-agentcore:ListEvents",
"bedrock-agentcore:GetMemory",
"bedrock-agentcore:RetrieveMemories",
"bedrock-agentcore:SearchMemories",
"bedrock-agentcore:ListBranches",
"bedrock-agentcore:ListBranchEvents"
],
"Resource": "arn:aws:bedrock-agentcore:<region>:<account-id>:memory/<memory-id>"
}排查方法:查看 CloudWatch 日志中的 AccessDeniedException 错误,日志会明确指出缺少哪个 action:
aws logs tail /aws/bedrock-agentcore/runtimes/<agent-name>-DEFAULT \
--log-stream-name-prefix "$(date +%Y/%m/%d)/[runtime-logs" --since 10m \
--format short | grep -i "denied\|error\|failed"问题:调用 MemoryClient.list_events() 时不传 actor_id 或 session_id 会报 TypeError。
解决:这两个参数都是必填的,无法只按 user 或只按 session 查询。查询时需要同时提供:
python scripts/query_memory.py events --user test_user --session <session_id>session_id 来自 agentcore invoke 返回结果中的 session_id 字段。