👻

Amazon Bedrock AgentCore が GA したので人間の実働 3 分で Runtime を動かす

に公開
3

本ブログはAWS AI Agent ブログ祭り(Zenn: #awsaiagentblogfes, X: #AWS_AI_AGENT_ブログ祭り)の第 1 日目です。

Amazon Bedrock AgentCore is Generally Available

出ました。
https://aws.amazon.com/jp/blogs/machine-learning/amazon-bedrock-agentcore-is-now-generally-available/

AgentCore 今北産業(個人のお気持ち)

  • AI エージェントを本番環境で利用しようとすると、AI Agent のホストだったり、会話履歴の管理だったり、認証だったり…大変です!
  • Agent そのもの以外は AgentCore にオフロードしようぜ!
  • だから開発者は AI エージェントの開発に注力しようぜ!

この記事は?

Agent Runtime というエージェントをホストするサービスを 3 分で試すものです。とりあえず動かす方法がわかれば、動いているエージェントを改変することで独自のエージェントを作れるようになります。
また番外編として、Cognito を用いた Bearer トークンを用いた Runtime の呼び出し方法も紹介します。

前提

  • CDK が使えること(早くもチートのかほり)
    • 当然クレデンシャル、ポリシー、などはちゃんと設定されてるよね?

やってみよう

CDK リポジトリの初期化

まずは CDK リポジトリの初期化をします。

npm install -g aws-cdk@latest # 最新版を改めてインストールする
mkdir cdk-agentcore-runtime && cd cdk-agentcore-runtime
cdk init --language typescript

CDK のコードを書く

デプロイコマンド一発で動かせるように CDK のコードを書きます。
AgentCore Runtime は Graviton で動くため Arm アーキテクチャーのコンピューティングリソースで動かす必要があります。今回は CodeBuild の Graviton を使っています。

lib/cdk-agentcore-runtime-stack.ts(すでにファイルがあるはずなので修正する)
import * as cdk from 'aws-cdk-lib';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';

export class CdkAgentcoreRuntimeStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ECR Repository
    const ecrRepository = new ecr.Repository(this, 'ECRRepository', {
      repositoryName: 'cdk-agentcore-runtime',
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      emptyOnDelete: true
    });

    // CodeBuild Service Role
    const codeBuildRole = new iam.Role(this, 'CodeBuildRole', {
      assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
      inlinePolicies: {
        CodeBuildPolicy: new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: ['logs:*', 'ecr:*', 's3:GetObject'],
              resources: ['*']
            })
          ]
        })
      }
    });

    // S3 Bucket for source files
    const sourceBucket = new s3.Bucket(this, 'SourceBucket', {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true
    });

    // Deploy assets to S3
    const deployAssets = new s3deploy.BucketDeployment(this, 'DeployAssets', {
      sources: [s3deploy.Source.asset('./assets')],
      destinationBucket: sourceBucket
    });

    // CodeBuild Project
    const buildProject = new codebuild.Project(this, 'BuildProject', {
      role: codeBuildRole,
      environment: {
        buildImage: codebuild.LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_3_0,
        computeType: codebuild.ComputeType.SMALL,
        privileged: true,
        environmentVariables: {
          AWS_DEFAULT_REGION: { value: this.region },
          AWS_ACCOUNT_ID: { value: this.account },
          IMAGE_REPO_NAME: { value: ecrRepository.repositoryName },
          SOURCE_BUCKET: { value: sourceBucket.bucketName }
        }
      },
      source: codebuild.Source.s3({
        bucket: sourceBucket,
        path: 'buildspec.yml'
      })
    });

    // Lambda execution role
    const lambdaRole = new iam.Role(this, 'LambdaRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')
      ],
      inlinePolicies: {
        CodeBuildAccess: new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: ['codebuild:*'],
              resources: ['*']
            })
          ]
        })
      }
    });

    // Lambda function to trigger build
    const buildTrigger = new lambda.Function(this, 'BuildTrigger', {
      runtime: lambda.Runtime.PYTHON_3_12,
      handler: 'index.handler',
      role: lambdaRole,
      timeout: cdk.Duration.minutes(15),
      memorySize: 256,
      code: lambda.Code.fromInline(`
import boto3
import cfnresponse
import time
import traceback

def handler(event, context):
    print(f'Event: {event}')
    try:
        if event['RequestType'] == 'Create':
            codebuild = boto3.client('codebuild')
            project_name = event['ResourceProperties']['ProjectName']
            print(f'Starting build for project: {project_name}')
            
            response = codebuild.start_build(projectName=project_name)
            build_id = response['build']['id']
            print(f'Build started with ID: {build_id}')
            
            status = 'IN_PROGRESS'
            for attempt in range(30):
                build_status = codebuild.batch_get_builds(ids=[build_id])
                status = build_status['builds'][0]['buildStatus']
                print(f'Attempt {attempt + 1}: Build status is {status}')
                
                if status in ['SUCCEEDED', 'FAILED', 'FAULT', 'STOPPED', 'TIMED_OUT']:
                    break
                time.sleep(30)
            
            if status == 'SUCCEEDED':
                print('Build succeeded')
                cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
            else:
                print(f'Build failed with status: {status}')
                cfnresponse.send(event, context, cfnresponse.FAILED, {})
        else:
            cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
    except Exception as e:
        print(f'Error: {str(e)}')
        print(f'Traceback: {traceback.format_exc()}')
        cfnresponse.send(event, context, cfnresponse.FAILED, {})
      `)
    });

    // Trigger build
    const triggerBuildResource = new cdk.CustomResource(this, 'TriggerBuild', {
      serviceToken: buildTrigger.functionArn,
      properties: {
        ProjectName: buildProject.projectName
      }
    });
    
    triggerBuildResource.node.addDependency(deployAssets);

    // Runtime Role
    const runtimeRole = new iam.Role(this, 'RuntimeRole', {
      assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'),
      inlinePolicies: {
        RuntimePolicy: new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: ['logs:*'],
              resources: ['*']
            }),
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                'ecr:GetAuthorizationToken',
                'ecr:BatchGetImage',
                'ecr:GetDownloadUrlForLayer'
              ],
              resources: ['*']
            }),
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: ['bedrock:*'],
              resources: ['*']
            })
          ]
        })
      }
    });

    // AgentCore Runtime
    const agentRuntime = new cdk.CfnResource(this, 'AgentRuntime', {
      type: 'AWS::BedrockAgentCore::Runtime',
      properties: {
        AgentRuntimeName: 'cdk_bedrock_agent_runtime',
        RoleArn: runtimeRole.roleArn,
        NetworkConfiguration: {
          NetworkMode: 'PUBLIC'
        },
        AgentRuntimeArtifact: {
          ContainerConfiguration: {
            ContainerUri: `${ecrRepository.repositoryUri}:latest`
          }
        }
      }
    });

    agentRuntime.addDependency(triggerBuildResource.node.defaultChild as cdk.CfnResource);

    // Output
    new cdk.CfnOutput(this, 'AgentRuntimeArn', {
      value: agentRuntime.getAtt('AgentRuntimeArn').toString()
    });

    // Output
    new cdk.CfnOutput(this, 'ECRRepositoryUri', {
      value: ecrRepository.repositoryUri
    });
  }
}

Agent のコードを書く

前述の通り、AgnetCore Runtime は AI エージェントをホストします。なので、エージェントのコードはユーザーが好きに書くことができます(つまりここは皆さんの腕の見せ所)。
今回は適当にエージェントを作りましたが、ここはみなさんが好きに改変してください。
AgentCore Runtime 自体はコンテナで動くので、ビルドするコンテナイメージを作るのに必要な情報すべてを与えます。今回は CDK の以下に記載した通り、 assets/ ディレクトリにすべてを揃え、S3 にアップロードし、CodeBuild にコピーするような仕組みにしています。

lib/cdk-agentcore-runtime-stack.ts 抜粋
    // Deploy assets to S3
    new s3deploy.BucketDeployment(this, 'DeployAssets', {
      sources: [s3deploy.Source.asset('./assets')],
      destinationBucket: sourceBucket
    });

ディレクトリを mkdir assets/ で作って以下のファイル達を用意します。

assets/app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Dict, Any
from datetime import datetime
from strands import Agent

app = FastAPI(title="Strands Agent Server", version="1.0.0")

# Initialize Strands agent
strands_agent = Agent()

class InvocationRequest(BaseModel):
    input: Dict[str, Any]

class InvocationResponse(BaseModel):
    output: Dict[str, Any]

@app.post("/invocations", response_model=InvocationResponse)
async def invoke_agent(request: InvocationRequest):
    try:
        user_message = request.input.get("prompt", "")
        if not user_message:
            raise HTTPException(
                status_code=400, 
                detail="No prompt found in input. Please provide a 'prompt' key in the input."
            )

        result = strands_agent(user_message)
        response = {
            "message": result.message,
            "timestamp": datetime.utcnow().isoformat(),
            "model": "strands-agent",
        }

        return InvocationResponse(output=response)

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Agent processing failed: {str(e)}")

@app.get("/ping")
async def ping():
    return {"status": "healthy"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8080)
assets/Dockerfile
FROM public.ecr.aws/docker/library/python:3.13-slim
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]
assets/requirements.txt
fastapi
pydantic
uvicorn
aws-opentelemetry-distro
strands-agents

最後に CodeBuild を動かすためのコマンドを buildspec.yaml に記載します。s3 からデータを DL してビルドしてプッシュするだけです。

assets/buildspec.yml
version: 0.2
phases:
  pre_build:
    commands:
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
      - aws s3 cp s3://$SOURCE_BUCKET/Dockerfile .
      - aws s3 cp s3://$SOURCE_BUCKET/requirements.txt .
      - aws s3 cp s3://$SOURCE_BUCKET/app.py .
  build:
    commands:
      - docker build -t $IMAGE_REPO_NAME:latest .
      - docker tag $IMAGE_REPO_NAME:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
  post_build:
    commands:
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest

これをベースにエージェントを膨らましていきましょう。

デプロイ

cdk deploy --require-approval never 以上。

動作確認

以下のようなエージェント呼び出しコードを書きます。

invoke.py
import boto3
import json
import sys

arn = sys.argv[1]

client = boto3.client('bedrock-agentcore', region_name='us-east-1')
payload = json.dumps({
    "input": {"prompt": "こんにちは"}
})

response = client.invoke_agent_runtime(
    agentRuntimeArn=arn,
    payload=payload,
)
response_body = response['response'].read()
response_data = json.loads(response_body)
print("Agent Response:", response_data)

そして最後に実行します。

実行
python3 invoke.py ${AgentCore Runtime の ARN}
出力
Agent Response: {'output': {'message': {'role': 'assistant', 'content': [{'text': 'こんにちは!お元気ですか?何かお手伝いできることがあれば、お気軽にお声かけください。'}]}, 'timestamp': '2025-10-14T04:57:51.484289', 'model': 'strands-agent'}}

無事人間の作業は 3 分で終わりましたね!

認証を入れてみよう

このままだと AgentCore Runtime を実行できるのは、この Runtime を invoke できるポリシーを持った IAM ユーザーもしくはロールに限られます。
実際にはログイン(認証)したユーザーにのみ使ってほしいケース(=インバウンド認証)があります。
もちろん API Gateway + Lambda から呼ぶ、こともできますが、AgentCore Runtime に直接認証情報を加えて呼ぶ事もできます。

CDK のコードを改変

AWS で認証といえば Cognito ですね。AgentCore Runtime はもちろん Cognito に対応しているので、Cognito のユーザープール及びアプリケーションクライアント、そしてテストユーザーまで作成します。

lib/cdk-agentcore-runtime-stack.ts(修正)
import * as cdk from 'aws-cdk-lib';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import { Construct } from 'constructs';

export class CdkAgentcoreRuntimeStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Cognito ユーザープールの作成
    const userPool = new cognito.UserPool(this, 'AgentCoreUserPool', {
      userPoolName: 'AgentCoreUserPool',
      passwordPolicy: {
        minLength: 8
      },
      // メール設定を追加
      autoVerify: {
        email: true
      },
      signInAliases: {
        email: true,
        username: true
      }
    });

    // アプリクライアントの作成
    const userPoolClient = new cognito.UserPoolClient(this, 'AgentCoreUserPoolClient', {
      userPool: userPool,
      userPoolClientName: 'AgentCoreUserPoolClient',
      generateSecret: true,
      authFlows: {
        userPassword: true,
        userSrp: true
      }
    });

    // テストユーザーの作成(最小構成)
    new cognito.CfnUserPoolUser(this, 'TestUser', {
      userPoolId: userPool.userPoolId,
      username: 'testuser',
      userAttributes: [
        {
          name: 'email',
          value: 'testuser@example.jp'
        },
        {
          name: 'email_verified',
          value: 'true'
        }
      ],
      messageAction: 'SUPPRESS'
    });

    // ECR Repository
    const ecrRepository = new ecr.Repository(this, 'ECRRepository', {
      repositoryName: 'cdk-agentcore-runtime',
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      emptyOnDelete: true
    });

    // CodeBuild Service Role
    const codeBuildRole = new iam.Role(this, 'CodeBuildRole', {
      assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
      inlinePolicies: {
        CodeBuildPolicy: new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: ['logs:*', 'ecr:*', 's3:GetObject'],
              resources: ['*']
            })
          ]
        })
      }
    });

    // S3 Bucket for source files
    const sourceBucket = new s3.Bucket(this, 'SourceBucket', {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true
    });

    // Deploy assets to S3
    const deployAssets = new s3deploy.BucketDeployment(this, 'DeployAssets', {
      sources: [s3deploy.Source.asset('./assets')],
      destinationBucket: sourceBucket
    });

    // CodeBuild Project
    const buildProject = new codebuild.Project(this, 'BuildProject', {
      role: codeBuildRole,
      environment: {
        buildImage: codebuild.LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_3_0,
        computeType: codebuild.ComputeType.SMALL,
        privileged: true,
        environmentVariables: {
          AWS_DEFAULT_REGION: { value: this.region },
          AWS_ACCOUNT_ID: { value: this.account },
          IMAGE_REPO_NAME: { value: ecrRepository.repositoryName },
          SOURCE_BUCKET: { value: sourceBucket.bucketName }
        }
      },
      source: codebuild.Source.s3({
        bucket: sourceBucket,
        path: 'buildspec.yml'
      })
    });

    // Lambda execution role
    const lambdaRole = new iam.Role(this, 'LambdaRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')
      ],
      inlinePolicies: {
        CodeBuildAccess: new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: ['codebuild:*'],
              resources: ['*']
            })
          ]
        })
      }
    });

    // Lambda function to trigger build
    const buildTrigger = new lambda.Function(this, 'BuildTrigger', {
      runtime: lambda.Runtime.PYTHON_3_12,
      handler: 'index.handler',
      role: lambdaRole,
      timeout: cdk.Duration.minutes(15),
      memorySize: 256,
      code: lambda.Code.fromInline(`
import boto3
import cfnresponse
import time
import traceback

def handler(event, context):
    print(f'Event: {event}')
    try:
        if event['RequestType'] == 'Create':
            codebuild = boto3.client('codebuild')
            project_name = event['ResourceProperties']['ProjectName']
            print(f'Starting build for project: {project_name}')
            
            response = codebuild.start_build(projectName=project_name)
            build_id = response['build']['id']
            print(f'Build started with ID: {build_id}')
            
            status = 'IN_PROGRESS'
            for attempt in range(30):
                build_status = codebuild.batch_get_builds(ids=[build_id])
                status = build_status['builds'][0]['buildStatus']
                print(f'Attempt {attempt + 1}: Build status is {status}')
                
                if status in ['SUCCEEDED', 'FAILED', 'FAULT', 'STOPPED', 'TIMED_OUT']:
                    break
                time.sleep(30)
            
            if status == 'SUCCEEDED':
                print('Build succeeded')
                cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
            else:
                print(f'Build failed with status: {status}')
                cfnresponse.send(event, context, cfnresponse.FAILED, {})
        else:
            cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
    except Exception as e:
        print(f'Error: {str(e)}')
        print(f'Traceback: {traceback.format_exc()}')
        cfnresponse.send(event, context, cfnresponse.FAILED, {})
      `)
    });

    // Trigger build
    const triggerBuildResource = new cdk.CustomResource(this, 'TriggerBuild', {
      serviceToken: buildTrigger.functionArn,
      properties: {
        ProjectName: buildProject.projectName
      }
    });
    
    triggerBuildResource.node.addDependency(deployAssets);

    // Runtime Role
    const runtimeRole = new iam.Role(this, 'RuntimeRole', {
      assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'),
      inlinePolicies: {
        RuntimePolicy: new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: ['logs:*'],
              resources: ['*']
            }),
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                'ecr:GetAuthorizationToken',
                'ecr:BatchGetImage',
                'ecr:GetDownloadUrlForLayer'
              ],
              resources: ['*']
            }),
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: ['bedrock:*'],
              resources: ['*']
            })
          ]
        })
      }
    });

    // AgentCore Runtime
    const agentRuntime = new cdk.CfnResource(this, 'AgentRuntime', {
      type: 'AWS::BedrockAgentCore::Runtime',
      properties: {
        AgentRuntimeName: 'cdk_bedrock_agent_runtime',
        RoleArn: runtimeRole.roleArn,
        NetworkConfiguration: {
          NetworkMode: 'PUBLIC'
        },
        AgentRuntimeArtifact: {
          ContainerConfiguration: {
            ContainerUri: `${ecrRepository.repositoryUri}:latest`
          }
        },
        AuthorizerConfiguration: {
          CustomJWTAuthorizer: {
            AllowedClients: [userPoolClient.userPoolClientId],
            DiscoveryUrl: `https://cognito-idp.${this.region}.amazonaws.com/${userPool.userPoolId}/.well-known/openid-configuration`
          }
        }
      }
    });

    agentRuntime.addDependency(triggerBuildResource.node.defaultChild as cdk.CfnResource);

    // Output
    new cdk.CfnOutput(this, 'AgentRuntimeArn', {
      value: agentRuntime.getAtt('AgentRuntimeArn').toString()
    });

    new cdk.CfnOutput(this, 'ECRRepositoryUri', {
      value: ecrRepository.repositoryUri
    });

    new cdk.CfnOutput(this, 'UserPoolId', {
      value: userPool.userPoolId
    });

    new cdk.CfnOutput(this, 'UserPoolClientId', {
      value: userPoolClient.userPoolClientId
    });

    // 本来は表示してはいけないが、今回はデモのため表示
    new cdk.CfnOutput(this, 'UserPoolClientSecret', {
      value: userPoolClient.userPoolClientSecret.unsafeUnwrap()
    });
  }
}

差分はこちらです。

diff --git a/lib/cdk-agentcore-runtime-stack.ts b/lib/cdk-agentcore-runtime-stack.ts
index 48ef2f7..ba405ef 100644
--- a/lib/cdk-agentcore-runtime-stack.ts
+++ b/lib/cdk-agentcore-runtime-stack.ts
@@ -5,12 +5,57 @@ import * as s3 from 'aws-cdk-lib/aws-s3';
 import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
 import * as codebuild from 'aws-cdk-lib/aws-codebuild';
 import * as lambda from 'aws-cdk-lib/aws-lambda';
+import * as cognito from 'aws-cdk-lib/aws-cognito';
 import { Construct } from 'constructs';
 
 export class CdkAgentcoreRuntimeStack extends cdk.Stack {
   constructor(scope: Construct, id: string, props?: cdk.StackProps) {
     super(scope, id, props);
 
+    // Cognito ユーザープールの作成
+    const userPool = new cognito.UserPool(this, 'AgentCoreUserPool', {
+      userPoolName: 'AgentCoreUserPool',
+      passwordPolicy: {
+        minLength: 8
+      },
+      // メール設定を追加
+      autoVerify: {
+        email: true
+      },
+      signInAliases: {
+        email: true,
+        username: true
+      }
+    });
+
+    // アプリクライアントの作成
+    const userPoolClient = new cognito.UserPoolClient(this, 'AgentCoreUserPoolClient', {
+      userPool: userPool,
+      userPoolClientName: 'AgentCoreUserPoolClient',
+      generateSecret: true,
+      authFlows: {
+        userPassword: true,
+        userSrp: true
+      }
+    });
+
+    // テストユーザーの作成(最小構成)
+    new cognito.CfnUserPoolUser(this, 'TestUser', {
+      userPoolId: userPool.userPoolId,
+      username: 'testuser',
+      userAttributes: [
+        {
+          name: 'email',
+          value: 'testuser@example.jp'
+        },
+        {
+          name: 'email_verified',
+          value: 'true'
+        }
+      ],
+      messageAction: 'SUPPRESS'
+    });
+
     // ECR Repository
     const ecrRepository = new ecr.Repository(this, 'ECRRepository', {
       repositoryName: 'cdk-agentcore-runtime',
@@ -188,6 +233,12 @@ def handler(event, context):
           ContainerConfiguration: {
             ContainerUri: `${ecrRepository.repositoryUri}:latest`
           }
+        },
+        AuthorizerConfiguration: {
+          CustomJWTAuthorizer: {
+            AllowedClients: [userPoolClient.userPoolClientId],
+            DiscoveryUrl: `https://cognito-idp.${this.region}.amazonaws.com/${userPool.userPoolId}/.well-known/openid-configuration`
+          }
         }
       }
     });
@@ -199,9 +250,21 @@ def handler(event, context):
       value: agentRuntime.getAtt('AgentRuntimeArn').toString()
     });
 
-    // Output
     new cdk.CfnOutput(this, 'ECRRepositoryUri', {
       value: ecrRepository.repositoryUri
     });
+
+    new cdk.CfnOutput(this, 'UserPoolId', {
+      value: userPool.userPoolId
+    });
+
+    new cdk.CfnOutput(this, 'UserPoolClientId', {
+      value: userPoolClient.userPoolClientId
+    });
+
+    // 本来は表示してはいけないが、今回はデモのため表示
+    new cdk.CfnOutput(this, 'UserPoolClientSecret', {
+      value: userPoolClient.userPoolClientSecret.unsafeUnwrap()
+    });
   }
 }
\ No newline at end of file

変更点は Cognito のユーザープール及びアプリクライアントを作成したことと、その作成したものを AgentRuntime に反映させていること、そして後で使う情報を出力していることです。

これで cdk deploy --require-approval never します。

Runtime の呼び出し、の前に認証するユーザーのアクティベート

ユーザー作成まできていますが、ユーザーのアクティベートができていませんので aws cli を使ってアクティベートします。

aws cognito-idp admin-set-user-password --user-pool-id {ユーザープール ID} --username testuser --password MyPassword123! --permanent
ユーザープール ID は CDK の出力から取得してください。

Runtime の呼び出し

Bearer token の取得

Cognito で認証したあとに呼び出すには、まず Cognito で Bearer token を取得し、その Bearer token を使って AgentCore Runtime を呼び出します。
まずは Bearer token を取得するコードです。

auth.py
import boto3
import sys
import hmac
import hashlib
import base64

username, password, client_id, client_secret = sys.argv[1:5]

cognito = boto3.client('cognito-idp', region_name='us-east-1')
secret_hash = base64.b64encode(hmac.new(client_secret.encode(), (username + client_id).encode(), hashlib.sha256).digest()).decode()

auth = cognito.initiate_auth(
    ClientId=client_id,
    AuthFlow='USER_PASSWORD_AUTH',
    AuthParameters={'USERNAME': username, 'PASSWORD': password, 'SECRET_HASH': secret_hash}
)

print(f"Bearer Token: {auth['AuthenticationResult']['AccessToken']}")

そしてコードを実行します。

python3 auth.py <username> <password> <client_id> <client_secret>

すると以下のような文字列を得られます。

出力
Bearer Token: xxxxxxxxx(激長)xxxxxxx

この得られたトークンを <token> とします。

Bearer Token を用いて Runtime を呼び出す

Runtime の呼び出しコードを以下のようにします。

invoke.py
import requests
import sys

arn, bearer_token = sys.argv[1:3]

url = f"https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/{arn.replace(':', '%3A').replace('/', '%2F')}/invocations?qualifier=DEFAULT"
response = requests.post(url, 
    headers={'Authorization': f'Bearer {bearer_token}', 'Content-Type': 'application/json'},
    json={"input": {"prompt": "こんにちは"}}
)

print(f"Status: {response.status_code}")
print(f"Response: {response.text}")

そして呼び出します。

python3 invoke.py <arn> <bearer_token>

すると以下のような出力を得ます。

出力
Status: 200
Response: {"output":{"message":{"role":"assistant","content":[{"text":"こんにちは!お元気ですか?何かお手伝いできることがあれば、お気軽にお声かけください。"}]},"timestamp":"2025-10-18T05:03:41.344119","model":"strands-agent"}}

無事エージェントを呼び出すことに成功しました。

最後にトークンが違ったらハネられるかを確認しましょう。

python3 invoke.py <arn> hogefugapiyo

すると以下のような出力を得てエージェントの呼び出しに失敗してくれます。

出力
Status: 403
Response: {"message":"OAuth authorization failed: Failed to parse token"}

このように、簡単に認証を加えることもできます。Runtime を使えばエージェントのホストがとても楽になることがわかったのではないでしょうか。

ぜひ皆さんもやってみてください。


追記

もっと簡単な方法あるよとのコメントを頂いたので試してみた。

deploy-time-buildという L3 Construct を使うと、長ったらしく書いていた CodeBuild のプロジェクトから Build キックまでをいい感じにラップしてくれる。(buildspec.yml も不要)

Cognito の認証無し版でやってみた。

lib/cdk-agentcore-runtime-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Platform } from 'aws-cdk-lib/aws-ecr-assets';
import { ContainerImageBuild } from 'deploy-time-build';
import { Construct } from 'constructs';


export class CdkAgentcoreRuntimeStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ECR Repository
    const image = new ContainerImageBuild(this, 'Image', {
      directory: './assets',
      platform: Platform.LINUX_ARM64,
    });

    // Runtime Role
    const runtimeRole = new iam.Role(this, 'RuntimeRole', {
      assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'),
      inlinePolicies: {
        RuntimePolicy: new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: ['logs:*'],
              resources: ['*']
            }),
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                'ecr:GetAuthorizationToken',
                'ecr:BatchGetImage',
                'ecr:GetDownloadUrlForLayer'
              ],
              resources: ['*']
            }),
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: ['bedrock:*'],
              resources: ['*']
            })
          ]
        })
      }
    });

    // AgentCore Runtime
    const agentRuntime = new cdk.CfnResource(this, 'AgentRuntime', {
      type: 'AWS::BedrockAgentCore::Runtime',
      properties: {
        AgentRuntimeName: 'cdk_bedrock_agent_runtime',
        RoleArn: runtimeRole.roleArn,
        NetworkConfiguration: {
          NetworkMode: 'PUBLIC'
        },
        AgentRuntimeArtifact: {
          ContainerConfiguration: {
            ContainerUri: image.imageUri
          }
        }
      }
    });

    // Output
    new cdk.CfnOutput(this, 'AgentRuntimeArn', {
      value: agentRuntime.getAtt('AgentRuntimeArn').toString()
    });
  }
}

差分だけを表示すると以下。

lib/cdk-agentcore-runtime-stack.ts
diff --git a/lib/cdk-agentcore-runtime-stack.ts b/lib/cdk-agentcore-runtime-stack.ts
index 48ef2f7..f684715 100644
--- a/lib/cdk-agentcore-runtime-stack.ts
+++ b/lib/cdk-agentcore-runtime-stack.ts
@@ -1,150 +1,20 @@
 import * as cdk from 'aws-cdk-lib';
-import * as ecr from 'aws-cdk-lib/aws-ecr';
 import * as iam from 'aws-cdk-lib/aws-iam';
-import * as s3 from 'aws-cdk-lib/aws-s3';
-import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
-import * as codebuild from 'aws-cdk-lib/aws-codebuild';
-import * as lambda from 'aws-cdk-lib/aws-lambda';
+import { Platform } from 'aws-cdk-lib/aws-ecr-assets';
+import { ContainerImageBuild } from 'deploy-time-build';
 import { Construct } from 'constructs';
 
+
 export class CdkAgentcoreRuntimeStack extends cdk.Stack {
   constructor(scope: Construct, id: string, props?: cdk.StackProps) {
     super(scope, id, props);
 
     // ECR Repository
-    const ecrRepository = new ecr.Repository(this, 'ECRRepository', {
-      repositoryName: 'cdk-agentcore-runtime',
-      removalPolicy: cdk.RemovalPolicy.DESTROY,
-      emptyOnDelete: true
+    const image = new ContainerImageBuild(this, 'Image', {
+      directory: './assets',
+      platform: Platform.LINUX_ARM64,
     });
 
-    // CodeBuild Service Role
-    const codeBuildRole = new iam.Role(this, 'CodeBuildRole', {
-      assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
-      inlinePolicies: {
-        CodeBuildPolicy: new iam.PolicyDocument({
-          statements: [
-            new iam.PolicyStatement({
-              effect: iam.Effect.ALLOW,
-              actions: ['logs:*', 'ecr:*', 's3:GetObject'],
-              resources: ['*']
-            })
-          ]
-        })
-      }
-    });
-
-    // S3 Bucket for source files
-    const sourceBucket = new s3.Bucket(this, 'SourceBucket', {
-      removalPolicy: cdk.RemovalPolicy.DESTROY,
-      autoDeleteObjects: true
-    });
-
-    // Deploy assets to S3
-    const deployAssets = new s3deploy.BucketDeployment(this, 'DeployAssets', {
-      sources: [s3deploy.Source.asset('./assets')],
-      destinationBucket: sourceBucket
-    });
-
-    // CodeBuild Project
-    const buildProject = new codebuild.Project(this, 'BuildProject', {
-      role: codeBuildRole,
-      environment: {
-        buildImage: codebuild.LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_3_0,
-        computeType: codebuild.ComputeType.SMALL,
-        privileged: true,
-        environmentVariables: {
-          AWS_DEFAULT_REGION: { value: this.region },
-          AWS_ACCOUNT_ID: { value: this.account },
-          IMAGE_REPO_NAME: { value: ecrRepository.repositoryName },
-          SOURCE_BUCKET: { value: sourceBucket.bucketName }
-        }
-      },
-      source: codebuild.Source.s3({
-        bucket: sourceBucket,
-        path: 'buildspec.yml'
-      })
-    });
-
-    // Lambda execution role
-    const lambdaRole = new iam.Role(this, 'LambdaRole', {
-      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
-      managedPolicies: [
-        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')
-      ],
-      inlinePolicies: {
-        CodeBuildAccess: new iam.PolicyDocument({
-          statements: [
-            new iam.PolicyStatement({
-              effect: iam.Effect.ALLOW,
-              actions: ['codebuild:*'],
-              resources: ['*']
-            })
-          ]
-        })
-      }
-    });
-
-    // Lambda function to trigger build
-    const buildTrigger = new lambda.Function(this, 'BuildTrigger', {
-      runtime: lambda.Runtime.PYTHON_3_12,
-      handler: 'index.handler',
-      role: lambdaRole,
-      timeout: cdk.Duration.minutes(15),
-      memorySize: 256,
-      code: lambda.Code.fromInline(`
-import boto3
-import cfnresponse
-import time
-import traceback
-
-def handler(event, context):
-    print(f'Event: {event}')
-    try:
-        if event['RequestType'] == 'Create':
-            codebuild = boto3.client('codebuild')
-            project_name = event['ResourceProperties']['ProjectName']
-            print(f'Starting build for project: {project_name}')
-            
-            response = codebuild.start_build(projectName=project_name)
-            build_id = response['build']['id']
-            print(f'Build started with ID: {build_id}')
-            
-            status = 'IN_PROGRESS'
-            for attempt in range(30):
-                build_status = codebuild.batch_get_builds(ids=[build_id])
-                status = build_status['builds'][0]['buildStatus']
-                print(f'Attempt {attempt + 1}: Build status is {status}')
-                
-                if status in ['SUCCEEDED', 'FAILED', 'FAULT', 'STOPPED', 'TIMED_OUT']:
-                    break
-                time.sleep(30)
-            
-            if status == 'SUCCEEDED':
-                print('Build succeeded')
-                cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
-            else:
-                print(f'Build failed with status: {status}')
-                cfnresponse.send(event, context, cfnresponse.FAILED, {})
-        else:
-            cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
-    except Exception as e:
-        print(f'Error: {str(e)}')
-        print(f'Traceback: {traceback.format_exc()}')
-        cfnresponse.send(event, context, cfnresponse.FAILED, {})
-      `)
-    });
-
-    // Trigger build
-    const triggerBuildResource = new cdk.CustomResource(this, 'TriggerBuild', {
-      serviceToken: buildTrigger.functionArn,
-      properties: {
-        ProjectName: buildProject.projectName
-      }
-    });
-    
-    triggerBuildResource.node.addDependency(deployAssets);
-
     // Runtime Role
     const runtimeRole = new iam.Role(this, 'RuntimeRole', {
       assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'),
@@ -186,22 +56,15 @@ def handler(event, context):
         },
         AgentRuntimeArtifact: {
           ContainerConfiguration: {
-            ContainerUri: `${ecrRepository.repositoryUri}:latest`
+            ContainerUri: image.imageUri
           }
         }
       }
     });
 
-    agentRuntime.addDependency(triggerBuildResource.node.defaultChild as cdk.CfnResource);
-
     // Output
     new cdk.CfnOutput(this, 'AgentRuntimeArn', {
       value: agentRuntime.getAtt('AgentRuntimeArn').toString()
     });
-
-    // Output
-    new cdk.CfnOutput(this, 'ECRRepositoryUri', {
-      value: ecrRepository.repositoryUri
-    });
   }
 }

めちゃくちゃ短くなってますね。

後は npm install deploy-time-build したあと、cdk deploy し直して、認証がない版の invoke.py を python invoke.py <arn> すれば動きます。

アマゾン ウェブ サービス ジャパン (有志)

Discussion

tmokmsstmokmss

ナイス記事です!!ちなみに、deploy-time-build使うとさらに簡略化できますよ!

export class CdkAgentcoreRuntimeStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    // 冒頭を4行に圧縮
    const image = new ContainerImageBuild(this, 'Image', {
      directory: './assets',
      platform: Platform.LINUX_ARM64,
    });
    // Runtime Role
    // 省略
    // AgentCore Runtime
    const agentRuntime = new cdk.CfnResource(this, 'AgentRuntime', {
      type: 'AWS::BedrockAgentCore::Runtime',
      properties: {
        AgentRuntimeName: 'cdk_bedrock_agent_runtime',
        RoleArn: runtimeRole.roleArn,
        NetworkConfiguration: {
          NetworkMode: 'PUBLIC'
        },
        AgentRuntimeArtifact: {
          ContainerConfiguration: {
            // ここで参照
            ContainerUri: image.imageUri
          }
        }
      }
    });

宣伝失礼しました!

Kazuhito GoKazuhito Go

お、おお!?おおおお!?!?!?良さそう。試して更新します!
というか公式に入れてくださいw