16
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ElixirAdvent Calendar 2023

Day 15

Amazon Bedrock の「生成AIによるチャット、画像生成」を Livebook から実行する

Last updated at Posted at 2023-12-04

はじめに

Amazon Bedrock は AWS から各種生成 AI を呼び出し、テキスト生成や画像生成を実行するサービスです

ブラウザからお手軽に試す場合、以下の記事を参考にしてください

本記事では Elixir の Livebook から Bedrock を使い、生成AIによるチャット、画像生成を実装します

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

Livebook とは

Livebook は Elixir のコードをブラウザから実行し、結果を表示してくれるツールです

Python における Jupyter のようなもので、 Elixir 入門や Elixir を使ったデータ分析、データの視覚化などに適しています

はじめ方は以下の記事を参考にしてください

セットアップ

Livebook で新しいノートブックを開いたら、先頭のセットアップセルに以下のコードを入力し、実行してください

Mix.install([
  {:aws, git: "https://github.com/aws-beam/aws-elixir/"},
  {:hackney, "~> 1.20"},
  {:kino, "~> 0.11"}
])

AWS Elixir というモジュールをインストールしています

まだリリースされているバージョンには Bedrock 用のモジュールが含まれていないため、 GitHub から最新版をインストールしています

クライアントの準備

AWS の認証情報を入力するためのテキストボックスを準備します

access_key_id_input = Kino.Input.password("ACCESS_KEY_ID")
secret_access_key_input = Kino.Input.password("SECRET_ACCESS_KEY")
region_input = Kino.Input.text("REGION")

[
  access_key_id_input,
  secret_access_key_input,
  region_input
]
|> Kino.Layout.grid(columns: 3)

セルを実行して表示されたテキストボックスにそれぞれ入力してください

リージョンは Bedrock を使用するリージョンなので、東京リージョン(ap-northeast-1)で使えないモデルを使いたい場合、バージニア北部(us-east-1)などを指定してください

スクリーンショット 2023-12-03 23.53.29.png

AWS の API と通信するためのクライアントを用意します
この際、先ほどの認証情報を受け渡します

client =
  AWS.Client.create(
    Kino.Input.read(access_key_id_input),
    Kino.Input.read(secret_access_key_input),
    Kino.Input.read(region_input)
  )

基盤モデル一覧の取得

入力したリージョンで使用可能な基盤モデルの一覧を表示します

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

Kino.DataTable.new(models)

Kino.DataTable を使うことで、結果が検索やソートも可能なテーブル表示になります

table.gif

テーブル表示した情報を参考にしながら、使いたい基盤モデルの情報を取得します
例えば Titan Text を使う場合、以下のように providerNameAmazon かつ modelNameTitan Text G1 - Express かつ
inferenceTypesSupported["ON_DEMAND"] の条件を指定して検索できます

同じモデルでも課金体系によって ON_DEMANDPROVISIONED が存在する点に注意してください
考え方は DynamoDB と同じように、完全に呼ばれる毎に課金されるのが ON_DEMAND で、一定の性能を確保して時間毎固定料金になる PROVISIONED です

本記事では数回しか呼ばないため、 ON_DEMAND を使用します

titan_express_model =
  models
  |> Enum.find(fn model ->
    model["providerName"] == "Amazon" and
    model["modelName"] == "Titan Text G1 - Express" and
    model["inferenceTypesSupported"] == ["ON_DEMAND"]
  end)

実行結果

%{
  "customizationsSupported" => [],
  "inferenceTypesSupported" => ["ON_DEMAND"],
  "inputModalities" => ["TEXT"],
  "modelArn" => "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-text-express-v1",
  "modelId" => "amazon.titan-text-express-v1",
  "modelLifecycle" => %{"status" => "ACTIVE"},
  "modelName" => "Titan Text G1 - Express",
  "outputModalities" => ["TEXT"],
  "providerName" => "Amazon",
  "responseStreamingSupported" => true
}

この情報のうち、基盤モデルを API 経由で呼び出す場合には modelId の値が必要になるため、取得しておきます

titan_express_model_id = titan_express_model["modelId"]

実行結果

"amazon.titan-text-express-v1"

日本語対応テキスト生成の Claude も使いたいのでモデル ID を取得しておきます

claude_model_id =
  models
  |> Enum.find(fn model ->
    model["modelName"] == "Claude" and
    model["inferenceTypesSupported"] == ["ON_DEMAND"]
  end)
  |> Map.get("modelId")

実行結果

"anthropic.claude-v1"

画像生成用のモデル ID も取得します

titan_image_id =
  models
  |> Enum.find(fn model ->
    model["modelName"] == "Titan Image Generator G1" and
    model["inferenceTypesSupported"] == ["ON_DEMAND"]
  end)
  |> Map.get("modelId")

実行結果

"amazon.titan-image-generator-v1"

テキスト生成(チャット)

テキスト生成によるチャットを実装してみます

まず、生成 AI に投げる質問を用意します

Titan は日本語非対応なので、英語で「Elixir での Hello, World のコードを教えて」と聞いてみましょう

input = "Please show me the code to display \"Hello, World\" in Elixir."

以下のようにして Bedrock の基盤モデルを呼び出します

results =
  client
  |> AWS.BedrockRuntime.invoke_model(
    titan_express_model_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "inputText" => input
    }
  )
  |> elem(1)
  |> Map.get("results")

第1引数が用意しておいたクライアント、第2引数がモデル ID 、第3引数が生成 AI に渡す入力です

第3引数の "accept" => "application/json""contentType" => "application/json" は API リクエスト時のヘッダーとして指定するため、固定で必ず必要です

それ以外の項目と、出力形式については基盤モデル毎にバラバラな仕様になっているため、個別に仕様を確認してください

Titan の場合、以下のような形式で結果が返ってきます

実行結果

[
  %{
    "completionReason" => "LENGTH",
    "outputText" => "\nTo display \"Hello, World\" in Elixir, you can use the \"IO.puts\" function. Here is an example:\n```Elixir\nIO.puts \"Hello, World\"\n```\nIn Elixir, the \"IO.puts\" function is used to print messages to the console. In this case, it will output \"Hello, World\" to the console.\nYou can also use formatting options to customize the output. For example, you can use \"IO.inspect\" to pretty-print the message:\n```Elixir\nIO.inspect \"Hello, World\"",
    "tokenCount" => 128
  }
]

生成 AI の出力は Markdown になっているため、ノートブック上で Markdown として表示してみましょう

results
|> Enum.at(0)
|> Map.get("outputText")
|> Kino.Markdown.new()

実行結果
スクリーンショット 2023-12-04 0.22.40.png

ちゃんと Hello, World のコードを解説付きで生成してくれました

日本語チャット

次に Claude による日本語チャットを実装します

今度は少し難しい問題として、 Elixir で 5 の階乗を計算するコードを生成してもらいます

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

result =
  client
  |> AWS.BedrockRuntime.invoke_model(
    claude_model_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "prompt" => "\n\nHuman: #{input}\n\nAssistant:",
      "max_tokens_to_sample" => 300
    },
    recv_timeout: 60_000
  )
  |> elem(1)
  |> Map.get("completion")

Claude の場合、入力テキストを prompt に \n\nHuman: <入力テキスト>\n\nAssistant: という形式で入れなければならない、という点に注意してください
\n\nHuman: で始まり、 \n\nAssistant: で終わる、という条件を満たしていないとエラーが返ります
max_tokens_to_sample も必須項目なので指定してください

処理に少し時間がかかるため、第4引数のオプションに recv_timeout: 60_000 を指定し、タイムアウトを 1 分まで延長しています
これを指定しないとタイムアウトエラーが発生します

Titan の場合、結果は results に入っていましたが、 Claude の場合は completion に入っている点も要注意です

結果を表示してみましょう

result
|> Kino.Markdown.new()

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

しっかり Elixir らしい実装を日本語の解説とともに返してくれました

画像生成

最後に画像生成です

Titan に Elixir の初心者向けコミュニティ piyopiyo.ex の画像を生成してもらいましょう

さすがに名前だけだと厳しいと思うので、「Elixir の赤ちゃんが卵から生まれている」「Elixir はかわいい顔をした紫色の雫」とヒントを与えます。

input = "piyopiyo.ex Elixir babies are being born from eggs. Elixir is a purple drop with a pretty face."

results =
  client
  |> AWS.BedrockRuntime.invoke_model(
    titan_image_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "taskType" => "TEXT_IMAGE",
      "textToImageParams" => %{
        "text" => input
      },
      "imageGenerationConfig" => %{
        "numberOfImages" => 1,
        "quality" => "standard",
        "height" => 512,
        "width" => 512,
        "cfgScale" => 2.0,
        "seed" => 999
      }
    },
    recv_timeout: 60_000
  )
  |> elem(1)
  |> Map.get("images")

実行結果

["iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOt...

結果を見て「何のこっちゃ」と思うかもしれませんが、これはバイナリデータを文字列で表現した Base64 という形式です

Base64 を PNG 画像として表示してみます

results
|> Enum.at(0)
|> then(&"""
<img src="data:image/png;base64,#{&1}" />
""")
|> Kino.HTML.new()

実行結果
piyopiyo.png

当たらずも遠からず、といったところでしょうか

まとめ

Elixir から Amazon Bedrock を呼び出し、生成 AI によるチャット、画像生成が実装できました

Bedrock は今後もモデルが増えるでしょうし、しかも自分のデータでモデルをカスタマイズすることも可能です

当然 AWS 上の Lambda などから呼び出したりなど、 AWS の各種サービスとの連携も容易になっています

今後、 Bedrock を使ったサービスがどんどん増えそうですね

16
0
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
16
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?