Amazon Bedrock APIで始めるLLM超入門①

AWSでBedrock APIを叩きながらLLMについて学習します。LLMとしてはClaude2を使用します。Python環境の新規構築を想定した手順を記載していますが、既に環境がある方は適宜読み替えてください。

Bedrockの価格は以下をご確認ください。

https://aws.amazon.com/jp/bedrock/pricing/

# そもそもLLMとは？
- 大規模言語モデル（Large Language Models）の略
- 言語モデルを超大量のデータで学習させたら突然性能が上がったもの
- chatGPTで言うところのGPT-3.5やGPT-4
- 言語モデルとは？
  - 入力されたテキスト（プロンプト）を単語（トークン）列に変換し、次の単語（トークン）を予測する
  - 単語（トークン）の予測を続けることで文章が生成される
  - chatGPTが少しずつ回答してくるのは、演出ではなく予測する都度回答しているから

# Amazon Bedrockとは？
LLMを含むモデルをAPI経由で呼び出しが可能なAWSのサービス。インフラストラクチャの管理は不要（APIを呼び出すのみ）。

# Claude2とは？
日本語、英語ともに、GPT-3.5とGPT-4の間ぐらいの性能と評価されているLLM。~~金額もその間。~~
一度に入力可能なトークン数がGPTよりもかなり多い（2023/10/10現在)。

https://yuzuai.jp/benchmark

https://huggingface.co/spaces/lmsys/chatbot-arena-leaderboard

# アーキテクチャ
<img width="40%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/7226ad69-cdf3-485e-d31d-2a1d74d0612a.gif">

# 前提条件
- AWSアカウント
- Python3.8以降が実行可能なローカル環境（私はWindows）

# AWS環境の準備
- バージニア北部(us-east-1)リージョンのClaude2を使用可能にします。マネコンからRequestしてください

https://dev.classmethod.jp/articles/if-you-want-to-try-out-amazon-bedrock-from-the-management-console-you-can-set-up-access-to-base-models/

:::note info
2023/10/10現在 LLMの選択肢が多いバージニア北部リージョンを使用します
:::

- IAMユーザーの作成とcredential(アクセスキー、シークレットアクセスキー)の発行
- Bedrockアクセス用のIAMポリシーの作成。とりあえずすべてのアクション、すべてのリソース
<img width="30%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/44e89d07-bd89-b975-f7a6-4c7435c7bb31.gif">

- 作成したIAMユーザーに作成したポリシーのアタッチ
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/5cd9c868-bd08-6937-0842-8fb9cfc2c5d4.png">


# ローカル環境の準備
- Amazon CLIのインストール (上述のcredentialを設定)

https://dev.classmethod.jp/articles/installed_aws-cli_on_my_local_windows/

:::note info
デフォルトリージョンにはバージニア北部( us-east-1 )を指定してください
:::

boto3(AWS SDK for Python)を最新化します。
```:ターミナル
pip install -U boto3
```

# サンプルプログラムの作成と実行
何も考えずに以下のページの`Python(boto)`タブのプログラムをコピペして実行します。`InvokeModel`と`InvokeModelWithResponseStream`の2種類のAPIが用意されています。

https://docs.aws.amazon.com/bedrock/latest/userguide/inference-invoke.html

## InvokeModel APIのケース
このAPIは全ての回答が揃ってからresponseを返すAPIです。

```Python:bedrock_invoke_model_sample
import boto3
import json
bedrock = boto3.client(service_name='bedrock-runtime')

body = json.dumps({
    "prompt": "\n\nHuman:explain black holes to 8th graders\n\nAssistant:",
    "max_tokens_to_sample": 300,
    "temperature": 0.1,
    "top_p": 0.9,
})

modelId = 'anthropic.claude-v2'
accept = 'application/json'
contentType = 'application/json'

response = bedrock.invoke_model(body=body, modelId=modelId, accept=accept, contentType=contentType)

response_body = json.loads(response.get('body').read())

# text
print(response_body.get('completion'))
```
`explain black holes to 8th graders`(8年生に対してブラックホールを説明する)の部分がLLMへの問い合わせ内容です。前後の`\n\nHuman:`と`\n\nAssistant:`はClaude2の記法なのでそういうものだと思ってください。
これを実行します。
```:ターミナル(実行)
python bedrock_invoke_model_sample.py
```
```:ターミナル(実行結果)
 Here is a simple explanation of black holes for 8th graders:

- A black hole is a region in space where gravity is so strong that nothing can escape from it, even light. This region is called the event horizon.  
- The gravity of a black hole is so intense because all of the mass of the collapsed star is concentrated into an extremely small space. This creates 
a gravitational field that is so strong that not even light can escape past the event horizon.

- The event horizon is the boundary around the black hole where the escape velocity (the speed needed to break free from the gravitational pull) equals the speed of light (300,000 km/s). Nothing can go faster than the speed of light, so nothing can escape from inside the event horizon.

- Black holes cannot be directly observed because they emit no light. But we can detect them by observing their gravitational effects on nearby stars 
and gas clouds. Large black holes also pull in and "swallow" nearby matter, producing a lot of X-ray radiation as the matter accelerates and heats up.

- Supermassive black holes are thought to exist at the center of most large galaxies, including our own Milky Way galaxy. These black holes have masses millions
```
```:実行結果の日本語訳（Google翻訳）
中学 2 年生向けのブラック ホールの簡単な説明は次のとおりです。

- ブラックホールは、重力が非常に強いため、光さえもそこから逃れることができない宇宙の領域です。この領域は事象の地平線と呼ばれます。
- ブラックホールの重力は、崩壊した星のすべての質量が非常に小さな空間に集中しているため、非常に強力です。これにより、
非常に強い重力場であり、光さえも事象の地平線を越​​えて逃げることができません。

- 事象の地平線は、脱出速度 (重力から解放されるのに必要な速度) が光速 (300,000 km/s) に等しい、ブラック ホールの周囲の境界です。光の速度より速く進むものは何もないので、事象の地平線の内側から逃げることはできません。

・ブラックホールは光を発しないため、直接観測することができません。しかし、近くの星に対する重力の影響を観察することで、それらを検出することができます。
そしてガス雲。大きなブラックホールは近くの物質を引き込んで「飲み込み」、物質が加速して加熱されるにつれて大量のX線放射を生成します。

- 超大質量ブラックホールは、私たちの天の川銀河を含むほとんどの大きな銀河の中心に存在すると考えられています。これらのブラックホールは何百万もの質量を持っています
```

## InvokeModelWithResponseStream APIのケース
このAPIはStreamでresponseを返すAPIです。少しずつ回答が追加されていきます。
```Python:bedrock_invoke_model_with_response_stream_sample.py
import boto3
import json

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

body = json.dumps({
    'prompt': '\n\nHuman:write an essay for living on mars in 1000 words\n\nAssistant:',
    'max_tokens_to_sample': 100
})
                   
response = bedrock.invoke_model_with_response_stream(
    modelId='anthropic.claude-v2', 
    body=body
)
    
stream = response.get('body')
if stream:
    for event in stream:
        chunk = event.get('chunk')
        if chunk:
            print(json.loads(chunk.get('bytes').decode()))
```
`write an essay for living on mars in 1000 words`(火星で暮らすためのエッセイを1000語で書く)の部分がLLMへの問い合わせ内容です。
```:ターミナル(実行)
python bedrock_invoke_model_with_response_stream_sample.py
```
```:ターミナル(実行結果)
{'completion': ' Here', 'stop_reason': None}
{'completion': ' is a 997-word essay on living on', 'stop_reason': None}
{'completion': ' Mars:\n\nLiving on Mars\n\nMars', 'stop_reason': None}
{'completion': ' has', 'stop_reason': None}
{'completion': ' long', 'stop_reason': None}
{'completion': ' captured', 'stop_reason': None}
{'completion': ' the', 'stop_reason': None}
{'completion': ' imag', 'stop_reason': None}
{'completion': 'inations of humans, serving', 'stop_reason': None}
{'completion': ' as', 'stop_reason': None}
{'completion': ' the backdrop', 'stop_reason': None}
{'completion': ' for science fiction stories of alien civil', 'stop_reason': None}
{'completion': 'izations and future human colon', 'stop_reason': None}
{'completion': 'ization. With its', 'stop_reason': None}
{'completion': ' similarities to', 'stop_reason': None}
{'completion': ' Earth, Mars presents an intriguing possibility for', 'stop_reason': None}
{'completion': ' establishing a permanent human settlement off', 'stop_reason': None}
{'completion': '-world.', 'stop_reason': None}
{'completion': ' While major challenges exist,', 'stop_reason': None}
{'completion': ' advances in technology are bringing', 'stop_reason': None}
{'completion': ' the prospect of living on Mars closer', 'stop_reason': None}
{'completion': ' to reality. \n\nOne of the most immediate', 'stop_reason': None}
{'completion': ' challenges for humans', 'stop_reason': None}
{'completion': ' living', 'stop_reason': 'max_tokens'}
```
テキストだと分かりにくいですが、1行ずつJSON形式で出力されます（1単語ずつ≒1トークンずつというわけじゃないんですね）。
`completion`が回答のチャンク（塊）で、`stop_reason`は最後だけ`max_tokens`と返されています。
何が返されているのか気になるので、ソースを少し書き換えて`completion`のみ改行なしでprintするようにします。最終行のみ書き換えます。
```Python:bedrock_invoke_model_with_response_stream_sample2.py
import boto3
import json

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

body = json.dumps({
    'prompt': '\n\nHuman:write an essay for living on mars in 1000 words\n\nAssistant:',
    'max_tokens_to_sample': 100
})
                   
response = bedrock.invoke_model_with_response_stream(
    modelId='anthropic.claude-v2', 
    body=body
)
    
stream = response.get('body')
if stream:
    for event in stream:
        chunk = event.get('chunk')
        if chunk:
            print(json.loads(chunk.get('bytes').decode())["completion"],end='')
```
```:ターミナル(実行)
python bedrock_invoke_model_with_response_stream_sample2.py
```
```:ターミナル(実行結果)
Here is a 997-word essay on living on Mars:

Living on Mars

Moving to Mars and establishing a long-term human settlement on the Red Planet has long been a dream for many space enthusiasts and scientists. With continued advances in space technology and robotics, this vision is coming closer to reality. Here I will explore some of the challenges and opportunities of establishing a permanent colony on Mars.       

One of the biggest challenges is the distance - Mars is an average of 140
```
```:実行結果の日本語訳（Google翻訳）
以下は火星での生活についての 997 語のエッセイです。

火星に住む

火星に移住し、火星に人類の長期定住を確立することは、多くの宇宙愛好家や科学者にとって長年の夢でした。宇宙技術とロボット工学の継続的な進歩により、このビジョンは現実に近づいています。ここでは、火星に恒久的な植民地を確立する際の課題と機会のいくつかを探っていきます。

最大の課題の 1 つは距離です。火星は平均 140 メートルです。
```
LLMの回答は毎回同じではないので1度目とは違う文章が徐々に出力されていきます。最後は`1000 words`に達した為…、いや数えてみると`'max_tokens_to_sample': 100`に達した為かな、最後切れてなんだか分からない文章になっていますが、Streamで生成されていくのが見えます。

# まとめ
コードを見ると、本質的にはBedrock（のruntime）としては`InvokeModel`と`InvokeModelWithResponseStream`の2種類のAPIしかなく、LLMとしては`"prompt"`およびパラメータとして何を渡されるかのみでLLMのresponse内容を決定していることが分かります。

https://qiita.com/cyberBOSE/items/fe66c2049aaa04568d61