Amazon Bedrockとチャットできる LINEbotボット構築ハンズオン

## はじめに
AWSから生成AIサービス「Amazon Bedrock」がGAされました。
この1ヶ月巷の至る所で「Bedrock」に関する勉強会や、事例の紹介がおこなわれています。
そんな熱気に当てられ、少し出足が遅くなりましたが「自分でも構築してみよう！」ということで ハンズオンしてみました！
なにより、こんな軽い気持ちで生成AIにさわれるだなんて控えめに言ってAWS最高ですね！

<br>

## 構成
### 処理フロー
①LINEでメッセージの送信
②APIGateway経由で Lambda 実行
③Lambdaの実行処理
　　　　③-1 LINEのリクエストを検証
　　　　③-2　DynamoDBから会話履歴の取得
　　　　③-3 LINEメッセージを　Bedrockに送信・応答を取得して LINEユーザに返信
　　　　③-4  ユーザとの会話をDynamoDBに保存

<br>

### 構成図

![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-10.54.45-640x243.png)

<br>

### 参考リンク
AWSドキュメント系
・[Amazon Bedrock boto3 セットアップ](https://github.com/aws-samples/amazon-bedrock-workshop/blob/b43299e8220f2b6b026ad2b02881c4d2732d3472/00_Intro/bedrock_boto3_setup.ipynb)
・[Boto3  ドキュメント](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock.html)
・[Lambda ランタイム　ドキュメント](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html)

テックブログ系
・DevlopersIO：[[Amazon Bedrock] Lambda関数からBedrockを呼び出してみた(著：青柳英明様)](https://dev.classmethod.jp/articles/invoke-bedrock-form-lambda-function/)
・Qiita：[Amazon BedrockをAWS Lambda上で呼び出してみた（API化）(著：@HayaP(Kohei Hayakawa)様)](https://qiita.com/HayaP/items/34eacb3055a368217aac)

<br>

## 構築
### 前提条件
・命名については一例として記載しているため、利用状況に応じて変更ください。
・LINE - APIGateway　- Lambda の構築部分については、[AWS Lambdaを利用したLINEbotハンズオン](https://cloud5.jp/aws-lambda_line-api/)を参照。

<br>

### 1.AmazonBedrock セットアップ
Amazon Bedrockは新たにモデルを使用するため設定を行う。

#### セットアップしたAmazonBedrockのサマリ

|リージョン|企業|モデル|
|-|-|-|
|ap-northeast-1|Anthropic|Claude Instant|

<br>

#### 1.1.AmazonBedrock　モデル使用のための設定
AWSマネコン > Amazon Bedrock > 左ペイン「Model access」 > 赤枠「Manage model access」を押下
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-7.48.48-640x189.png)

利用するモデル(今回は **Claude Instant**)に左側赤枠 チェックを入れる > 右下赤枠「save changes」を押下
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-7.55.13-640x160.png)

いくつか質問に回答後、下記画面で「Access granted」が表示されれば、モデルが利用可能となる。
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-7.59.39-640x176.png)

<br>

### 2.Lambdaの作成
#### 構築したLambdaのサマリ

|関数名|ランタイム|アーキテクチャ|実行ロール|
|-|-|-|-|
|bedrock-lambda-inamura|Python 3.11|arm64|Bedrock-fullaccess-role|

<br>

#### 構築順序サマリ

|No.|構築リソース|説明|
|-|-|-|
|2.1|IAMロール|LambdaにアタッチするIAMロール
|2.2|Lambda|Lambdaの本体
|2.3|boto3 ダウンロード|Python3.11のデフォルトboto3だとバージョンが古いため設定
|2.4|Lambdaレイヤ設定|2.3で取得した boto3を利用するために　Lambdaレイヤに設定
|2.5|Pythonコード|Lambdaの中身（コード部分）
|2.5|LINE-APIGateway-Lambda連携|LINEのメッセージをLambdaに送付できるように設定

<br>

#### 2.1.IAMロール作成

|リソース|リソース名|タイプ|説明|
|-|-|-|-|
|IAMロール|Bedrock-fullaccess-role|-|LambdaにアタッチするIAMロール|
|IAMポリシー|Bedrock-fullaccess|カスタマー管理|Bedrockを利用できるようにするためのポリシー|
|IAMポリシー|AmazonDynamoDBFullAccess|AWS管理|DynamoDBを利用できるようにするためのポリシー|
|IAMポリシー|CloudWatchLogsFullAccess|AWS管理|CloudWatchLogsを利用できるようにするためのポリシー|


##### 2.1.1.IAMポリシー作成
下記内容でIAMポリシーを作成する
```yaml
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "bedrock:*",
            "Resource": "*"
        }
    ]
}
```

<br>

##### 2.1.2.IAMロール作成
2.1.1.で作成したIAMポリシー及び、DynamoDB、CloudWatchLogsに書き込みをできるように「AmazonDynamoDBFullAccess」、「CloudWatchLogsFullAccess」をアタッチする。
※検証のためAWS管理ポリシーを利用する

![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-8.20.03-640x339.png)

<br>

#### 2.2.Lambdaの作成

|関数名|ランタイム|アーキテクチャ|実行ロール|
|-|-|-|-|
|bedrock-lambda-inamura|Python 3.11|arm64|Bedrock-fullaccess-role(※手順2.1で作成したロール)|

<br>

![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-8.31.34-640x391.png)

<br>

#### 2.3.boto3 ダウンロード
2023/11/5 現在、Python3.11のboto3は 1.27.1であるため、最新のboto3を利用する必要がある
zipにしてひとまとめにして、後続 2.4.のレイヤー作成時に利用する
参照：[Lambda ランタイム　ドキュメント](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html)

##### 2.3.1.boto3 ダウンロード
・Linuxの場合
```shel
# ディレクトリの作成
$ mkdir -p bedrock_demo/python
# boto3のインストール
$ pip install -t bedrock_demo/python boto3
# bedrock_demoディレクトリに移動
$ cd bedrock_demo
# ZIPファイルの作成
$ zip -r boto3-1.28.77.zip python
```

<br>

・Windowsの場合
```shel
# ディレクトリの作成
New-Item -ItemType Directory -Path bedrock_demo\python
# boto3のインストール
pip install -t bedrock_demo\python boto3
# bedrock_demoディレクトリに移動
cd bedrock_demo
# ZIPファイルの作成
Compress-Archive -Path python\* -DestinationPath boto3-1.28.77.zip
```

<br>

#### 2.4.Lambdaレイヤ設定
2.3で取得した boto3を利用するために、Lambdaレイヤを作成する
参照：[Lambdaレイヤーでの作業](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/chapter-layers.html)

<br>

|名前|アップロード|互換性のあるアーキテクチャ|互換性のあるランタイム|
|-|-|-|-|
|boto3_12877|2.3.手順で作成したboto3のzipファイル|arm64|Python 3.11|

※互換性のあるアーキテクチャについては、2.2.手順で構築したLambdaと合わせる必要がある

<br>

##### 2.4.1.Lambdaレイヤの作成画面遷移
AWSマネコン > Lambda > 左ペイン「レイヤー」 > 右側赤枠「レイヤーの作成」押下

![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-8.54.27-640x116.png)

<br>

##### 2.4.2.Lambdaレイヤの作成
赤枠部分を入力及び、ファイルのアップロード
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-8.57.57-559x640.png)

<br>

##### 2.4.3.Lambdaレイヤの設定
AWSマネコン > Lambda > 左ペイン「関数」 > 手順2.2.で構築したLambda（bedrock-lambda-inamura）選択 > 「コード」画面の 「レイヤーの追加」を選択 > 手順2.4.2.で作成したレイヤを設定する

![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-9.11.24-640x553.png)

<br>

#### 2.5.Pythonコードデプロイ
##### 2.5.1.コード
下記コードをLambdaのコードソースに貼り付け Deployを実行する

```python
import os
import sys
import json
import boto3
import time
from botocore.exceptions import ClientError
from boto3.dynamodb.conditions import Key, Attr

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)
from linebot.exceptions import (
    LineBotApiError, InvalidSignatureError
)
import logging

# ロギング設定
logger = logging.getLogger()
logger.setLevel(logging.ERROR)

# LINE認証情報の取得
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
if channel_secret is None:
    logger.error('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    logger.error('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

# LINE APIクライアントの初期化
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('UserConversations')

# Bedrock Runtimeクライアントの初期化
bedrock_runtime = boto3.client(service_name='bedrock-runtime')

def lambda_handler(event, context):
    # LINEからの署名確認
    signature = event["headers"].get("x-line-signature") or event["headers"].get("X-Line-Signature")
    body = event["body"]

    ok_json = {"isBase64Encoded": False, "statusCode": 200, "headers": {}, "body": ""}
    error_json = {"isBase64Encoded": False, "statusCode": 500, "headers": {}, "body": "Error"}

    # LINEからのリクエストを処理
    try:
        handler.handle(body, signature)
    except LineBotApiError as e:
        logger.error("Got exception from LINE Messaging API: %s\n" % e.message)
        for m in e.error.details:
            logger.error("  %s: %s" % (m.property, m.message))
        return error_json
    except InvalidSignatureError:
        return error_json

    # 処理が成功した場合のレスポンス
    return ok_json

# DynamoDBからユーザーの会話履歴を取得する関数を追加します。
def get_user_conversation_history(user_id):
    try:
        # ソートキー（Timestamp）に基づいて最新の履歴を取得する
        response = table.query(
            KeyConditionExpression=Key('UserID').eq(user_id),
            ScanIndexForward=False,  # 結果を降順にする
            Limit=1  # 最新の1項目だけ取得
        )
        # 履歴が存在すれば、それを返す
        if 'Items' in response and len(response['Items']) > 0:
            return response['Items'][0]['History']
        else:
            return ""  # 履歴がない場合は空文字を返す
    except ClientError as e:
        logger.error("DynamoDB query failed: {}".format(e.response['Error']['Message']))
        return ""  # エラーが発生した場合も空文字を返す

# message関数を更新
@handler.add(MessageEvent, message=TextMessage)
def message(line_event):
    # LINEからのメッセージを取得
    user_message = line_event.message.text
    user_id = line_event.source.user_id
    timestamp = int(line_event.timestamp)
    
    # ユーザーの会話履歴を取得
    conversation_history = get_user_conversation_history(user_id)
    
    # 会話履歴と新しいメッセージを結合してプロンプトを作成
    prompt = f"{conversation_history}\n\nHuman: {user_message}\n\nAssistant:"
    
    # Bedrock Runtimeを使用してAI応答を生成
    response = bedrock_runtime.invoke_model(
        modelId='anthropic.claude-instant-v1',
        contentType='application/json',
        accept='*/*',
        body=json.dumps({
            "prompt": prompt,
            "max_tokens_to_sample": 300,
            "temperature": 1,
            "top_k": 250,
            "top_p": 0.999,
            "stop_sequences": ["\n\nHuman:"],
            "anthropic_version": "bedrock-2023-05-31"
        })
    )

    # 応答をJSON形式で取得し、テキストメッセージを取り出す
    response_body = json.loads(response['body'].read().decode('utf-8'))
    ai_text_response = response_body['completion']

    # DynamoDBにユーザーの会話を記録
    save_conversation_to_dynamodb(user_id, prompt + ai_text_response)

    # LINEユーザーに応答を返す
    line_bot_api.reply_message(line_event.reply_token, TextSendMessage(text=ai_text_response))

    # DynamoDBにユーザーの会話を記録
def save_conversation_to_dynamodb(user_id, full_conversation):
    timestamp = str(int(time.time()))
    try:
        response = table.put_item(
            Item={
                'UserID': user_id,
                'Timestamp': timestamp,
                'History': full_conversation
            }
        )
        logger.info("DynamoDB save successful.")
    except Exception as e:
        logger.error("Error saving to DynamoDB: {}".format(e))
```

<br>

##### 2.5.2.Tips
・API requestの形式について、Bedrockの 左ペイン「Providers」より参照できる
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-9.36.33-640x485.png)

<br>

・より詳細な API requestについては 左ペイン「Playgrounds」の「Text」　右下「View API request」を押下
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-9.39.00-640x338.png)

<br>
どのようなAPI request形式になっているのか確認しやすい

![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-9.41.55.png)

<br>

#### 2.6.LINE - APIGateway - Lambda の連携
LINE - APIGateway　- Lambda の構築部分については、[AWS Lambdaを利用したLINEbotハンズオン](https://cloud5.jp/aws-lambda_line-api/)を参照。

<br>

### 3.DynamoDBの作成
#### 構築したDynamoDBのサマリ

|テーブル名|パーテーションキー|ソートキー|キー|
|-|-|-|-|
|UserConversations|UserID (String)|Timestamp (String)|History|

<br>

#### 3.1.テーブル作成
AWSマネコン > DynamoDB > 左ペイン「テーブル」 > 右側赤枠「テーブルの作成」を押下
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-9.52.48-640x98.png)

<br>

#### 3.2.テーブル設定
赤枠部分を入力し、設定画面右下「テーブルの作成」を押下
※検証レベルの構築のため、詳細は考慮していない
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-9.54.36-385x640.png)

#### ※3.3.メッセージを自動的に削除する場合(Time to Live)
AWSマネコン > DynamoDB > 左ペイン「テーブル　設定の更新」 > テーブル選択 > 追加の設定タグを選択　> TTLの設定を押下
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-10.03.38-640x328.png)

<br>
以上で、構築については完了となる
<br>


## 挙動の確認
### 1.LINEでメッセージを送信して、メッセージに対する回答が送られてくる
①富士山の高さを質問
※何度か投稿を繰り返しているため、前の質問を引きずってしますが、高さについては答えられてます
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-10.17.09.png)

<br>

②高尾山の高さを質問
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-10.15.38.png)

<br>

### 2.前回のメッセージ ＋ 自身の回答を踏まえた回答が送られてくる
③指示語を利用して前回の回答を保持しているのかの質問
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-10.19.33.png)

<br>

### 3.DynamoDBにてメッセージが保管されている
・AWS側のDynamoDBの画面
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-10.26.05-640x170.png)

<br>
・DynamoDB History部分

![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-10.27.27-640x411.png)

<br>

## さいごに
ブログとりまとめていた最中ですが、もしかしたら「DynamoDBのTimestamp不要ですかね？」そうすれば毎回上書きしてくれるんじゃ、、などブログを書いている最中にも色々な反省点が出てきます。「修正しないんかい！」というツッコミをもらいそうですが、そう言った部分も踏まえて次のブログに活かせればと思います。

さて最後にAmazonBedrock（Claude）に質問したら、こんな回答をいただけました。
![](https://cloud5.jp/wp-content/uploads/2023/11/スクリーンショット-2023-11-05-10.35.31.png)
おっしゃる通り技術の進歩を楽しみながら、適切な利用ができるように少しずつでも学んでいこうと思います！