【速報】Agents for Amazon BedrockがCloudFormationに対応しました（ほぼ一撃）

もっと早い人がいます。めちゃくちゃ早い！

https://dev.classmethod.jp/articles/bedrock-cfn-kbase-agent/


---
こちらの記事でLambdaをSAMでデプロイする方法を紹介しました。
APIがかなりスピーディに作成できますので、一度お試しください！！

https://qiita.com/moritalous/items/f828c5d7d2d116884f9a

このSAMプロジェクトを修正して **ほぼ一撃** でデプロイできることを確認しましたので紹介します。

## サンプルプロジェクトの取得

AWS Lambda Web Adapterのサンプルを使用します。

https://github.com/awslabs/aws-lambda-web-adapter

```bash
git clone https://github.com/awslabs/aws-lambda-web-adapter.git
```

examplesフォルダー内にある`bedrock-agent-fastapi-zip`に対して修正を行います。

:::note
サンプルには以下の3つのAPIが含まれています。

| パス | メソッド | 内容 |
| --- | --- | --- |
| /s3_bucket_count | GET | S3バケットの数を返却 |
| /s3_object_count | GET | 指定したS3バケットの中のオブジェクトの数を返却 |
| /s3_object | GET | 指定したバケットとオブジェクトキーの最終更新日を返却 | 
:::


## 手順

### OpenAPIスキーマを作る

`app`フォルダー内で以下のコマンドを実行してOpenAPIスキーマを生成します。

```shell
pip install -r requirements.txt
pip install boto3

python -c "import main;import json; print(json.dumps(main.app.openapi()))" > openapi.json
```

`openapi.json`が作成されます。

### template.yamlを修正する

もともとあるものを含め、以下のリソースを作成します。

* Lambda Function

    AWS Lambda Web Adapterを使って、FastAPIのAPI作成だけでBedrockに対応できるすぐれものです。

    ```yaml
      BedrockAgentFastAPIFunction:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: ./app
          Runtime: python3.12
          Handler: run.sh
          MemorySize: 1024
          Environment:
            Variables:
              AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap
              AWS_LWA_READINESS_CHECK_PROTOCOL: TCP
              PORT: 8000
          Layers:
            - !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:20
          Policies: AmazonS3ReadOnlyAccess
    ```

* Lambda Permission
    ```yaml
      BedrockAgentPermission:
        Type: AWS::Lambda::Permission
        Properties:
          FunctionName: !Ref BedrockAgentFastAPIFunction
          Action: lambda:InvokeFunction
          Principal: bedrock.amazonaws.com
          SourceAccount: !Ref "AWS::AccountId"
          SourceArn: !Sub arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*
    ```

* IAMロール（Bedrockの実行ロール）　☆☆☆追加☆☆☆
    ロール名は`AmazonBedrockExecutionRoleForAgents_`で始まる必要があるようです。
    BedrockモデルをInvokeする権限と、アクションのLambdaをInvokeする権限が必要です。

    ```yaml
      BedrockAgentResourceRole:
        Type: AWS::IAM::Role
        Properties:
          RoleName: AmazonBedrockExecutionRoleForAgents_FastAPISample
          AssumeRolePolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Principal:
                  Service: bedrock.amazonaws.com
                Action: sts:AssumeRole
                Condition:
                  StringEquals:
                    aws:SourceAccount: !Sub ${AWS::AccountId}
                  ArnLike:
                    aws:SourceArn: !Sub arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*
          Policies:
            - PolicyName: AmazonBedrockExecutionRoleForAgents_FastAPISamplePolicy
              PolicyDocument:
                Version: 2012-10-17
                Statement:
                  - Effect: Allow
                    Action: bedrock:InvokeModel
                    Resource: !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-v2:1
                  - Effect: Allow
                    Action: lambda:InvokeFunction
                    Resource: !GetAtt BedrockAgentFastAPIFunction.Arn
    ```

* BedrockAgent　☆☆☆追加☆☆☆
    BedrockAgent。Payloadに先ほど出力したOpenAPIスキーマを貼り付ける。
    文字列で指定するので、JSON文字列をシングルクオートで囲います。

    ```yaml
      BedrockAgent:
        Type: AWS::Bedrock::Agent
        Properties:
          AgentName: BedrockAgentFastAPISample
          Description: Query S3 information agent.
          AgentResourceRoleArn: !GetAtt BedrockAgentResourceRole.Arn
          Instruction: This agent allows you to query the S3 information in your AWS account.
          FoundationModel: anthropic.claude-v2:1
          ActionGroups:
            - ActionGroupName: action-group
              ActionGroupExecutor:
                Lambda: !GetAtt BedrockAgentFastAPIFunction.Arn
              ApiSchema:
                Payload: 'ここにOpenAPIスキーマのJSONを丸ごと貼り付ける'
    ```

:::note
ここだけ手作業。。惜しい！！
外部ファイルの文字列をCloudFormationテンプレートの一部に、自動で埋め込む方法をご存じの方いらっしゃいましたら教えて下さい。
:::

:::note warn
必須項目は`AgentName`だけなのですが、`AgentResourceRoleArn`や`Instruction`、`FoundationModel`を指定せずに作成してしまうと、CloudFormationの更新も削除もできなくなります。さらにAgents for Amazon Bedrockの管理画面からも消せなくなってしまいますので、注意してください。
:::

* BedrockAgentのエイリアス　☆☆☆追加☆☆☆

    ```yaml
      BedrockAgentRelease:
        Type: AWS::Bedrock::AgentAlias
        Properties:
          AgentAliasName: v1
          AgentId: !Ref BedrockAgent
    ```

最終的にこのような`template.yaml`となります。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
  bedrock-agent-fastapi

  FastAPI app that work with Agents for Amazon Bedrock

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 60

Resources:
  BedrockAgentFastAPIFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./app
      Runtime: python3.12
      Handler: run.sh
      MemorySize: 1024
      Environment:
        Variables:
          AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap
          AWS_LWA_READINESS_CHECK_PROTOCOL: TCP
          PORT: 8000
      Layers:
        - !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:20
      Policies: AmazonS3ReadOnlyAccess

  BedrockAgentPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref BedrockAgentFastAPIFunction
      Action: lambda:InvokeFunction
      Principal: bedrock.amazonaws.com
      SourceAccount: !Ref "AWS::AccountId"
      SourceArn: !Sub arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*

  BedrockAgentResourceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: AmazonBedrockExecutionRoleForAgents_FastAPISample
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Sub ${AWS::AccountId}
              ArnLike:
                aws:SourceArn: !Sub arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*
      Policies:
        - PolicyName: AmazonBedrockExecutionRoleForAgents_FastAPISamplePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: bedrock:InvokeModel
                Resource: !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-v2:1
              - Effect: Allow
                Action: lambda:InvokeFunction
                Resource: !GetAtt BedrockAgentFastAPIFunction.Arn

  BedrockAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: BedrockAgentFastAPISample
      Description: Query S3 information agent.
      AgentResourceRoleArn: !GetAtt BedrockAgentResourceRole.Arn
      Instruction: This agent allows you to query the S3 information in your AWS account.
      FoundationModel: anthropic.claude-v2:1
      ActionGroups:
        - ActionGroupName: action-group
          ActionGroupExecutor:
            Lambda: !GetAtt BedrockAgentFastAPIFunction.Arn
          ApiSchema:
            Payload: '{"openapi": "3.0.2", "info": {"title": "FastAPI", "description": "This agent allows you to query the S3 information in your AWS account.", "version": "0.1.0"}, "paths": {"/s3_bucket_count": {"get": {"summary": "Get S3 Bucket Count", "description": "This method returns the number of S3 buckets in your AWS account.\n\nReturn:\n    S3BucketCountResponse: A json object containing the number of S3 buckets in your AWS account.", "operationId": "get_s3_bucket_count_s3_bucket_count_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/S3BucketCountResponse"}}}}}}}, "/s3_object_count": {"get": {"summary": "Get S3 Object Count", "description": "This method returns the number of S3 objects in your specified bucket.\n\nReturn:\n    S3ObjectCountResponse: A json object containing the number of S3 objects in your specified bucket.", "operationId": "get_s3_object_count_s3_object_count_get", "parameters": [{"name": "bucket_name", "in": "query", "required": true, "schema": {"type": "string", "description": "Bucket name", "title": "Bucket Name"}, "description": "Bucket name"}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/S3ObjectCountResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/s3_object": {"post": {"summary": "Get S3 Object", "description": "This method returns the last modified date of S3 object.\n\nReturn:\n    S3GetObjectResponse: A json object containing the last modified date of S3 objects.", "operationId": "get_s3_object_s3_object_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/S3GetObjectRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}}, "components": {"schemas": {"HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "S3BucketCountResponse": {"properties": {"count": {"type": "integer", "title": "Count", "description": "the number of S3 buckets"}}, "type": "object", "required": ["count"], "title": "S3BucketCountResponse"}, "S3GetObjectRequest": {"properties": {"bucket_name": {"type": "string", "title": "Bucket Name", "description": "Bucket name"}, "object_key": {"type": "string", "title": "Object Key", "description": "Object key"}}, "type": "object", "required": ["bucket_name", "object_key"], "title": "S3GetObjectRequest"}, "S3ObjectCountResponse": {"properties": {"count": {"type": "integer", "title": "Count", "description": "the number of S3 objects"}}, "type": "object", "required": ["count"], "title": "S3ObjectCountResponse"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}}}}'
  BedrockAgentRelease:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentAliasName: v1
      AgentId: !Ref BedrockAgent

Outputs:
  BedrockAgentFastAPIFunction:
    Description: "BedrockAgentFastAPIFunction Lambda Function ARN"
    Value: !GetAtt BedrockAgentFastAPIFunction.Arn
  BedrockAgent:
    Description: "BedrockAgent ID"
    Value: !Ref BedrockAgent
  BedrockAgentAlias:
    Description: "BedrockAgentAlias ID"
    Value: !Ref BedrockAgentRelease

```

## ビルド、デプロイ

通常のコマンドです。

```shell
sam build
sam deploy
```

## 動作確認

AWS CLIはInvokeAgentに未対応のためPythonスクリプトで確認

:::note

agentIdとagentAliasIdはCfnのOutputから取得できます。（agentAliasIdは、agentIdとagentAliasIdをパイプで繋いだ文字列が出力されています。）

```
Key                 BedrockAgentAlias
Description         BedrockAgentAlias ID
Value               YGTJ0VMR7O|LZAELUEUPI

Key                 BedrockAgent
Description         BedrockAgent ID
Value               YGTJ0VMR7O
```
:::


```python
import uuid

import boto3

client = boto3.client("bedrock-agent-runtime")
response = client.invoke_agent(
    agentId="YGTJ0VMR7O",
    agentAliasId="LZAELUEUPI",
    sessionId=str(uuid.uuid4()),
    inputText="S3バケットの数を教えて下さい。",
)

for event in response.get("completion"):
    chunk = event["chunk"]
    print(chunk["bytes"].decode(), flush=None)

print()
```

> あなたのAWSアカウントには14個のS3バケットがあります。

うまくいきました

:::note
ちなみに、マネジメントコンソールでも確認できました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/3fb3b4ea-9ed6-3cad-d454-e73c33a3d23f.png)
:::