Amazon Bedrock (Claude v2) をboto3で触ってみた ~ Tipsを添えて ~

Amazon Bedrockが一般公開(GA)されたとのことで、早速触ってみたいと思います！

マネジメントコンソールのPlaygroundで触ってみるのも面白そうだが、ここではboto3での利用を想定してやっていく。

https://aws.amazon.com/jp/blogs/aws/amazon-bedrock-is-now-generally-available-build-and-scale-generative-ai-applications-with-foundation-models/


まずAmazon Bedrockを利用可能な最新バージョンまでboto3やbotocoreのバージョンを上げておく。

※ anthropicは後述のAnthropic社のClaudeモデルを利用する際に、token数の計算をしたいなと思いインストールしておきましたがBedrock自体の動作に必須ではないです。
```python
%pip install -U pip
%pip install -U botocore boto3
%pip install anthropic
```

以下のバージョンまで上がっているのを確認する。
- boto3 >= 1.28.57
- botocore >= 1.31.57


```python
%pip list | grep -E 'botocore|boto3' | awk '{print $1, $2}'
```


## モデルを利用可能な状態に設定
初めてそのAWSアカウント、リージョンでBedrockを利用する場合は、マネジメントコンソールのModel Accessページからモデルを利用可能にする設定が必要。

詳細はドキュメントをご確認。
https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html#add-model-access

## シンプルなBedrockの利用例

それでは早速boto3でBedrockを使ってみる。モデルは日本語もいけるAnthropic社のClaude v2。

invoke_modelを叩くので、boto3 clientにはbedrock-runtimeを利用している。


```python
import boto3
import json

# InvokeModel, InvokeModelWithStreamingResponse APIを呼び出す場合のみ 
# "bedrock-runtime" を利用し、それ以外では" bedrock" を利用するとのことで注意
bedrock_runtime_client = boto3.client(
    service_name="bedrock-runtime",
    region_name="us-east-1"
)

text = "こんにちは"
# Anthropic社のClaudeモデルのプロンプトフォーマットを参照： https://docs.anthropic.com/claude/docs/introduction-to-prompt-design#human--assistant-formatting
prompt = f"\n\nHuman: {text}\n\nAssistant:"

response = bedrock_runtime_client.invoke_model(
        body=json.dumps({"prompt": prompt, "max_tokens_to_sample": 100}), modelId="anthropic.claude-v2"
)

response_body = json.loads(response.get("body").read())
print(response_body.get("completion"))

```
     はい、こんにちは。どうしましたか?


BedrockでClaudeを利用する際は、上記のプロンプトのフォーマットに従わないと以下のエラーが出るので注意。
```terminal
ValidationException: An error occurred (ValidationException) when calling the InvokeModel operation: Invalid prompt: prompt must start with "

Human:" turn, prompt must end with "

Assistant:" turn
```

次に利用可能なモデルを確認してみる。こちらはboto3.clientでbedrockを指定している。

```python
# 利用可能なモデルの確認
bedrock_client = boto3.client(
    service_name="bedrock",
    region_name="us-east-1"
)

for model in bedrock_client.list_foundation_models()["modelSummaries"]:
    print(model["modelId"])
```

    amazon.titan-tg1-large
    amazon.titan-e1t-medium
    amazon.titan-embed-g1-text-02
    amazon.titan-text-express-v1
    amazon.titan-embed-text-v1
    stability.stable-diffusion-xl
    stability.stable-diffusion-xl-v0
    ai21.j2-grande-instruct
    ai21.j2-jumbo-instruct
    ai21.j2-mid
    ai21.j2-mid-v1
    ai21.j2-ultra
    ai21.j2-ultra-v1
    anthropic.claude-instant-v1
    anthropic.claude-v1
    anthropic.claude-v2
    cohere.command-text-v14

## もう少しリアルなBedrockの利用例

ここからはもう少しリアルなプロンプトで試してみる。

```python
import json
import os
import sys
import time
import boto3
from anthropic import Anthropic

def generate_anthropic(text, model):
    # Pricingは変更される可能性があるため料金ページを参照。 https://aws.amazon.com/jp/bedrock/pricing/
    if model == "claude-v2":
        prompt_cost_per_token = 0.00001102
        completion_cost_per_token = 0.00003268
        model_id="anthropic.claude-v2" 
    elif model == "claude-v1":
        prompt_cost_per_token = 0.00001102
        completion_cost_per_token = 0.00003268
        model_id="anthropic.claude-v1" 
    elif model == "claude-instant-v1":
        prompt_cost_per_token = 0.00000163
        completion_cost_per_token = 0.00000551
        model_id="anthropic.claude-instant-v1" 
    else:
        raise Exception("this model name is not supported here: " + model)

    prompt = f"\n\nHuman: {text}\n\nAssistant:"

    # Claudeのパラメータの詳細はこちらに：https://docs.anthropic.com/claude/reference/complete_post
    body = json.dumps({"prompt": prompt, "max_tokens_to_sample": 1000, "temperature": 0.1})

    response = bedrock_runtime_client.invoke_model(
        body=body, modelId=model_id
    )
    
    response_body = json.loads(response.get("body").read())
    completion = response_body.get("completion")
    print(completion)

    # Claudeを利用した際のtoken数と推定料金を測定してみる。
    anthropic_client = Anthropic()
    prompt_tokens = anthropic_client.count_tokens(prompt)
    completion_tokens = anthropic_client.count_tokens(completion)

    prompt_cost = prompt_cost_per_token * prompt_tokens
    completion_cost = completion_cost_per_token * completion_tokens
    total_cost = prompt_cost + completion_cost

    print(f'prompt_tokens: {prompt_tokens:,}, completion_tokens: {completion_tokens:,}, prompt_cost: ${prompt_cost:.9f}, completion_cost: ${completion_cost:.9f}, total_cost: ${total_cost:.9f}')

bedrock_runtime_client = boto3.client(
    service_name="bedrock-runtime",
    region_name="us-east-1"
)

model = 'claude-v2'
```


```python
text = """
以下のコメントの感情を、「ネガティブ: n」「ポジティブ: p」のいずれかに分類してください。
1．"帰ったら宿題やらなきゃなあ…"
2. "ここのラーメン超うまい"
3. "購入して半年なのに壊れてしまいました。残念です。"
5. "使い方が理解できなかった"
6. "強度が低く使いものにならない"
7. "ぜひ今後も継続して利用したいですね"
8. "思ったより丈夫で軽かったので驚きました"

結果についてはJSON形式で返してください。それ以外の文字列は返却しないでください。
JSONは下記のような形式で1行で出力してください。
{"1": "p", "2": "n"}
"""
generate_anthropic(text, model)
```

     {"1": "n", "2": "p", "3": "n", "5": "n", "6": "n", "7": "p", "8": "p"}
    prompt_tokens: 270, completion_tokens: 42, prompt_cost: $0.002975400, completion_cost: $0.001372560, total_cost: $0.004347960



```python
text = """
以下の"入力文章"を、"分野"のいずれか一つに分類してください。
分野ID、つまり整数のみを出力し、分野ID以外の文字列は出力しないでください。

# 入力文章
ついにさつまいもが美味しい季節がついにやってきましたね。王道の焼き芋や、みんな大好き大学芋、最近はサラダやポタージュスープなど色々な形で食卓を彩る秋には欠かせない食材となっています。そこで、今回の特集では大学芋の美味しい作り方について紹介していきます。

# 分野 (分野ID: 分野名)
1: テクノロジー, 2: 料理, 3: スポーツ, 4: 美容, 5: 生活, 6: ビジネス, 7: 恋愛, 8: エンタメ, 9: その他
"""
generate_anthropic(text, model)
```

     2
    prompt_tokens: 308, completion_tokens: 1, prompt_cost: $0.003394160, completion_cost: $0.000032680, total_cost: $0.003426840



```python
text = """
あなたはIT周りを得意とする優秀な翻訳家です。以下の"入力文章"を、英語から日本語に翻訳してください。翻訳結果は<result></result>タグの中に含めて下さい。

# 入力文章
It’s common to initially think of an application’s availability as a single target for the application as a whole. However, upon closer inspection, we frequently find that certain aspects of an application or service have different availability requirements. For example, some systems might prioritize the ability to receive and store new data ahead of retrieving existing data. Other systems prioritize real-time operations over operations that change a system’s configuration or environment. Services might have very high availability requirements during certain hours of the day, but can tolerate much longer periods of disruption outside of these hours. These are a few of the ways that you can decompose a single application into constituent parts, and evaluate the availability requirements for each. The benefit of doing this is to focus your efforts (and expense) on availability according to specific needs, rather than engineering the whole system to the strictest requirement.
"""
generate_anthropic(text, model)
```

     <result>
    アプリケーションの可用性を、アプリケーション全体の単一の目標として最初に考えるのは一般的です。
    しかし、より注意深く調べると、アプリケーションやサービスの特定の側面には、異なる可用性要件があることが頻繁にわかります。
    例えば、あるシステムは、既存のデータの取得よりも、新しいデータの受信と保存を優先する場合があります。
    他のシステムは、リアルタイムの操作を、システムの構成や環境を変更する操作よりも優先します。
    サービスには、1日の特定の時間帯には非常に高い可用性要件がある一方で、それ以外の時間帯でははるかに長い中断を許容できる場合があります。
    これらは、単一のアプリケーションを構成要素に分解し、各要素の可用性要件を評価する方法のいくつかの例です。
    これを行う利点は、最も厳しい要件に合わせてシステム全体を設計するのではなく、特定のニーズに応じて可用性に焦点を当てることができることです。
    </result>
    prompt_tokens: 264, completion_tokens: 395, prompt_cost: $0.002909280, completion_cost: $0.012908600, total_cost: $0.015817880



```python
text = """
以下のユーザー属性やクエリ文章に合う観光地を、観光地候補から2つ選び、出力フォーマットに合わせて回答してください。
# ユーザー属性
性別: 男性
年齢: 30代
興味のある観光地カテゴリ: 温泉, 美術館

# クエリ文章
軽井沢でおすすめのゆっくりできるところを教えて

# 観光地候補 (観光地名: 観光地の詳細)
- Aホテル: 木造の西洋式ホテルとして有名なクラシックホテルです。
- ショッピングプラザB: リゾート型ショッピングモール。自然とショッピングモールの融合を目指したこのショッピングモールは、100を超えるショップが展開されています
- Cの湯: 源流かけ流しを楽しむならここ。和モダンな銭湯をお楽しみいただけます
- Dカフェ: テラスでは大自然を眺めながらコーヒーをお楽しみいただけます
- E美術館: 広大な森の中に佇むF美術館。ヨーロッパの様々な展示品をご覧ください

# 出力フォーマット
２つの観光地名(name)と、あたながユーザー属性やクエリ文章からそれを選定した理由(reason)を、以下のようなJSON形式で返してください。JSON以外の文字列は絶対に返却しないでください。
{"results": [{"name": "","reason": ""},{"name": "","reason": ""}]}
"""
generate_anthropic(text, model)
```

     {
      "results": [
        {
          "name": "Cの湯",
          "reason": "30代の男性ユーザーで温泉に興味があるため、源流かけ流しの温泉施設Cの湯がゆっくりできそうです。"
        },
        {
          "name": "E美術館", 
          "reason": "男性ユーザーで美術館に興味があるため、広大な森の中にあるE美術館がゆっくり見学できそうです。"
        }
      ]
    }
    prompt_tokens: 560, completion_tokens: 165, prompt_cost: $0.006171200, completion_cost: $0.005392200, total_cost: $0.011563400



```python
def extract_phrases(question):
    text = f"""
以下の"入力文章"から、"条件"に従って日時(date)、人数(guests)、キーワード(keywords)を抽出し、"出力フォーマット"に合わせて回答してください。"例"に例を示します。

# 入力文章
{question}

# 条件
- 現在の日時は、"2023-08-05 18:30" です。
- 入力文章が日時(具体的な時間や今日、明日などの指示)に言及していない場合は"date": nullと答えて下さい。日時に関する記載がある場合、現在の日時も参考にして正しいフォーマットで記載してください。明日、明後日のような表現の場合は、現在の日時の翌日、翌々日を返す必要があることに注意して下さい。
- 人数が入力文章に記載されていない場合や、人数が11人を超える場合は"guests": nullを出力してください。1-10人の場合は、人数(guests)にその数を整数で記載してください。
- キーワード(keywords)には、場所に関係する具体的な地名や駅名の他に、店舗の特徴を表す単語(例：居酒屋、中華、静か）というような単語を中心に0から3個抽出してください。0個の場合は"keywords":nullと記載してください。
- 入力文章に含まれていない単語は絶対に回答に含めないで下さい。

# 例
「博多駅周辺の居酒屋」と入力された場合、日時や人数への言及がないので、{{"date": null, "guests": null, "keywords": ["居酒屋", "博多駅"]}}と回答します。
「明後日の12時から3人で焼肉が食べれる渋谷の店を探してます！」と入力された場合、{{"date": "2023-08-07 12:00", "guests": "3", "keywords": ["渋谷", "焼肉"]}}と回答します。
「眠いですね」と入力された場合は日時、人数、場所や店舗の特徴いずれにも言及されていないので、{{"date": "null", "guests": null, "keywords": null}}と回答します。

# 出力フォーマット
結果についてはJSON形式で返してください。それ以外の文字列は絶対に返却しないでください。
以下にJSONのスキーマとその例を示します。
{{"date": "2023-02-10 19:00", "guests": "4", "keywords": ["新宿", "イタリアン"]}}
"""
    print(question)
    generate_anthropic(text, model)

extract_phrases("今日の19時から焼肉が食べれる場所を探してます")
extract_phrases("明日の19時から4人で六本木周辺で焼肉が食べれる店を探してます！")
extract_phrases("渋谷の居酒屋")
extract_phrases("こんにちは")
```

    今日の19時から焼肉が食べれる場所を探してます
     {"date": "2023-08-05 19:00", "guests": null, "keywords": ["焼肉"]}
    prompt_tokens: 817, completion_tokens: 29, prompt_cost: $0.009003340, completion_cost: $0.000947720, total_cost: $0.009951060
    明日の19時から4人で六本木周辺で焼肉が食べれる店を探してます！
     {"date": "2023-08-06 19:00", "guests": 4, "keywords": ["六本木", "焼肉"]}
    prompt_tokens: 829, completion_tokens: 36, prompt_cost: $0.009135580, completion_cost: $0.001176480, total_cost: $0.010312060
    渋谷の居酒屋
     {"date": null, "guests": null, "keywords": ["渋谷", "居酒屋"]}
    prompt_tokens: 804, completion_tokens: 28, prompt_cost: $0.008860080, completion_cost: $0.000915040, total_cost: $0.009775120
    こんにちは
     {"date": null, "guests": null, "keywords": null}
    prompt_tokens: 798, completion_tokens: 16, prompt_cost: $0.008793960, completion_cost: $0.000522880, total_cost: $0.009316840



```python
text = """
あなたは優秀な会社員です。議論の論点を正確に理解し、わかりやすく回答することができます。
末尾の会議内容を、以下のテンプレートでまとめてください。該当する項目が存在しない場合は「不明」と記載してください。会議で言及されていないことをまとめに載せないでください。

打ち合わせ情報: 打ち合わせの基本的な情報を提供します。これには以下の項目が含まれます。
- 打ち合わせのテーマ（一行で簡潔に）
- 打ち合わせの場所（オンラインの場合はその旨を記述）
- 出席者リスト
- 議事録作成者（今回は「議事録AI」と書いてください）
議論の内容: 打ち合わせ中の議論の要約を記載します。意思決定に至るまでのプロセスや、異なる意見や提案内容も記載します。箇条書きで記載してください。
決定事項: 打ち合わせで決定された内容を明記します。
アクションアイテム: 打ち合わせの中で生じたタスクや責任を明記します。誰が、何を、いつまでに行うかという情報を記載します。
次回打ち合わせのスケジュール: 次回の打ち合わせの日時や場所を記載します。（具体的な日時の言及がなかった場合は、来週月曜、来月半ばなどのキーワードがあればそれを、なければ不明と記載してください）

----
中村事業部長: さて、皆様、本日は出席いただきありがとうございます。この打ち合わせでは新製品「A」の現状のコンセプトや各チームの状況を確認していきましょう。
伊藤課長: 「A」は次世代のヘルスケアガジェットで、我々が提供するプラットフォームを通して、人々の健康をサポートするというビジョンがあります。ターゲット層は、30-50歳の中年層で、初年度の予想販売台数は200,000台です。
高橋R&Dマネージャー: 新製品「A」の開発には、センサー技術の統合やバッテリーの効率化など、いくつかの技術的な問題がありますが、それに対するソリューションはすでに検討しています。開発期間は1年と計画しており、必要なリソースも確保しています。
小林マーケティングディレクター: 当社の「A」は、競合製品に対して操作が簡単で直感的な点が強みです。この中年層に特に響くよう、ウェブとSNSを使ったマーケティング戦略を検討しています。
佐々木デザインリーダー: 「A」のデザインはモダンでありながらも機能的で、利便性と美しさを両立させることを目指しています。これにより、ブランドの価値も向上するでしょう。
渡辺エンジニア: 開発計画全体については基本的に問題ないと考えていますが、センサー技術の統合に際して、セキュリティの問題もしっかりと考慮すべきだと思います。
中村事業部長: さて、各部署からの質問や提案、意見があればどうぞ。
小林マーケティングディレクター: センサー技術の統合について、それがどのように顧客の健康に貢献するのか具体的な例がありますか？
渡辺エンジニア: はい、センサー技術を用いて、心拍数、睡眠の質、運動量などをトラッキングできます。ユーザーは自分の健康状態をより詳細に把握し、生活の改善につなげることができます。
佐々木デザインリーダー: 製品のUIはどのように設計されていますか？
高橋R&Dマネージャー: UIはシンプルで直感的な操作を前提としたデザインを考えています。ユーザーフィードバックを収集して、必要な調整も行います。
伊藤課長: 小林さん、SNSを使ったマーケティング戦略についてもう少し詳しく教えてください。
小林マーケティングディレクター: もちろんです。SNSでの広告や、インフルエンサーとのパートナーシップを通じて製品の認知度を高める予定です。ユーザーからの評価やコメントを活用して、製品改善やブランドイメージの確立にも繋げます。
中村事業部長: では、今日の会議をまとめましょうか。今日の会議で出た意見や提案を基に、次回の会議でより具体的な計画を議論することにします。次回の会議は来週水曜の14時で、今日と同様にオンラインでお願いします。よろしくお願いします。
"""
generate_anthropic(text, model)
```

     打ち合わせ情報:
    
    - 打ち合わせのテーマ: 新製品「A」の現状確認
    - 打ち合わせの場所: オンライン
    - 出席者リスト: 中村事業部長、伊藤課長、高橋R&Dマネージャー、小林マーケティングディレクター、佐々木デザインリーダー、渡辺エンジニア
    - 議事録作成者: 議事録AI
    
    議論の内容:
    
    - 新製品「A」は健康管理ガジェットで、30-50歳の中年層をターゲットにしている
    - 技術面ではセンサーの統合やバッテリー効率化などの課題があるが対策を検討中
    - マーケティングではSNSを活用した戦略を検討中
    - デザインは機能性と美しさのバランスを目指している
    - セキュリティへの配慮が必要との意見が出された
    - センサー技術が健康管理にどう貢献するかを説明
    - UIデザインはシンプルで直感的な操作を目指していると回答
    - SNSマーケティングの戦略についてより詳細な説明がなされた
    
    決定事項:
    - 次回の会議でより具体的な計画を議論する
    
    アクションアイテム: 
    - センサー技術の健康管理への貢献についての説明資料を作成 (渡辺)
    - UIデザインの詳細とユーザーフィードバック収集の計画を作成 (高橋)
    - SNSマーケティングの具体的な戦略を提示 (小林)
    
    次回打ち合わせのスケジュール:
    - 来週水曜 14時、オンライン
    prompt_tokens: 1,758, completion_tokens: 609, prompt_cost: $0.019373160, completion_cost: $0.019902120, total_cost: $0.039275280


## プロンプトについて
実際はClaude v2以外にClaude Instantを利用する選択肢もあるだろうし、プロンプト自体もAnthropic社のPrompt Designのドキュメントには結構詳しく書いてあるので工夫の余地が多分にありそう。そもそもプロンプトの指定自体は英語でもいいかもしれない。

https://docs.anthropic.com/claude/docs/introduction-to-prompt-design

以下の動画でも、一部はClaudeらしいTipsが紹介されている。

https://www.youtube.com/watch?v=OfUn6HjwXhI

- タスクは正確且つ詳細に伝えろ
- XMLタグに注目するように学習されているので効率的に使え
- 例を含めよう (Few-shot learning)
- 最大100K tokenのlong contextを活用しよう
- Claudeに考えさせよう (Chain of Thought)

100K近いlong contextを使う場合の工夫もある。

https://www.anthropic.com/index/prompting-long-context


以上、簡単ではありましたがGA記念ということで、Amazon Bedrockをboto3で触ってみる編でした。