金曜日, 9月 26, 2025
金曜日, 9月 26, 2025
- Advertisment -
ホームニューステックニュースAmazon Bedrock AgentCore が CloudFormation に対応したので試してみた

Amazon Bedrock AgentCore が CloudFormation に対応したので試してみた



現時点だと以下4つのリソースに対応しています。

https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/AWS_BedrockAgentCore.html

Resource types

  • AWS::BedrockAgentCore::BrowserCustom
  • AWS::BedrockAgentCore::CodeInterpreterCustom
  • AWS::BedrockAgentCore::Runtime
  • AWS::BedrockAgentCore::RuntimeEndpoint

早速やってみよう!

作って使ってみる

Strands Agents を使います。また、Amazon Bedrock AgentCore はポート 8080 で /ping/invocations を受け付けられる必要があります。詳細はこちらをご確認ください。

CloudFormation のテンプレート

こんな感じで用意しました。

bedrock-agentcore-runtime.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Complete CodeBuild and AgentCore Runtime'

Resources:
  
  ECRRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: bedrock-agent-runtime

  
  CodeBuildRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: CodeBuildPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:*
                  - ecr:*
                Resource: "*"

  
  BuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: ARM_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-aarch64-standard:3.0
        PrivilegedMode: true
        EnvironmentVariables:
          - Name: AWS_DEFAULT_REGION
            Value: !Ref AWS::Region
          - Name: AWS_ACCOUNT_ID
            Value: !Ref AWS::AccountId
          - Name: IMAGE_REPO_NAME
            Value: !Ref ECRRepository
      Source:
        Type: NO_SOURCE
        BuildSpec: |
          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
            build:
              commands:
                - |
                  cat > Dockerfile  requirements.txt  app.py 

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

                  
                  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)                  
                  EOF
                - 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

  
  BuildTrigger:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.9
      Handler: index.handler
      Role: !GetAtt LambdaRole.Arn
      Timeout: 900
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import time
          
          def handler(event, context):
              try:
                  if event['RequestType'] == 'Create':
                      codebuild = boto3.client('codebuild')
                      project_name = event['ResourceProperties']['ProjectName']
                      
                      response = codebuild.start_build(projectName=project_name)
                      build_id = response['build']['id']
                      
                      for attempt in range(20):
                          build_status = codebuild.batch_get_builds(ids=[build_id])
                          status = build_status['builds'][0]['buildStatus']
                          
                          if status in ['SUCCEEDED', 'FAILED', 'FAULT', 'STOPPED', 'TIMED_OUT']:
                              break
                          time.sleep(30)
                      
                      if status == 'SUCCEEDED':
                          cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                      else:
                          cfnresponse.send(event, context, cfnresponse.FAILED, {})
                  else:
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
              except Exception as e:
                  cfnresponse.send(event, context, cfnresponse.FAILED, {})

  
  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: CodeBuildAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - codebuild:*
                Resource: "*"

  
  TriggerBuild:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt BuildTrigger.Arn
      ProjectName: !Ref BuildProject
    DependsOn: ECRRepository

  
  RuntimeRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock-agentcore.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: RuntimePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:*
                Resource: "*"
              - Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                  - ecr:BatchGetImage
                  - ecr:GetDownloadUrlForLayer
                Resource: "*"
              - Effect: Allow
                Action:
                  - bedrock:*
                Resource: "*"

  
  AgentRuntime:
    Type: AWS::BedrockAgentCore::Runtime
    Properties:
      AgentRuntimeName: bedrock_agent_runtime
      RoleArn: !GetAtt RuntimeRole.Arn
      NetworkConfiguration:
        NetworkMode: PUBLIC
      AgentRuntimeArtifact:
        ContainerConfiguration:
          ContainerUri: !Sub "${ECRRepository.RepositoryUri}:latest"
    DependsOn: TriggerBuild

Outputs:
  AgentRuntimeArn:
    Value: !GetAtt AgentRuntime.AgentRuntimeArn
  ECRRepositoryUri:
    Value: !GetAtt ECRRepository.RepositoryUri

デプロイ

AWS CLI でデプロイします。

デプロイ

aws cloudformation deploy \
  --template-file bedrock-agentcore-runtime.yaml \
  --stack-name bedrock-agent-runtime \
  --capabilities CAPABILITY_IAM \
  --region us-east-1

呼び出す

デプロイが終わったら呼び出しましょう。
呼び出すにあたって、AgentCore Runtime の ARN が必要なので取得します。

Runtime ARN の取得

AGENT_RUNTIME_ARN=$(aws cloudformation describe-stacks \
  --stack-name bedrock-agent-runtime \
  --region us-east-1 \
  --query 'Stacks[0].Outputs[?OutputKey==`AgentRuntimeArn`].OutputValue' \
  --output text)

AWS CLI でもいいのですが、Boto3 で呼び出したほうが楽なので invoke するコードを用意しました。prompt は好きに差し替えられます。

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)

そして推論します。

invoke 実行

python3 invoke.py $AGENT_RUNTIME_ARN

するとこんな出力を得られます。

result

Agent Response: {'output': {'message': {'role': 'assistant', 'content': [{'text': 'こんにちは!お元気ですか?何かお手伝いできることがあれば、お気軽にお声かけください。'}]}, 'timestamp': '2025-09-25T13:24:07.602632', 'model': 'strands-agent'}}

ざっくり解説

ECR

AgentCore Runtime はコンテナベースで動くので、最初に ECR のリポジトリを作る必要があります。

ECR

  ECRRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: bedrock-agent-runtime

コンテナイメージのビルドに CodeBuild

ただしこれだけだと空のリポジトリなのでイメージをビルドする必要があります。
AWS だといくつかビルドの方法がありますが、今回は CodeBuild を使いました。

CodeBuild

CodeBuildRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: CodeBuildPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:*
                  - ecr:*
                Resource: "*"

  
  BuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: ARM_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-aarch64-standard:3.0
        PrivilegedMode: true
        EnvironmentVariables:
          - Name: AWS_DEFAULT_REGION
            Value: !Ref AWS::Region
          - Name: AWS_ACCOUNT_ID
            Value: !Ref AWS::AccountId
          - Name: IMAGE_REPO_NAME
            Value: !Ref ECRRepository
      Source:
        Type: NO_SOURCE
        BuildSpec: |
          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
            build:
              commands:
                - |
                  cat > Dockerfile  requirements.txt  app.py 

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

                  
                  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)                  
                  EOF
                - 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

コンテナイメージの中に Agent のコードを入れる必要がありますので、標準入力等を使って CodeBuild 内に無理やりファイルを作ったりしています。他、requirements.txt も作り、依存するパッケージもインストールするコードも入れています。

今回はここにある Agent のコードをそのまま使っています。

https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/getting-started-custom.html

FastAPI を使ってます。また、/ping/invocations, そしてポート8080 で受け付ける必要があるところにご注意ください。

ただし、このままだと CodeBuild のプロジェクトが出来上がるだけなので、動かす必要があります。Lambda のトリガーを使って動かしています。

lambda

  BuildTrigger:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.9
      Handler: index.handler
      Role: !GetAtt LambdaRole.Arn
      Timeout: 900
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import time
          
          def handler(event, context):
              try:
                  if event['RequestType'] == 'Create':
                      codebuild = boto3.client('codebuild')
                      project_name = event['ResourceProperties']['ProjectName']
                      
                      response = codebuild.start_build(projectName=project_name)
                      build_id = response['build']['id']
                      
                      for attempt in range(20):
                          build_status = codebuild.batch_get_builds(ids=[build_id])
                          status = build_status['builds'][0]['buildStatus']
                          
                          if status in ['SUCCEEDED', 'FAILED', 'FAULT', 'STOPPED', 'TIMED_OUT']:
                              break
                          time.sleep(30)
                      
                      if status == 'SUCCEEDED':
                          cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                      else:
                          cfnresponse.send(event, context, cfnresponse.FAILED, {})
                  else:
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
              except Exception as e:
                  cfnresponse.send(event, context, cfnresponse.FAILED, {})

  
  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: CodeBuildAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - codebuild:*
                Resource: "*"

  
  TriggerBuild:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt BuildTrigger.Arn
      ProjectName: !Ref BuildProject
    DependsOn: ECRRepository

AgentCore Runtime

最後に AgentCore Runtime を作ります。Runtime は AWS のリソースを触るため、ロールを作ってポリシーをアタッチする必要があります。CWL 及び ECR へのアクセスは必須、そしてほとんどのケースにおいて Bedrock へのアクセスが必要です。Bedrock のポリシーは面倒だったのでアクションとリソースをすべて許してしまいましたが、適切に絞ることが大切です。

Role

  RuntimeRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock-agentcore.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: RuntimePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:*
                Resource: "*"
              - Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                  - ecr:BatchGetImage
                  - ecr:GetDownloadUrlForLayer
                Resource: "*"
              - Effect: Allow
                Action:
                  - bedrock:*
                Resource: "*"

そして最後に Runtime を作ります。

runtime

  AgentRuntime:
    Type: AWS::BedrockAgentCore::Runtime
    Properties:
      AgentRuntimeName: bedrock_agent_runtime
      RoleArn: !GetAtt RuntimeRole.Arn
      NetworkConfiguration:
        NetworkMode: PUBLIC
      AgentRuntimeArtifact:
        ContainerConfiguration:
          ContainerUri: !Sub "${ECRRepository.RepositoryUri}:latest"
    DependsOn: TriggerBuild

すでに作ってある Role や ECR のコンテナイメージの URI を指定するだけです。

さいごに

IaC できるようになりました。近いうち(?)に CDK もきっと来ることでしょう。来てくれるとうれしいな。



Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -