ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

Ruby から Bedrock Claude 2 のAPI を使ってみる

みなさんこんにちは、ユニファの赤沼です。 この記事は Unifa Advent Calendar 2023 の6日目の記事です。

adventar.org

生成AIといえば OpenAI や Microsoft が主流になっていますが、最近は AWS もかなり力を入れていて、AWS上でマネージドサービスとしてモデルを動かせる Bedrock も対応モデルが増えてきています。今回はその中でもトークンが最大200kまで拡大されるなど勢いを増してきているイメージのある Claude 2 を Ruby から利用してみます。

aws.amazon.com

AWSコンソールから確認

まずはコンソールから Claude が利用できる状態になっているか確認します。東京リージョンではまだ Claude Instant 1.2 しか使えないので、今回はバージニア北部リージョンで試してみます。

Access status が Access granted になっていれば利用可能です。今までに利用していなければ利用のリクエストを送って利用可能な状態にする必要があります(その手順は今回は割愛します)。

AWS CLI でモデルを確認

AWSの認証情報が正しく設定できているかの確認を兼ねて、AWS CLI で Bedrock で利用可能なモデルのリストを取得してみます。

認証情報を設定した上で下記のコマンドを実行します。 <profile_name> は実際のプロフィール名に置き換えてください。

$ aws bedrock list-foundation-models --profile <profile_name> --region us-east-1

レスポンスに下記のように Claude のモデルの情報が含まれます。

        {
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-instant-v1:2:100k",
            "modelId": "anthropic.claude-instant-v1:2:100k",
            "modelName": "Claude Instant",
            "providerName": "Anthropic",
            "inputModalities": [
                "TEXT"
            ],
            "outputModalities": [
                "TEXT"
            ],
            "responseStreamingSupported": true,
            "customizationsSupported": [],
            "inferenceTypesSupported": [
                "PROVISIONED"
            ]
        },
        {
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-instant-v1",
            "modelId": "anthropic.claude-instant-v1",
            "modelName": "Claude Instant",
            "providerName": "Anthropic",
            "inputModalities": [
                "TEXT"
            ],
            "outputModalities": [
                "TEXT"
            ],
            "responseStreamingSupported": true,
            "customizationsSupported": [],
            "inferenceTypesSupported": [
                "ON_DEMAND"
            ]
        },
        {
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v1:3:18k",
            "modelId": "anthropic.claude-v1:3:18k",
            "modelName": "Claude",
            "providerName": "Anthropic",
            "inputModalities": [
                "TEXT"
            ],
            "outputModalities": [
                "TEXT"
            ],
            "responseStreamingSupported": true,
            "customizationsSupported": [],
            "inferenceTypesSupported": [
                "PROVISIONED"
            ]
        },
        {
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v1:3:100k",
            "modelId": "anthropic.claude-v1:3:100k",
            "modelName": "Claude",
            "providerName": "Anthropic",
            "inputModalities": [
                "TEXT"
            ],
            "outputModalities": [
                "TEXT"
            ],
            "responseStreamingSupported": true,
            "customizationsSupported": [],
            "inferenceTypesSupported": [
                "PROVISIONED"
            ]
        },
        {
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v1",
            "modelId": "anthropic.claude-v1",
            "modelName": "Claude",
            "providerName": "Anthropic",
            "inputModalities": [
                "TEXT"
            ],
            "outputModalities": [
                "TEXT"
            ],
            "responseStreamingSupported": true,
            "customizationsSupported": [],
            "inferenceTypesSupported": [
                "ON_DEMAND"
            ]
        },
        {
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v2:0:18k",
            "modelId": "anthropic.claude-v2:0:18k",
            "modelName": "Claude",
            "providerName": "Anthropic",
            "inputModalities": [
                "TEXT"
            ],
            "outputModalities": [
                "TEXT"
            ],
            "responseStreamingSupported": true,
            "customizationsSupported": [],
            "inferenceTypesSupported": [
                "PROVISIONED"
            ]
        },
        {
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v2:0:100k",
            "modelId": "anthropic.claude-v2:0:100k",
            "modelName": "Claude",
            "providerName": "Anthropic",
            "inputModalities": [
                "TEXT"
            ],
            "outputModalities": [
                "TEXT"
            ],
            "responseStreamingSupported": true,
            "customizationsSupported": [],
            "inferenceTypesSupported": [
                "PROVISIONED"
            ]
        },
        {
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v2:1:18k",
            "modelId": "anthropic.claude-v2:1:18k",
            "modelName": "Claude",
            "providerName": "Anthropic",
            "inputModalities": [
                "TEXT"
            ],
            "outputModalities": [
                "TEXT"
            ],
            "responseStreamingSupported": true,
            "customizationsSupported": [],
            "inferenceTypesSupported": [
                "PROVISIONED"
            ]
        },
        {
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v2:1:200k",
            "modelId": "anthropic.claude-v2:1:200k",
            "modelName": "Claude",
            "providerName": "Anthropic",
            "inputModalities": [
                "TEXT"
            ],
            "outputModalities": [
                "TEXT"
            ],
            "responseStreamingSupported": true,
            "customizationsSupported": [],
            "inferenceTypesSupported": [
                "PROVISIONED"
            ]
        },
        {
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v2:1",
            "modelId": "anthropic.claude-v2:1",
            "modelName": "Claude",
            "providerName": "Anthropic",
            "inputModalities": [
                "TEXT"
            ],
            "outputModalities": [
                "TEXT"
            ],
            "responseStreamingSupported": true,
            "customizationsSupported": [],
            "inferenceTypesSupported": [
                "ON_DEMAND"
            ]
        },
        {
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v2",
            "modelId": "anthropic.claude-v2",
            "modelName": "Claude",
            "providerName": "Anthropic",
            "inputModalities": [
                "TEXT"
            ],
            "outputModalities": [
                "TEXT"
            ],
            "responseStreamingSupported": true,
            "customizationsSupported": [],
            "inferenceTypesSupported": [
                "ON_DEMAND"
            ]
        },

AWS SDK for Ruby によるアクセス

では Ruby のコードから Bedrock の API を使ってみます。AWSから SDK が提供されているのでそれを利用します。

Gemfile

まずは Gemfile に下記のように記述して bundle install を実行します。 SDK から使われる XML ライブラリとして nokogiri も一緒にインストールします。

gem "nokogiri"
gem "aws-sdk-bedrockruntime"

モデルの実行(invoke model)

モデルの処理を実行するためには Aws::BedrockRuntime::Client クラスの invoke_model メソッドを使用します。

docs.aws.amazon.com

body のパラメータとして渡す内容は使用するモデルによって異なりますが、 AWS コンソールの Providers の画面で下記のようにサンプルを確認できます。また、 Playgrounds からも実際のリクエスト内容を確認できます。

上記を参考にして今回は下記のように実装しました。会話の履歴情報はリストなどではなくプロンプトの中にそのままテキストとして埋め込みます。

認証情報は自動的に取得されるように環境変数に設定したので、コードの中では明示的に指定していません。

require "json"
require "aws-sdk-bedrockruntime"

client = Aws::BedrockRuntime::Client.new(region: "us-east-1")

query = "私の名前がわかりますか?"

prompt = """
You are a helpful assistant.

Human: こんにちは!私はジョンと言います
Assistant: こんにちは、ジョンさん。私はアシスタントです。どのようにお手伝いしましょうか?
Human: #{query}
Assistant:
"""

body = {
  "prompt": prompt,
  "max_tokens_to_sample": 300,
  "temperature": 0.5,
  "top_k": 250,
  "top_p": 1,
  "stop_sequences": [
      "\n\nHuman:"
  ],
  "anthropic_version": "bedrock-2023-05-31"
}

response = client.invoke_model({
  accept: "*/*",
  content_type: "application/json",
  body: body.to_json,
  model_id: "anthropic.claude-v2:1"
})

res = response.body.read
puts JSON.parse(res).dig("completion")

response.body は StringIO クラスなので read メソッドで内容を読み出しています。

内容としては下記のような string が返ってきますので、 completion の値を取り出して出力しています。

{"completion"=>" はい、ジョンさんですね。よろしくお願いします。", "stop_reason"=>"stop_sequence", "stop"=>"\n\nHuman:"}

Streaming

同様の内容をストリーミングで表示するには Aws::BedrockRuntime::Client クラスの invoke_model_with_response_stream メソッドを使用します。

body のパラメータは先程と同様です。

require "json"
require "aws-sdk-bedrockruntime"

client = Aws::BedrockRuntime::Client.new(region: "us-east-1")

query = "自己紹介してください。"

prompt = """
You are a helpful assistant.

Human: #{query}
Assistant:
"""

body = {
  "prompt": prompt,
  "max_tokens_to_sample": 300,
  "temperature": 0.5,
  "top_k": 250,
  "top_p": 1,
  "stop_sequences": [
    "\n\nHuman:"
  ],
  "anthropic_version": "bedrock-2023-05-31"
}

params = {
  accept:       "*/*",
  content_type: "application/json",
  body:         body.to_json,
  model_id:     "anthropic.claude-v2:1"
}

client.invoke_model_with_response_stream(params) do |stream|
  stream.on_error_event do |event|
    raise event # => Aws::Errors::EventError
  end

  stream.on_event do |event|
    print JSON.parse(event.bytes).dig("completion")
  end
end

stream.on_event で取得している event の内容は下記のようになっていますので、 completion の値を取り出して随時出力しています。

#<struct Aws::BedrockRuntime::Types::PayloadPart
 bytes="{\"completion\":\"\xE3\x81\xAF\xE3\x81\x84\xE3\x80\x81\xE8\x87\xAA\xE5\xB7\xB1\xE7\xB4\xB9\",\"stop_reason\":null,\"stop\":null}",
 event_type=:chunk>

結果として下記のような内容が徐々に表示されていきます。

はい、自己紹介します。私は人工知能のアシスタントです。ChatGPTにインスパイアされて作られました。人と会話することが大好きです。質問に答えたり、助けが必要なときはサポートできればと思っています。ぜひ気軽に話しかけてください。よろしくお願いします!

まとめ

AWSのサービスをAPIで利用する場合は環境によっては認証処理を乗り越えるのが地味に大変なところですが、そこを乗り越えてしまえば簡単に Claude のレスポンスを得ることができました。

今回はシンプルな例で試しただけなので、今後 Function Calling なども試せるとよいかと思っています。

ChatGPT と比べると Bedrock / Claude の事例はまだまだ少ないと思いますが、本番でサービスを提供していくことを考えると様々な選択肢、バックアップを考慮しておきたいですし、モデルによって得意な領域や費用も違うので、適材適所で使い分けていけると良いかと思います。

ユニファではLLMの活用含めて共に保育をHackしてくれる仲間を募集中です!少しでも興味をお持ちいただけた方は、ぜひ一度カジュアルにお話しましょう!

unifa-e.com