【Amazon Bedrockエージェント実践】Next.js使ってチャットアプリを作る

## この記事を読んで何を作れる?

![agent.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/04c48f04-23fb-12a6-3de2-23e9c1b2bb16.gif)

:::note warn
レスポンス帰って来るまでちょっと時間かかります。
:::

Amazon BedrockエージェントとNext.jsアプリを連携し、通常の会話はもちろん、
都市の天気についての質問にも最新の情報を取得して答えできるアプリが作れます。

### アーキテクチャ
![hi.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/47dd8d46-a3fa-fce9-a1cc-206de452a9cf.png)

ローカル開発時のアーキテクチャーは上記の図になります。

:::note info
もしご好評でしたら、次回の記事でcdk使ってデプロイまでやります:relaxed:
:::

### そもそもBedrockって何？

AWSが提供する基盤モデルを使用して生成 AI アプリケーションを構築およびスケーリングする最も簡単な方法らしいです。

詳しく知りたい方はこちらの資料をご覧ください。

https://speakerdeck.com/minorun365/awsnosheng-cheng-aisabisuru-men-and-zui-qiang-noshu-ji-shao-jie

### 事前準備

#### AWSのアカウント

https://aws.amazon.com/jp/register-flow/

#### BedRock モデルの有効化
AWSアカウントが準備できましたら、コンソールからAmazon Bedrockを開いて、
![BADCA02A-F687-4F63-9937-3C59F7C936C2_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/456fe43d-6b9a-ce29-47d4-ea4f7d183d3e.jpeg)

**モデルアクセス**から今回使用する`Claude 3.5 Sonnet`モデルへのアクセス申請を行ってください。

![60A03F76-4A1B-4F48-AF82-9247117ED8D2.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/83078023-c682-4902-827a-d7088fd15992.jpeg)

いくつかの質問に回答すると、しばらくしてアクセスが付与されます。

#### OpenWeather - APIキー
![A4289034-AA03-41E6-9785-37C054599468.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/1e372949-23a3-9e26-ee25-8ee331fca38d.jpeg)

OpenWeatherMapは、Webやモバイルアプリケーションの開発者に、現在の天候や予測履歴を含む各種気象データの無料APIを提供するオンラインサービスである。

https://openweathermap.org/

アカウントを作ったらメニューバーの`My API Keys`からApiKeyを入手できます。

![E3DD8F4B-A9C3-49A7-A275-9B8D39BE4528_1_105_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/26fcce78-e45d-b568-ba15-286cbabbd6e6.jpeg)


:point_up_tone1: 一点注意すべきなのはApiKey生成後、しばらく待たないと使用できないことです。

### エージェント作成

Amazon Bedrockの左にあるメニューバーから**エージェント**を開いて、エージェントを作成します。
![4D9302D5-ED5C-4CCD-957D-E75AE783F720.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/ae68aaa0-801f-1be1-193a-a0a24aed45c6.jpeg)

エージェントの名前を`weather-agent`にして、説明文を入れて作成します。

![8D28812D-DF5E-4685-8B1F-47C9E6E248E6_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/825e82b3-12c8-963f-ca4f-f49d8fe3bd85.jpeg)

作成されたら、あらかじめ有効化にされたモデルを選びます、その他の設定は一旦デフォルトのままにしましょう。

![C0FEB239-4A64-476E-81E8-6CA46F07961E.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/9e12e5ec-16f3-dcae-71b9-f4f740e9530b.jpeg)

次、アクショングループを作成します。

![C11DEAE1-A046-46CB-BD93-FF00E31DFB87_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/d2ea005b-9844-3658-0dff-15f167d33f99.jpeg)

追加ボタンをクリックすればアクションを追加画面が開きます。

![8DC3AC0B-8AF9-4C9F-8238-35A7E92A1C34.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/34c415e3-2204-d2af-1f40-4e38ff532efd.jpeg)

- アクショングループ名を入力します: `action-group-get-weather`
- 説明を入力します: `get weather action group`
- アクショングループタイプ: `APIスキーマで定義`を選択します
- アクショングループの呼び出し: `新しい Lambda 関数をすばやく作成する - 推奨`にチェックを入れます
- アクショングループスキーマは: `インラインスキーマエディタで定義`を選びます

`インラインオープン API スキーマ`は下記のように定義します。

```yaml
openapi: 3.0.0
info:
  title: GetWeather
  version: 1.0.0
paths:
  /weather:
    get:
      summary: Search
      description: Retrieve current weather information for a specified city
      operationId: searchGetWeatherInfo
      parameters:
        - name: cityName
          in: path
          description: Name of the city to get weather information for API
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  body:
                    type: string      
```
最後`作成`をクリックします、しばらくしたらエージェントビルダーのメイン画面に戻されます。

![9E255E1B-132C-42DB-AF93-B085D4A03CBE_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/cebc7c9d-e7ad-48ac-acd7-59e9505d72af.jpeg)

そしたら、アクショングループから先ほど作られたアクションを確認できます。

![AF04C9DF-B1BC-48F0-89B3-5CECB5B43235_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/a3d21b5c-ff37-2355-c3a2-5b948fd2b682.jpeg)

アクションの名前をクリックして、編集画面に入ります。
その画面の`Lambda 関数を選択`から`表示`ボタンをクリックしてください。
![284110C3-B84A-407F-A851-B93FC8F25F2C.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/26a41f4b-5056-d003-6c74-c10b98245711.jpeg)

別タグからLambda関数の編集画面が表示されると思います。

:::note warn
エージェントビルダー画面から新しいLambdaを作成する場合、現在Python製になる一択です。
:::

Lambdaコードを下記のように修正して、Deployしてください。
OpenWeatherMapのAPIKeyは一旦ベタ書きします。
```python:dummy_lambda.py
import json
import urllib.request
import urllib.parse
from datetime import datetime

def lambda_handler(event, context):
    api_key = "" # ご自身のキーを入れてください。
    get_parameters = event.get("parameters", [])
    city_name = get_parameters[0].get("value", "New York City")
    print("city_name:", city_name)
    
    base_url = "http://api.openweathermap.org/data/2.5/weather"
    query_params = {
        "q": city_name,
        "appid": api_key,
        "lang": "ja",
        "units": "metric"
    }
    url = f"{base_url}?{urllib.parse.urlencode(query_params)}"
    
    try:
        with urllib.request.urlopen(url) as response:
            if response.status != 200:
                raise Exception(f"HTTP Error: {response.status}")
            
            data = json.loads(response.read().decode("utf-8"))
        
        weather_description = data["weather"][0]["description"]
        temperature = data["main"]["temp"]
        feels_like = data["main"]["feels_like"]
        humidity = data["main"]["humidity"]
        wind_speed = data["wind"]["speed"]
        
        sunrise_time = datetime.fromtimestamp(data["sys"]["sunrise"]).strftime('%H:%M')
        sunset_time = datetime.fromtimestamp(data["sys"]["sunset"]).strftime('%H:%M')
        
        contents = {
            "city": city_name,
            "weather": weather_description,
            "temperature": temperature,
            "feels_like": feels_like,
            "humidity": humidity,
            "wind_speed": wind_speed,
            "sunrise": sunrise_time,
            "sunset": sunset_time
        }
        
        message = (f"{city_name}の現在の天気は{weather_description}、気温は{temperature:.1f}℃です。"
                   f"体感温度は{feels_like:.1f}℃、湿度は{humidity}%、風速は{wind_speed}m/sです。"
                   f"日の出は{sunrise_time}、日の入りは{sunset_time}です。")
        
        contents["message"] = message
        
    except Exception as e:
        contents = {"error": f"天気情報の取得中にエラーが発生しました: {str(e)}"}
    
    response_body = {"application/json": {"body": json.dumps(contents)}}
    
    action_response = {
        "actionGroup": event.get("actionGroup", ""),
        "apiPath": event.get("apiPath", ""),
        "httpMethod": event.get("httpMethod", ""),
        "httpStatusCode": 200,
        "responseBody": response_body,
    }
    
    api_response = {"messageVersion": "1.0", "response": action_response}
    return api_response
```
次、Lambda関数の設定から、アクセス権限を選び、リソースベースのポリシーステートメントから`agentsInvokeFunction`をチェック入れて編集します。
![D9F62485-1F2B-4371-8D9B-48EC43395C36_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/095151e4-9eda-1ba0-52f8-eee53914e555.jpeg)

ポリシーの編集画面でソース ARNに`エージェントのARN`を貼り付けて、アクションを`lambda:InvokeFunction`を選択します。
![AB1D2639-774A-4008-9916-DCD54E887E0F.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/be4fc3e9-c75a-dd9e-9fe2-2d90b2567d3c.jpeg)


エージェント ARNは該当エージェントのメイン画面から入手できます。
![A7903A5E-7753-4B50-B87F-769C590B4987.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/1e4e3848-688a-d535-e467-6eaacd1829da.jpeg)
設定完了したら、保存してAmazon Bedrockエージェントの画面に戻ります。

次に`エージェント向けの指示`を入力します。いわゆるプロンプトです。
今回の目的は、エージェントに天気について質問した際に、アクションを使ってリアルタイムの情報を取得し、それを元に回答してもらうことです。
また、OpenWeatherMap の API は都市名に英語でしか対応していません。そのため、日本語の都市名が入力された場合には、適切に英語に翻訳してから問い合わせてくれると便利です。

![名称未設定ファイル.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/2108f31a-7a19-ecb5-fed6-e12898cdb60c.png)

```text: プロンプト

あなたはリアルタイムの天気情報を取得してユーザーに回答するエージェントです。以下の条件に従って行動してください：

1.ユーザーからの一般質問に関して、任意に回答してください。
2.ユーザーから天気情報に関する質問はアクション使って回答してください。
3.ユーザーから日本語で都市名を指定された場合、その都市名を英語に翻訳してから使ってください。

ユーザーが指定しない場合、デフォルトの都市名は「Tokyo」としてください。
```
![B03CE26E-BEDA-4442-B607-4A6DE4E30AFA_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/6cae54af-15c6-237d-367c-d81e966e622e.jpeg)

入力完了しましたら、`保存`してから`準備`をクリックしてください。

![81FCABFE-ED72-4283-8AB8-7E7294FBE331_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/03e426fa-9292-1778-2fe0-4698d90d7c21.jpeg)

エージェントの右側にテストできるので、軽く試してみます。
![81859DC2-93BC-4071-A401-3EF6FC2AAFBF.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/a4df99cd-a8a2-01ea-a31f-0ac8c1fba350.jpeg)

予想通りの結果が返ってきましたね、次はフロントエンドの実装をやっていきます。

### チャット画面の実装

最初は Next.js プロジェクトを 0 から構築してすべて解説する予定でしたが、
尺の都合で、こちらのリポジトリをクローンしてご利用ください。
Star:star:いただけると励みになります:relaxed:

https://github.com/huanshenyi/chat-front

クローン後、ルートディレクトリの配下に`.env`ファイルを作ってください。

```text:.env
NEXT_PUBLIC_AWS_REGION=
NEXT_PUBLIC_AWS_ACCESS_KEY_ID=
NEXT_PUBLIC_AWS_SECRET_ACCESS_KEY=
NEXT_PUBLIC_AWS_SESSION_TOKEN=
NEXT_PUBLIC_AWS_AGENTID=
NEXT_PUBLIC_AWS_AGENT_ALIASID=
```

環境変数それぞれの意味を説明します。

- NEXT_PUBLIC_AWS_REGION

先作ったBenrock Agentが存在するリージョン、東京リージョンの場合:`ap-northeast-1`

- NEXT_PUBLIC_AWS_ACCESS_KEY_ID
- NEXT_PUBLIC_AWS_SECRET_ACCESS_KEY
- NEXT_PUBLIC_AWS_SESSION_TOKEN

`AWS Identity Center`を利用している方は、アクセスポータルからアクセスキーなどをコピーしてご使用ください。  
利用していない方は、Bedrock 用の IAM ユーザーを作成し、`AmazonBedrockFullAccess` ポリシーを付与した上で、アクセスキーとセキュリティキーをコピーしてご使用ください、IAM ユーザー利用される場合、`NEXT_PUBLIC_AWS_SESSION_TOKEN`は不要です。

- NEXT_PUBLIC_AWS_AGENTID

エージェントの概要一覧で確認できます。
![FE2F5330-34F8-4D17-9769-A6AE0FB37938_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/692a6b3f-fd9f-24f4-88c0-a4c5c5fccdcb.jpeg)

- NEXT_PUBLIC_AWS_AGENT_ALIASID

同じくエージェント概要一覧画面の最下部でエイリアス作成できます、作成された`エイリアンID`を利用しましょう

![85D87A2C-0301-45DD-ABF4-49B7E9722125.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/ba34219c-eb71-cca5-e2dc-a61dd558b6f5.jpeg)

ローカルでの動作確認は下記のコマンドを実行してください。
```
$ npm i
$ npm run dev
```

### 最後に

完成品は下記の図のような感じになります。

![372ECD4A-10AC-4FD1-A7BC-2579C400C5EA.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/a6de9442-7d3b-4299-d838-47b0e5093fb9.jpeg)

デプロイやIaCに興味がある方がいらっしゃれば、今度また別の記事でご紹介します:writing_hand_tone1: