3
1

Livebook で Claude 3.5 Sonnet を Amazon Bedrock の基盤モデルとして呼び出す

Last updated at Posted at 2024-08-02

はじめに

AWS の生成 AI サービス Bedrock を Livebook から呼び出します

2023 年 12 月にもやっていますが、今回は Claude 3.5 Sonnet に画像を与え、内容を説明してもらいます

実装したノートブックはこちら

事前準備

以下の記事を参考に、 AWS コンソールから基盤モデルのアクセス要求を出しておきましょう

また、 AWS にアクセスするための IAM ユーザー、認証情報が必要です

セットアップ

必要なモジュールをインストールします

以前はまだ AWS Elixir の Bedrock 対応版がリリースされていませんでしたが、 2024 年 8 月現在は対応済です

Mix.install([
  {:aws, "~> 1.0"},
  {:hackney, "~> 1.20"},
  {:kino, "~> 0.13"}
])

シークレットの登録

AWS の認証情報をシークレットに登録します

  • ACCESS_KEY_ID
  • SECRET_ACCESS_KEY

スクリーンショット 2024-08-01 22.27.38.png

クライアントの作成

AWS の API にアクセスするためのクライアントを作成します

シークレットに登録した値には LB_ を先頭につけて環境変数としてアクセスします

リージョンは適宜変更してください

client =
  AWS.Client.create(
    System.get_env("LB_ACCESS_KEY_ID"),
    System.get_env("LB_SECRET_ACCESS_KEY"),
    "us-east-1"
  )

基盤モデル一覧の確認

AWS.Bedrock.list_foundation_models で基盤モデルの一覧が取得できます

Kino.DataTable.new でテーブル表示してみましょう

models =
  client
  |> AWS.Bedrock.list_foundation_models()
  |> elem(1)
  |> Map.get("modelSummaries")

keys = [
  "modelId",
  "modelName",
  "providerName",
  "inputModalities",
  "outputModalities"
]

Kino.DataTable.new(models, keys: keys)

実行結果

スクリーンショット 2024-08-01 22.38.18.png

確かに Claude 3.5 Sonnet が存在します

Claude 3.5 Sonnet の呼び出し

Claude 1.0 は AWS.BedrockRuntime.invoke_model で呼び出せていましたが、 Claude 3.5 Sonnet では以下のようなエラーが発生します

"claude-3-5-sonnet-20240620" is not supported on this API. Please use the Messages API instead.

invoke_model の代わりに converse を使いましょう

input = "Elixirで5の階乗を計算するコードを教えてください。"

{:ok, body, _response} =
  client
  |> AWS.BedrockRuntime.converse(
    model_id_claude,
    %{
      "messages" => [%{
        "role" => "user",
        "content" => [%{"text" => input}]
      }]
    },
    recv_timeout: 60_000
  )

実行結果

{:ok,
 %{
   "metrics" => %{"latencyMs" => 10208},
   "output" => %{
     "message" => %{
       "content" => [
         %{
           "text" => "Elixirで5の階乗を計算するコードをいくつかの方法で示します:\n\n1. 再帰を使用する方法:\n\n```elixir\ndefmodule Factorial do\n  def of(0), do: 1\n  def of(n) when n > 0, do: n * of(n - 1)\nend\n\nIO.puts Factorial.of(5)  # 120\n```\n\n2. Enumモジュールを使用する方法:\n\n```elixir\nfactorial = fn n ->\n  1..n |> Enum.reduce(&*/2)\nend\n\nIO.puts factorial.(5)  # 120\n```\n\n3. リスト内包表記を使用する方法:\n\n```elixir\nfactorial = fn n ->\n  for i <- 1..n, reduce: 1, do: (acc -> acc * i)\nend\n\nIO.puts factorial.(5)  # 120\n```\n\n4. パイプ演算子を使用する方法:\n\n```elixir\nfactorial = fn n ->\n  1..n\n  |> Enum.to_list()\n  |> Enum.reduce(&*/2)\nend\n\nIO.puts factorial.(5)  # 120\n```\n\nこれらの方法はすべて5の階乗(5!)を計算し、結果として120を出力します。再帰を使用する方法が最も一般的で、数学的な定義に近いアプローチです。他の方法は、Elixirの異なる機能を示しています。\n\n実際の使用では、パフォーマンスや可読性を考慮して、適切な方法を選択してください。小さな数の階乗を計算する場合は、これらの方法の間で大きな性能差はありません。"
         }
       ],
       "role" => "assistant"
     }
   },
   "stopReason" => "end_turn",
   "usage" => %{"inputTokens" => 29, "outputTokens" => 463, "totalTokens" => 492}
 },
 %{
   body: "{\"metrics\":{\"latencyMs\":10208},\"output\":{\"message\":{\"content\":[{\"text\":\"Elixirで5の階乗を計算するコードをいくつかの方法で示します:\\n\\n1. 再帰を使用する方法:\\n\\n```elixir\\ndefmodule Factorial do\\n  def of(0), do: 1\\n  def of(n) when n > 0, do: n * of(n - 1)\\nend\\n\\nIO.puts Factorial.of(5)  # 120\\n```\\n\\n2. Enumモジュールを使用する方法:\\n\\n```elixir\\nfactorial = fn n ->\\n  1..n |> Enum.reduce(&*/2)\\nend\\n\\nIO.puts factorial.(5)  # 120\\n```\\n\\n3. リスト内包表記を使用する方法:\\n\\n```elixir\\nfactorial = fn n ->\\n  for i <- 1..n, reduce: 1, do: (acc -> acc * i)\\nend\\n\\nIO.puts factorial.(5)  # 120\\n```\\n\\n4. パイプ演算子を使用する方法:\\n\\n```elixir\\nfactorial = fn n ->\\n  1..n\\n  |> Enum.to_list()\\n  |> Enum.reduce(&*/2)\\nend\\n\\nIO.puts factorial.(5)  # 120\\n```\\n\\nこれらの方法はすべて5の階乗(5!)を計算し、結果として120を出力します。再帰を使用する方法が最も一般的で、数学的な定義に近いアプローチです。他の方法は、Elixirの異なる機能を示しています。\\n\\n実際の使用では、パフォーマンスや可読性を考慮して、適切な方法を選択してください。小さな数の階乗を計算する場合は、これらの方法の間で大きな性能差はありません。\"}],\"role\":\"assistant\"}},\"stopReason\":\"end_turn\",\"usage\":{\"inputTokens\":29,\"outputTokens\":463,\"totalTokens\":492}}",
   headers: [
     {"Date", "Thu, 01 Aug 2024 13:48:39 GMT"},
     {"Content-Type", "application/json"},
     {"Content-Length", "1474"},
     {"Connection", "keep-alive"},
     {"x-amzn-RequestId", "0bbc2381-ca9b-4a8d-9c9c-94600010ea28"}
   ],
   status_code: 200
 }}

生成されたテキストを Markdown として解釈しましょう

body
|> Map.get("output")
|> Map.get("message")
|> Map.get("content")
|> Enum.at(0)
|> Map.get("text")
|> Kino.Markdown.new()

実行結果

スクリーンショット 2024-08-01 22.54.04.png

非常に多くのパターンを生成してくれました

参考として、 Claude 1.0 の生成結果は以下のとおり

スクリーンショット 2023-12-04 0.31.05.png

画像の説明

Claude に画像の説明をしてもらいましょう

任意の画像をバイナリとして読み込みます

image = File.read!("/home/livebook/vix/puppies.png")

Claude を呼び出す関数を用意します

invoke_claude = fn content ->
  client
  |> AWS.BedrockRuntime.converse(
    model_id_claude,
    %{
      "messages" => [%{
        "role" => "user",
        "content" => content
      }]
    },
    recv_timeout: 60_000
  )
  |> elem(1)
  |> Map.get("output")
  |> Map.get("message")
  |> Map.get("content")
  |> Enum.at(0)
  |> Map.get("text")
end

画像のバイナリデータを Base64 エンコードして Bedrock に送ります

画像と生成結果を並べて表示してみましょう

result =
  invoke_claude.([
    %{
      "image" => %{
        "format" => "png",
        "source" => %{"bytes" => Base.encode64(image)}
      }
    },
    %{"text"=> "画像に写っているものを説明してください"},
  ])

[
  image,
  Kino.Markdown.new(result)
]
|> Kino.Layout.grid(columns: 2)

実行結果

スクリーンショット 2024-08-01 23.01.47.png

かなり詳細で正確な説明ができています

フォームから選択した画像の説明

画像説明用の関数を用意します

describe_image = fn image ->
  invoke_claude.([
    %{
      "image" => %{
        "format" => "png",
        "source" => %{"bytes" => Base.encode64(image)}
      }
    },
    %{"text"=> "画像に写っているものを説明してください"},
  ])
end

入出力の UI とフォーム送信時の処理を用意します

画像入力に format: :png を指定しているのが肝です

指定が漏れていると画像として処理できません

# 入力用フォーム
form =
  Kino.Control.form(
    [
      image: Kino.Input.image("IMAGE", format: :png)
    ],
    submit: "Submit"
  )

# 出力用フレーム
frame = Kino.Frame.new()

# フォーム送信時の処理
Kino.listen(form, fn event ->
  image =
    event.data.image.file_ref
    |> Kino.Input.file_path()
    |> File.read!()

  result = describe_image.(image)

  result
  |> Kino.Markdown.new()
  |> then(&Kino.Frame.render(frame, &1))
end)

# 入出力を並べて表示
Kino.Layout.grid([form, frame], columns: 2)

実行結果

スクリーンショット 2024-08-01 23.08.23.png

左側の入力フォームで画像を選択し、 "Submit" ボタンをクリックすると、画像の説明が右側に表示されます

スクリーンショット 2024-08-01 23.12.01.png

アプリ化

以下の記事で紹介しているように、簡単にアプリ化することも可能です

bedrock.gif

まとめ

Claude 3.5 Sonnet を使うことで、かなり性能の高い AI アプリが実装できそうですね

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1