生成AIの答えが微妙すぎる？Bedrock ToolUseで一歩先の応答を実現してみた

# Situation(状況)

自分の趣味のことについて下記のように生成AIに聞いてみても、当たり障りのないことまでしか答えてくれないことがありました。(3個の質問に対する回答がすべて微妙・・・)
具体的な回答をしてくれるような基盤が欲しいので作ってみました。
※ 自分の趣味だと同じ趣味を持っている人しかわからないので、ここでは天気について質問しました。

**◆質問とその回答(Bedrockのプレイグラウンドを使用)**
Bedrockのプレイグラウンドを使用した際の質問とそれに対しての回答をまとめた表になります。
一番左の列に回答に対しての結果を〇,×で記載しています。
回答内容が、「例年7月ってこうですよね？」と2024年7月はこうだったという回答ではないため、すべて結果を×にしています。
| 結果 | 質問 | 回答 |
| ---- | ---- |---- |
|×| 7月の天気について教えて | 主な特徴<br>本格的な夏到来。気温が高く蒸し暑い。湿度が高い。梅雨明けが多い時期。<br><br>気温<br>平均最高気温：25-32℃程度<br>平均最低気温：20-25℃程度<br><br>天候の特徴<br>梅雨明け後は晴れの日が多い。夕立が発生しやすい。台風の接近・上陸の可能性あり。<br><br>注意点<br>熱中症への警戒が必要。急な雨への備え。紫外線対策が重要。<br><br>地域による違い<br>北海道：比較的涼しい。本州：蒸し暑い。沖縄：既に梅雨明けしている場合が多い。 |
|×| 2024年の7月の天気で、地域ごとで特徴的な記録を教えてもらえる？ | 予報の特徴<br>- 気象庁の長期予報は3ヶ月先までの傾向のみ示すため、詳細な予測は難しい<br>- エルニーニョ現象の影響が予測される<br><br>予測される傾向<br>- 全国的に平年より気温が高くなる可能性（30%～40%）<br>- 太平洋側で降水量が平年並みか多い予想<br><br>地域ごとの予測（参考）<br>- 北日本：気温は平年並みか高め、降水量は平年並み<br>- 東日本：気温は高め、降水量は平年並みか多い<br>- 西日本：気温は高め、降水量は平年並みか多い<br>- 沖縄・奄美：気温は高め、降水量は平年並み<br><br>注意点<br>- 長期予報は傾向のみを示す参考情報<br>- 実際の天候は直前の予報で確認が必要<br>- エルニーニョの影響で例年と異なる気象パターンの可能性あり |
|×| 2024年の7月で台風関連の情報ある？ | 発生予測<br>- 7月の平均的な発生数：3.6個<br>- 2024年は例年より発生数が若干多めの可能性<br>- エルニーニョ現象の影響で台風の進路が例年と異なる可能性<br><br>注目ポイント<br>- エルニーニョ年は台風が日本の東側を通過しやすい傾向<br>- 南シナ海での発生が多くなる可能性<br>- 勢力が強まりやすい気象条件となる予測<br><br>警戒点<br>- 7月は梅雨前線と台風の複合災害に注意<br>- 海水温が高めで、急発達する可能性<br>- 雨量が多くなりやすい予報<br><br>備考<br>- 現時点での予測は参考程度<br>- 発生・進路は直前にならないと正確な予測は困難<br>- 気象庁等の最新情報の確認が重要 |


# Task(課題)

Situation(状況)で記載しているように今回質問した3つの質問を具体性のある質問にしていきたいので
下記3つのやり方を試してみました。

１．システムプロンプトに特定のサイトのURLを埋め込む
２．AIエージェントを使ったサイト内の情報読み取り
３． 最後の希望、ToolUseを用いたサイト内の情報読み取り

# Action(行動)

## １．システムプロンプトに特定のサイトのURLを埋め込む

BedrockのシステムプロンプトにURLを埋め込んで
「このサイトの内容に従って回答して」というプロンプトを埋め込んでみました。
ただ、 BedrockのプロンプトにURLを埋め込んでもURLの内容を読み取るということはまでは出来ないという結果に。。。

※システムプロンプト
```
'あなたは優秀なAIアシスタントです。ユーザーからの質問や指示に対して、以下の手順で適切な回答を提供してください。'\
'1.依頼内容が実績と予測の差について、Web(https://playvalorant.com/ja-jp/news/game-updates/valorant-patch-notes-9-10/)内を検索し推測を行い、結果をユーザーに提供してください。'\
'Web内のどの文章を引用したか、参考にした文章全体を末尾に「引用：」として記載してください。'\
'また、回答の最後にURLを「■参照元:<url>」と、ファイルを「■参照元:<file>」と出力してください。'\
'2.回答する際は、ユーザーの知識レベルや関心に合わせて、技術的な詳細の量を調整するなど、わかりやすく親しみやすい言葉遣いを心がけてください。'\
'以上の手順に沿って、ユーザーの期待に応える高品質なアシスタントサービスを提供してください。'}] 
```

## ２．AIエージェントを使ったサイト内の情報読み取り

プロンプトにURLの情報を書き込んで、サイトの情報を読み取ってもらうことはできないので、AIエージェントを作成し、エージェントからLambdaを起動し、Lambdaでサイトの情報を読み取ってそれを用いて、特定のサイトにフォーカスした回答ができるようにしたい！

ただ今度は、AIエージェント内で起動したLambdaからのレスポンスのサイズが大きすぎて※1エラーが出てしまい断念。。。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3790709/f2856b37-7e13-b09c-df56-967bb94d6d69.png)

※1 LambdaからBedrockまでの25KBまで
https://docs.aws.amazon.com/bedrock/latest/userguide/agents-lambda.html#agents-lambda-response
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3790709/a1b65400-2ed5-d5c1-b1c7-9210b6caf553.png)


## ３． 最後の希望、ToolUseを用いたサイト内の情報読み取り

ToolUseなら、AIエージェントを用いた際に発生したLambdaのレスポンスサイズが大きすぎて発生したエラーが回避できるはずとのことをAWSサポートから教えていただいたので、こちらを実践！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3790709/d38594b1-8df0-1966-8d37-a685fb7a7fd1.png)


### ToolUseとは

まず、ToolUseについて簡単に説明します。

AWS BedrockのTool Use（ツール使用）機能は、大規模言語モデル（LLM）が外部ツールやAPIを利用できるようにする仕組みです。この機能を利用することで、インターネットや外部APIと通信して取得した情報をモデルに組み込んだ応答が可能になります。
→ 「３． 最後の希望、ToolUseを用いたサイト内の情報読み取り」に記載したイラストにあるインターネットと通信している部分が、外部ツール/APIを使用している部分になります。

### ConverseAPIについて
次に、ConverseAPIについて説明します。

ConverseAPIは、AWS Bedrockの「プレイグラウンド → Chat / Text」で使用されているAPIで、プロンプトのやり取りをプログラム的に行うためのものです。このAPIを使うことで、プレイグラウンド上と同様にプロンプトを送信して生成AIモデルから回答を得られます。

さらに、このConverseAPIにはToolUseを組み込むことが可能です。これにより、通常の生成AIの会話機能に加え、外部ツールやAPIの情報を利用した高度な応答が可能になります。

## ToolUseとConverseAPIの組み合わせ

これらを組み合わせることで、次のようなフローで動作します：

1. ConverseAPIがユーザーのプロンプトを受け取る
    - 例：「2024年7月の天気について教えて」といった質問を受け取ります。
2. ToolUseが必要な場合、外部ツールを呼び出す
    - 例えば、特定のWebサイトの情報を取得するためにカスタムツールを呼び出します。
3. 外部ツールから取得した情報をConverseAPIに戻す
    - Webサイトから取得した天気情報をConverseAPIに返します。
4. 生成AIが外部情報を統合し、回答を生成
    - 「全国的に高温で、7月7日は静岡で40.0℃を記録した」など、外部情報を反映した回答を生成します。

この仕組みにより、ユーザーの質問に対して具体的で詳細な回答が可能になります。

## ConverseAPI/ToolUse実装
ConverseAPI/ToolUseを組み合わせたものが下記のコードになります。

※ 参考ドキュメント
[1]ConverseAPI/ConverseStreamAPIの使用例
https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/tool-use-examples.html

```
import logging
import json
import boto3
import requests
from bs4 import BeautifulSoup
import time

from botocore.exceptions import ClientError

class WeatherInfoNotFoundError(Exception):
    """天気情報が見つからない場合のカスタム例外"""
    pass

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)

def get_weather_info():
    """指定されたURLから天気情報を取得します。
    戻り値:
        list: 天気情報に関連するテキスト内容を抽出したリスト。
    """
    
    print("get_weather_info start")
    
    weather_info_contents = ""
    call_url = "https://www.data.jma.go.jp/cpd/longfcst/monthly/202407/202407m.html"
    
    # 指定されたURLにGETリクエストを送信してコンテンツを取得
    response = requests.get(call_url)
    soup = BeautifulSoup(response.content, "html.parser")
    
    texts = []
    
    # クラス"honbun"を持つ<p>タグ内の内容を抽出
    for tmp_content in soup.find_all("p", class_="honbun"):
        # HTMLタグを削除して純粋なテキストを取得
        text = tmp_content.get_text(strip=True)
        if text:
            texts.append(text)
        
        weather_info_contents = texts

    logger.info("weather_info_contents %s", weather_info_contents)

    return weather_info_contents

def generate_text(bedrock_client, model_id, tool_config, input_text):
    """Amazon Bedrockを使用してToolUse機能を活用してテキストを生成します。
    引数:
        bedrock_client: Boto3のBedrockランタイムクライアント。
        model_id (str): Amazon BedrockのモデルID。
        tool_config (dict): ToolUseの設定。
        input_text (str): 応答を生成するためのユーザー入力。
    """
    
    logger.info("Generating text with model %s", model_id)
    
    # ユーザー入力メッセージを準備
    messages = [{
        "role": "user",
        "content": [{"text": input_text}]
    }]
    
    # 入力をBedrockに送信して応答を取得
    response = bedrock_client.converse(
        modelId=model_id,
        messages=messages,
        toolConfig=tool_config
    )
    
    output_message = response['output']['message']
    messages.append(output_message)
    stop_reason = response['stopReason']
    
    print("stop_reason:" + stop_reason)
    
    if stop_reason == 'tool_use':
        # ToolUseが要求された場合、ツールとのやり取りを処理
        tool_requests = response['output']['message']['content']
        for tool_request in tool_requests:
            if 'toolUse' in tool_request:
                tool = tool_request['toolUse']
                logger.info("Requesting tool %s. Request: %s", tool['name'], tool['toolUseId'])

                text = "weather_info"
                if text in tool['name']:
                    tool_result = {}
                    try:
                        # カスタム関数を使用して天気情報を取得
                        weather_info_contents = get_weather_info()
                        
                        # Bedrockに返すツール結果を準備
                        tool_result = {
                            "toolUseId": tool['toolUseId'],
                            "content": [{"json": {"weather_info_contents": weather_info_contents}}]
                        }
                    except WeatherInfoNotFoundError as err:
                        # 天気情報が見つからない場合の処理
                        tool_result = {
                           "toolUseId": tool['toolUseId'],
                           "content": [{"text": err.args[0]}],
                            "status": 'error'
                        }
                    
                    tool_result_message = {
                        "role": "user",
                        "content": [
                            {
                                "toolResult": tool_result 
                            }
                        ]
                    }
                    
                    messages.append(tool_result_message)
                    
                    # レート制限を回避するための遅延を追加
                    time.sleep(60)
                    
                    # ツール結果をBedrockに再送信してさらに処理
                    response = bedrock_client.converse(
                        modelId=model_id,
                        messages=messages,
                        toolConfig=tool_config
                    )
                    
                    output_message = response['output']['message']      

        # モデルからの最終応答を出力
        for content in output_message['content']:
            print(json.dumps(content, indent=4, ensure_ascii=False))
    else: 
        # ToolUseを使用しない場合、初期応答を出力
        for content in output_message['content']:
            print(json.dumps(content, indent=4, ensure_ascii=False))

def main():  
    logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
    
    model_id = "anthropic.claude-3-5-sonnet-20240620-v1:0"
    input_text = "7月の天気について教えて"
   
    # ToolUseの設定を定義
    tool_config = {
        "tools": [
            {
                "toolSpec": {
                    "name": "weather_info",
                    "description": "2024年7月の天気に関する情報",
                    "inputSchema": {
                        "json": {
                            "type": "object",
                            "properties": {},
                            "required": []
                        }
                    }
                }
            }
        ]
    }
    
    bedrock_client = boto3.client(service_name='bedrock-runtime')
    
    try:
        print(f"Question: {input_text}")
        # 設定されたToolUseを使用してBedrockでテキストを生成
        generate_text(bedrock_client, model_id, tool_config, input_text)
        
    except ClientError as err:
        # AWSクライアントエラーを処理
        message = err.response['Error']['Message']
        logger.error("A client error occurred: %s", message)
        print(f"A client error occurred: {message}")
    
    else:
        print(f"Finished generating text with model {model_id}.")
    
if __name__ == "__main__":
    main()

```

# Result(どんな結果・成果を導き出したか)
Situation(状況)で記載した3つの質問が下記のように3個中3個具体性のある回答になりました！
こんな風に回答がいけてなかったとしても、ToolUseを用いた特化型基盤を作ることで具体的な回答をさせることができるので、使ってみてください！

**◆質問とその回答(ConverseAPI/ToolUse実装を使用)**
ConverseAPI/ToolUse実装を使用した際の質問とそれに対しての回答をまとめた表になります。
一番左の列に回答に対しての結果を〇,×で記載しています。
回答内容が、Situationに記載したプレイグラウンドを使用した場合と異なり、「例年7月ってこうですよね？」から「2024年7月はこうだった」という回答に3つすべてなっていたため、結果も3つ×から3つ〇に変わりました。

| 結果 | 質問 | 回答 |
| ---- | ---- |---- |
|〇| 7月の天気について教えて | 気温<br>- 全国的にかなり高温<br>- 東日本と沖縄・奄美では、1946年の統計開始以来、7月として最高気温を記録<br>- 全国153の気象台等のうち62地点で、7月の月平均気温が歴代1位<br>- 日本の月平均気温の偏差は+2.16℃で、1898年以降の7月として過去最高を更新<br><br>降水量<br>- 北日本日本海側でかなり多く、東日本日本海側と沖縄・奄美で多い<br>- 東日本太平洋側では少ない<br>- その他の地域では平年並み<br><br>日照時間<br>- 沖縄・奄美ではかなり多く、北日本太平洋側、東日本太平洋側、西日本太平洋側では多い<br>- その他の地域では平年並み<br><br>特徴的な現象<br>- 7日：静岡で日最高気温40.0℃を記録<br>- 25日頃：山形県で線状降水帯が発生、大雨特別警報発表<br>- 29日：佐野で日最高気温41.0℃を記録<br><br>梅雨明け<br>- 九州南部：17日頃<br>- 関東甲信・東海：18日頃<br>- 四国：19日頃<br>- 中国・近畿：21日頃<br>- 九州北部：22日頃<br><br>台風<br>- 2つの台風（第3号と第4号）が発生<br>- 台風第3号が沖縄地方に接近し、大雨や大荒れの天候 |
|〇| 2024年の7月の天気で、地域ごとで特徴的な記録を教えてもらえる？ | 全国的な傾向<br>- 非常に暑い月<br>- 月平均気温偏差は+2.16℃（1898年以降の7月として最高記録）<br><br>地域別の特徴<br>【沖縄・奄美】<br>- 月平均気温が統計開始以降（1946年～）7月として1位<br>- 中旬まで日照時間がかなり多い<br><br>【東日本】<br>- 月平均気温が統計開始以降（1946年～）7月として1位タイ<br>- 7月下旬の平均気温偏差が+2.8℃（1946年以降最高）<br><br>【北日本日本海側】<br>- 降水量がかなり多い<br>- 7月25日頃、山形県で線状降水帯による記録的大雨<br><br>【西日本】<br>- 7月下旬の平均気温偏差が+2.2℃（1946年以降最高）<br><br>顕著な気温記録<br>- 7月7日：静岡で40.0℃<br>- 7月29日：佐野で41.0℃<br><br>梅雨明け（速報値）<br>- 九州南部：7/17頃<br>- 関東甲信・東海：7/18頃<br>- 四国：7/19頃<br>- 中国・近畿：7/21頃<br>- 九州北部：7/22頃<br><br>台風の影響<br>- 7月下旬：台風第3号が沖縄地方に接近<br>- 大雨や荒れた天気をもたらす |
|〇| 2024年の7月で台風関連の情報ある？ | 台風の発生<br>- 第3号台風：7月20日発生<br>- 第4号台風：7月21日発生<br><br>台風の接近<br>- 第3号台風が沖縄地方に接近<br><br>台風の影響<br>- 沖縄・奄美地域：7月下旬（21日〜31日頃）に大雨や大荒れ<br>- 7月下旬の降水量がかなり多く、日照時間は少ない<br><br>台風の上陸<br>- 日本本土への上陸なし<br><br>全体的な状況<br>- 台風活動は比較的穏やか<br>- 影響は主に沖縄・奄美地域に限定<br>- 他の気象現象（高温・大雨）の方が顕著 |