AWS完全初心者がAmazon Bedrockアプリを作成した話(JAWS-UG茨城支部)

## はじめに
こんにちは。Tsukasaです！
今日は5/21に行われたJAWS-UG茨城支部のワークショップに参加したお話について書いていきたいと思います。今回のワークショップではタイトルにある通りBedrockを用いてアプリを作成しました。
このようにJAWS-UGでは各地域支部や専門支部で今回のような勉強会が開かれていますので、興味のある方は是非参加してみてください！
それでは早速見ていきましょう！

## 今回やること
今回やることは以下のようになります。
一つ一つの役割を見ていきましょう。

**Amplify Hosting**
Webアプリケーションを簡単に構築・デプロイできるフルスタックのフレームワーク。
フロントエンド開発とバックエンドサービスの連携を容易にするツール。
今回のハンズオンではチャットボットのUI画面をホスティングする役割を持ちます。

**Amazon API Gateway**
APIの作成、公開、管理、モニタリングを行うサービス。
HTTPSエンドポイントを作成し、外部からのリクエストを受け付ける。
今回のハンズオンではUI画面からリクエストを受け付けてLambdaに転送します。

**Lambda**
サーバーの管理なく、コードを実行できるコンピューティングサービス。
今回のハンズオンではユーザーからのメッセージをBedrockに送ります。

**Bedrock**
様々なベンダーの生成AIモデルを簡単に使えるサービス。
今回のハンズオンではユーザーからのメッセージに対して回答を作成します。

![スクリーンショット 2025-05-22 19.39.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/e37fe060-f38c-4ca9-b360-2e65a5d7b6a2.png)

## Bedrockの設定
まずはBedrockの設定を行う必要があります。

コンソールからBedrockにアクセスし、左下のモデルアクセスページを開きます。
特定のモデルを有効にするボタンをクリックし、追加したい権限を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/980b3568-4299-4b8e-bdb9-5cd512ef2524.png)

今回は``` Claude 3 sonnet ```と ```Claude 3 Haiku```の2つを足します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/546e3ae8-11d8-4aa5-bf07-6353900ebe5f.png)

追加されたら下記の画面のようにアクセスが付与されましたと表示が出るまで待ちます。
大体1分程で終わると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/1bc3b892-960c-492c-b98c-03d9951f0017.png)


## フロントエンドファイルの作成
フロントエンドファイルの作成を行います。
こちらがアプリのUIを司るものになります。
ソースコードは下記のものを使用します。

```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Amazon Bedrockサンプルアプリ</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.4.min.js"></script>

<script type="text/javascript">
    $(function(){
        $("#response").html("Bedrockからの回答がここに表示されます");

        $("#button").click( function(){
            $("#response").html("Bedrockに問い合わせしています");
            // ここのURLはAPIのデプロイ時に表示されるURL（後述）に差し替える
            var url = "https://XXXXXXXX.execute-api.us-west-2.amazonaws.com/prod";
                var JSONdata = {
                    "key1": $("#text").val()
                };
            $.ajax({
                type : 'post',
                url : url,
                data : JSON.stringify(JSONdata),
                contentType: 'application/json',
                dataType : 'json',
                scriptCharset: 'utf-8',
                success : function(data) {
                    $("#response").html(data);
                },
                error : function(data) {
                    // Error
                    alert("error");
                    alert(JSON.stringify(data));
                    $("#response").html(JSON.stringify(data));
                }
            });
        })
    })
</script>
</head>
<body>
    <h1>Amazon Bedrockサンプルアプリ</h1>
    <p>このアプリケーションは入力した文書に対して返事をしてくれます</p>
    <p>入力テキスト: <input type="text" id="text" size="100" placeholder="テキストを入力してください"></p>
    <p><button id="button" type="button">送信</button></p>
    <textarea id="response" cols=120 rows=10 disabled></textarea>
</body>
</html>
```

こちらのソースコードをVSCodeに貼り付けます。
ここではVSCodeのファイルの作り方等については割愛します。
注意点としてはファイル名は必ずindex.htmlにする必要があります。
後述しますが、私はこのファイル作成のせいで、この後大苦戦することになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/2db54fe3-bdd9-4aa6-b678-7ab0e0b525b3.png)


## Amplifyの作成
ここではAmplifyを作成します。
作成後に先程のindex.htmlファイルをアップロードします。
コンソールからAmplifyを開きます。
アプリケーションをデプロイボタンをクリックします。
![スクリーンショット 2025-05-22 19.49.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/611ad9e0-959f-43f8-9580-1d8a2dad2726.png)

この画面に来たら、Gitなしでデプロイを選択します。
今回は、ファイルを直接アップロードするので、こちらになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/2e91c0d6-152f-4b00-8772-3d71d789b4ff.png)

アプリケーションの名前は、SimpleBedrockにし、ブランチ名はmainにします。
ファイルのアップロード方法は、ドラックアンドドロップを選択します。
先程作成したindex.htmlファイルはzip化しておく必要があるので、事前に準備しておいてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/93763eea-f512-4c7a-b2d0-657aeb88d4be.png)


デプロイが完了したら、URLを直接クリックするか、右上のデプロイされたURLにアクセスのどちらかをクリックすると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/80805400-84b7-4cbe-a0b6-900f009fe3e6.png)

下記の画面にアクセスできれば今の時点では大丈夫です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/5d7248bf-740c-4178-a49c-ba4a8bdb2ed8.png)


## Lambda関数の作成
ここではLambda関数の作成を行います。
コンソールからLambda関数を検索し、関数の作成を行います。
一から作成を選択し、基本的な情報は下記のようにしてもらえれば大丈夫です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/6004743a-dc3a-432c-b631-da546461116f.png)

関数が作成できたら、コードを作成します。
コードは下記のものを使います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/59f08102-bc3b-4300-bdf0-3cb0c455159d.png)

```
import json
import boto3

bedrock_runtime_client = boto3.client(service_name='bedrock-runtime')

def lambda_handler(event, context):
    user_prompt = event["key1"]
    model_id = 'anthropic.claude-3-haiku-20240307-v1:0'
    system_prompt = "あなたは生成AIのエージェントです。ユーザからの質問に丁寧に回答してください。"
    max_tokens = 1000
    temperature = 0

    user_message = {
        "role": "user",
        "content": user_prompt      
    }
    body = json.dumps(
        {
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": max_tokens,
        "system": system_prompt,
        "messages": [user_message],
        "temperature": temperature
        }  
    )  

    response = bedrock_runtime_client.invoke_model(body=body, modelId=model_id)
    response_json = json.loads(response.get('body').read())
    
    return response_json['content'][0]['text']
```
コードを貼り付けられたら、Deployを押します。


次に設定画面に行き、タイムアウトの編集を行います。
デフォルトでは3秒になっていますが、Bedrockなどで質問して回答を得るアプリとなると、回答を得るまで時間がかかる場合があるので、こちらを変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/99bfd05e-5be0-429f-8e50-fda7086c41dd.png)

編集ボタンを押して、下記の画像から、タイムアウトを変更します。今回は3分にします。
最後に保存ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/27300712-bee4-47a1-8379-7cc0af684f65.png)

次にアクセス制限を行います。
現状ではLambdaでBedrockにアクセスすることができません。ここでBedrockにアクセスするように設定を行います。下記画像のロール名のURLをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/9b76800c-b01b-4f84-b8e0-0992db68179a.png)

その後許可を追加ボタンをクリックし、AmazonBedrockFullAccessを追加します。
最後に許可を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/84328743-06cf-4f97-a122-be8d61c1f953.png)

ここまでできたら、テストを行います。
テスト画面から、テストイベントを作成します。
下記のように設定してもらえれば大丈夫です。
イベントJSONのkey1を自分が聞きたい質問にします。
私は今回日本の首都がどこかを聞いてみたいと思います。
テストボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/f735c03b-07fd-4312-8d3b-a8850fc4edc2.png)

下記のような回答が出てきました。
テストは成功です！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/85091f1d-1ba1-4fd8-818b-55a32f5e088c.png)

## API Gatewayの作成
最後にAPI Gatewayの作成を行います。
コンソールからAPI Gatewayを検索し、作成を行います。
REST API画面から構築をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/ca3afd58-126a-4661-8939-bfcb3cbc47fb.png)

APIの詳細は下記のように設定します。
終わりましたら、APIを作成をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/6da71593-3f21-4c27-87c5-48ceb1f43e8a.png)

APIの作成が完了したら、メソッドの作成を行います。
メソッドを作成をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/705fa7ba-bbba-4fd8-b73e-401c855f2fc6.png)

メソッドタイプはPOSTを選択します。
統合タイプはLambadaを選択し、Lambda関数はus-west-2を選択します。
そうすると先程Lambdaで作成した関数が出てきますので、それを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/7f198277-1f4c-4251-a648-2528ad4354e6.png)

メソッドの作成が完了したら、ホーム画面に戻り、APIのリソースの設定をしていきます。
スラッシュボタンをクリックします。
そうするとCORSを有効にするボタンが押せるようになるので、押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/fb5b2f2b-e16c-4e00-aee5-4a0924b0aab7.png)

下記のようにPOSTにチェックを入れたら、右下の保存ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/aa490559-cd43-44a8-b191-8e1699046d9d.png)


ホーム画面に戻ってきたら、APIをデプロイボタンを押して、APIをデプロイします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/2740bf6f-6322-4129-9db2-681d59851430.png)


ステージは新しいステージを選択し、ステージ名はprodと入力します。
入力できたら、デプロイボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/f490feec-94a4-46cd-8eab-f276b9d77d5d.png)

ホーム画面に戻ったら、画面中央にURLを呼び出すという項目があるので、その下にあるURLをコピーします。
これがAPIのURLになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/b5bbedb8-2281-4595-86ef-a103eb695c67.png)


コピーしたURLを先程作成したindex.htmlの15行目にあるURLと差し替えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/aea5add0-b74d-4408-b788-6af578a3b5db.png)


Amplifyに行き、先程のindex.htmlファイルをアップロードします。
注意点としてはindex.htmlファイルは更新されましたが、zipファイルは更新されていないので、再度zipファイルを作成して、アップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/6a766c59-bdce-4ce4-adf7-863dc8e1972d.png)



デプロイが完了したら、先程と同じようにドメインのURLをクリックするか、右上のデプロイされたURLにアクセスボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/ad23e42f-8987-4a6d-a5ab-d08f694af290.png)


画面にアクセスができました。
今回はJAWS-UG茨城支部に参加したので、茨城について質問してみようと思います。
結果は下記のようになりました！！
この結果を見て茨城にも遊びに行ってみたいですね。
Bedrockアプリの作成成功です！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/dc7a9853-8f7b-40b3-9342-ce023b6c280b.png)


## 今回の失敗
私は今回このアプリの作成に3時間近くかかりました。
それはずっとエラーと戦い続けたからです。
今回は一体何をやらかしたのか？
その内容について紹介していきたいと思います。

## リージョンの不一致
今回はオレゴンリージョン(us-west-2)を使いました。
しかし、API Gatewayだけどういうわけか他のリージョンを使っていたのです。
そのため、APIを作成しそのURLを確認すると、us-west-2になっていませんでした。そのため、エラーとなったのです。それをindex.htmlファイルにURLを差し替えてAmplifyにファイルをアップロードし、アクセスすると下記のようにソースコードだけが書かれているページにアクセスされたのです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/f1f3a16d-5f58-4db5-86fc-aac3fb185b81.png)

## index.htmlファイルの名前問題
一番初めにVSCodeを用いて、index.htmlファイルを作成しましたよね？私はこのファイル名を当初handson.htmlといった名前にしていました。そのため今度は下記のようなエラーが発生してしまったのです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478024/7150cc54-5d91-4efc-8b34-b553fd25b1aa.png)

index.htmlファイルを作成時に名前をindex.htmlにする必要があるのは、エラーを出さないためです。
それでは何故index.htmlというファイル名にしないといけないのでしょうか？
それは「Webサーバーの初期表示ファイル（デフォルトドキュメント）」として読み込まれるからです。
Amplifyでホスティングする静的サイトは、裏側で Amazon S3 + CloudFront を使って提供されています。
このとき、アクセスされたときのURLが / の場合（例：https://example.amplifyapp.com/) に、サーバーは「どのファイルを最初に表示するか？」を探す必要があります。
順序としては下記のようになります。
1. ユーザーが https://example.amplifyapp.com/ にアクセス
2. サーバーは /（ルート）ディレクトリ内にある index.html を探す
3. index.html が存在すれば表示
4. 無ければ 404（Not Found）エラーになる
つまり、index.htmlがなければ初期表示ページが見つからない＝サイトが表示されないのです。
以上の理由からファイル名はindex.htmlと指定する必要があるのです。

## 終わりに
いかがだったでしょうか？
このようにエラーを出して失敗することで新たに学ぶ事がたくさんありました。
そして、Bedrockアプリを作成することもできました。
JAWS-UGではこのような勉強会がたくさんあるので、興味のある方は是非参加してみてください！
最後に今回このワークショップを企画してくださったJAWS-UG茨城支部の運営の皆さんありがとうございました！