Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (2024)

はじめに

2024/9/28 大分で Cloudflare Meet-up 開催します!

テーマは「AI Gateway」

というわけで、 Elixir Livebook から AI Gateway を使ってみます

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

AI Gateway とは

AI Gateway は各種 AI サービスへのプロキシ(中継)として、以下のような機能を提供します

  • キャッシュ(同じ質問・指示にはキャッシュで応答)
  • レート制限(単位時間あたりのリクエスト数)
  • リアルタイムログ取得(リクエストとレスポンスの内容を1時間保持)
  • 使用量制限
  • 分析
  • リクエストフォールバック(一つのAIサービスでエラーが起こったとき、別のAIサービスを使って応答)

今使える機能は無償利用可能です

今後、ログの永続化などを有償提供するようです

セットアップ

新しいノートブックを開き、セットアップセルで以下のコードを実行します

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

直接 Amazon Bedrock を呼び出す場合、 AWS モジュールを使用します

また、 AI Gateway 経由で呼ぶ場合の署名にも利用します

事前準備

AWS

Amazon Bedrock を使うための準備をしておきます

  • Amazon Bedrock で Claude 3.5 のアクセス要求
  • Amazon Bedrock を呼び出す権限を付与した IAM ユーザーとその認証情報を作成

手順は以下の記事を参照

Cloudflare

Cloudflare のアカウントを作成し、 AI Gateway のゲートウェイを作成しておきます

アカウント作成手順は以下の記事を参照

アカウント作成後、ホーム画面で左メニューから AI を開き、 AI Gateway をクリック

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (1)

「ゲートウェイの作成」をクリックして開いたモーダルにゲートウェイの名前を入力
(本記事ではゲートウェイ名を「bedrock」に指定)

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (2)

ゲートウェイが作成されるので、右上(赤枠)の API ボタンをクリック

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (3)

開いたモーダルに AI Gateway のエンドポイントが表示されます

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (4)

エンドポイントは以下のような構成になっています

https://gateway.ai.cloudflare.com/v1/<Cloudflare のアカウントID>/<ゲートウェイ名>/

「Cloudflare のアカウントID」と「ゲートウェイ名」を後の手順で使用します

認証情報の設定

テキスト入力を作成し、 AWS の認証情報とリージョン、 Cloudflare で用意しておいたアカウント ID、ゲートウェイ名を入力します

リージョンはアクセス要求を出したリージョンを指定します

aws_access_key_id_input = Kino.Input.password("AWS ACCESS_KEY_ID")aws_secret_access_key_input = Kino.Input.password("AWS SECRET_ACCESS_KEY")aws_region_input = Kino.Input.text("AWS REGION")cf_account_id_input = Kino.Input.password("CLOUDFLARE ACCOUNT_ID")cf_gateway_name_input = Kino.Input.text("CLOUDFLARE GATEWAY_NAME")[ aws_access_key_id_input, aws_secret_access_key_input, aws_region_input, cf_account_id_input, cf_gateway_name_input]|> Kino.Layout.grid(columns: 3)

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (5)

認証情報を利用して、 AWS にアクセスするためのクライアントを作成します

aws_client = AWS.Client.create( Kino.Input.read(aws_access_key_id_input), Kino.Input.read(aws_secret_access_key_input), Kino.Input.read(aws_region_input) )

AWS クライアントからの Bedrock 呼び出し

まずは通常通り、 AWS クライアントから Bedrock を呼び出してみます

やっていることは以下の記事と同じです

モデルIDで Claude 3.5 Sonnet を指定します

model_id = "anthropic.claude-3-5-sonnet-20240620-v1:0"

生成AIへの指示を用意します

input = "あなたの名前を教えてください。"payload = %{ "messages" => [%{ "role" => "user", "content" => [%{"text" => input}] }]}

返答に少し時間がかかるため、リクエストのタイムアウトに 60 秒を指定します

options = [recv_timeout: 60_000]

AWS.BedrockRuntime.converse で Claude 3.5 にリクエストを送ります

result = aws_client |> AWS.BedrockRuntime.converse(model_id, payload, options) |> elem(1)

レスポンスは以下のようになります

%{ "metrics" => %{"latencyMs" => 1095}, "output" => %{ "message" => %{ "content" => [ %{ "text" => "私の名前はClaudeです。AIアシスタントとして作られました。どうぞよろしくお願いします。" } ], "role" => "assistant" } }, "stopReason" => "end_turn", "usage" => %{"inputTokens" => 19, "outputTokens" => 39, "totalTokens" => 58}}

Livebook ではレスポンスからテキストを取り出し、マークダウンとして表示できます

result|> Map.get("output")|> Map.get("message")|> Map.get("content")|> hd()|> Map.get("text")|> Kino.Markdown.new()

実行結果

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (6)

ここまでは通常通りの Bedrock の使い方です

AWS.Client.request による Bedrock の呼び出し

AI Gateway から Amazon Bedrock を呼び出す場合、リクエストへの署名が必要です

AWS モジュールでは内部的に署名と AWS のAPI 呼び出しを実施してくれています

まずはその実装を参考に署名と AWS API 呼び出しを実行してみましょう

まず、 API 呼び出しのヘッダーと URL を用意します

aws_region = Kino.Input.read(aws_region_input)host = "bedrock-runtime.#{aws_region}.amazonaws.com"headers = [ {"Host", host}, {"Content-Type", "application/json"}]encoded_path = "/model/#{AWS.Util.encode_uri(model_id)}/converse"url = "https://#{host}#{encoded_path}"

AWS モジュールの実装では "Content-Type" を "application/x-amz-json-1.1" に指定していますが、この指定だと AI Gateway のログ上にリクエスト内容が表示されません

https://github.com/aws-beam/aws-elixir/blob/master/lib/aws/generated/bedrock_runtime.ex#L886

"Content-Type" は "application/json" を指定します

Bedrock 自体はどちらでも応答してくれます

URL は以下のような値になります

"https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-3-5-sonnet-20240620-v1%3A0/converse"

AWS.Util.encode_uri を通すことで :%3A になっていることが肝ですね

署名に必要な現在時刻を取得します

now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)

実行結果(例)

~N[2024-08-28 04:53:09]

署名には有効期間があるため、この時刻から一定時間経過すると使えなくなります(エラーメッセージに Signature expired が返ってくる)

リクエスト内容を JSON 文字列に変換します

encoded_payload = AWS.Client.encode!(aws_client, payload, :json)

実行結果

"{\"messages\":[{\"content\":[{\"text\":\"あなたの名前を教えてください。\"}],\"role\":\"user\"}]}"

AWS.Signature.sign_v4 関数を利用して署名付ヘッダーを生成します

siged_headers = AWS.Signature.sign_v4( %{aws_client | service: "bedrock"}, now, :post, url, headers, encoded_payload )

実行結果

[ {"Authorization", "AWS4-HMAC-SHA256 Credential=xxx/20240828/us-east-1/bedrock/aws4_request,SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date,Signature=999"}, {"X-Amz-Content-SHA256", "aaa"}, {"X-Amz-Date", "20240828T045309Z"}, {"Host", "bedrock-runtime.us-east-1.amazonaws.com"}, {"Content-Type", "application/json"}]

署名付ヘッダーを指定して AWS API を呼び出します

AWS.Client.request(aws_client, :post, url, encoded_payload, siged_headers, options)

実行結果

{:ok, %{ body: "{\"metrics\":{\"latencyMs\":706},\"output\":{\"message\":{\"content\":[{\"text\":\"私の名前はClaudeです。よろしくお願いします。\"}],\"role\":\"assistant\"}},\"stopReason\":\"end_turn\",\"usage\":{\"inputTokens\":19,\"outputTokens\":23,\"totalTokens\":42}}", headers: [ {"Date", "Wed, 28 Aug 2024 07:40:18 GMT"}, {"Content-Type", "application/json"}, {"Content-Length", "244"}, {"Connection", "keep-alive"}, {"x-amzn-RequestId", "07b47719-ae2d-4822-93cd-158d2a9c7853"} ], status_code: 200 }}

AI Gateway 経由で Amazon Bedrock を呼び出す

AI Gateway のエンドポイント URL を組み立てます

cf_account_id = Kino.Input.read(cf_account_id_input)cf_gateway_name = Kino.Input.read(cf_gateway_name_input)cf_host = "gateway.ai.cloudflare.com"gw_url = "https://#{cf_host}/v1/#{cf_account_id}/#{cf_gateway_name}/aws-bedrock/bedrock-runtime/#{aws_region}#{encoded_path}"

署名付ヘッダーのうち、 "Host" の値を AI Gateway のホストに変換します

gw_header = siged_headers |> Enum.map(fn {key, value} -> if key == "Host" do {key, cf_host} else {key, value} end end)

実行結果

[ {"Authorization", "AWS4-HMAC-SHA256 Credential=xxx/20240828/us-east-1/bedrock/aws4_request,SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date,Signature=999"}, {"X-Amz-Content-SHA256", "aaa"}, {"X-Amz-Date", "20240828T045309Z"}, {"Host", "gateway.ai.cloudflare.com"}, {"Content-Type", "application/json"}]

Req を使って、普通の REST API のようにリクエストを投げます

result = Req.new(url: gw_url, headers: gw_header, body: encoded_payload) |> Req.post!(connect_options: [timeout: 60_000]) |> Map.get(:body)

実行結果

%{ "metrics" => %{"latencyMs" => 1008}, "output" => %{ "message" => %{ "content" => [ %{ "text" => "私の名前はClaudeです。人工知能アシスタントとして作られました。よろしくお願いします。" } ], "role" => "assistant" } }, "stopReason" => "end_turn", "usage" => %{"inputTokens" => 19, "outputTokens" => 38, "totalTokens" => 57}}

無事、 AI Gateway 経由で呼び出すことができました

AI Gateway のコンソールを見ると、リスエストとレスポンスのログが取得できています

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (7)

モジュール化

ここまでの処理を整理し、 AI Gateway 経由で Bedrock を呼び出すためのモジュールを定義しましょう

defmodule AIGateway do @cf_host "gateway.ai.cloudflare.com" def invoke(aws_client, model_id, cf_account_id, cf_gateway_name, input) do payload = %{ "messages" => [%{ "role" => "user", "content" => [%{"text" => input}] }] } host = "bedrock-runtime.#{aws_client.region}.amazonaws.com" headers = [ {"Host", host}, {"Content-Type", "application/json"} ] encoded_path = "/model/#{AWS.Util.encode_uri(model_id)}/converse" encoded_payload = AWS.Client.encode!(aws_client, payload, :json) gw_headers = sign_headers( aws_client, "https://#{host}#{encoded_path}", headers, encoded_payload ) gw_url = "https://#{@cf_host}/v1/#{cf_account_id}/#{cf_gateway_name}/aws-bedrock/bedrock-runtime/#{aws_client.region}#{encoded_path}" Req.new(url: gw_url, headers: gw_headers, body: encoded_payload) |> Req.post!(connect_options: [timeout: 60_000]) |> Map.get(:body) end defp sign_headers(aws_client, url, headers, payload) do now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) %{aws_client | service: "bedrock"} |> AWS.Signature.sign_v4( now, :post, url, headers, payload ) |> Enum.map(fn {key, value} -> if key == "Host" do {key, @cf_host} else {key, value} end end) endend

キャッシュの確認

キャッシュ機能を試してみたいので、 AI Gateway でキャッシュを有効にします

AI Gateway の画面から「設定」タブを開き、「キャッシュ レスポンス」のトグルを ON にしてください

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (8)

Livebook で任意の質問・指示ができるように、テキストエリアを用意します

instruction_input = Kino.Input.textarea("INSTRUCTION")

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (9)

定義したモジュールを使って AI Gateway を呼び出し、結果を表示します

instruction = Kino.Input.read(instruction_input)aws_client|> AIGateway.invoke(model_id, cf_account_id, cf_gateway_name, instruction)|> Map.get("output")|> Map.get("message")|> Map.get("content")|> hd()|> Map.get("text")|> Kino.Markdown.new()

実行結果

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (10)

約8秒程でちゃんと答えが返ってきました

正確な源泉数や湧出量はこちらを参照してください

https://www.city.beppu.oita.jp/sangyou/onsen/detail4.html

このまま質問を変えずにもう一度セルを実行すると、今度は1秒未満でなじ結果が返ってきます

AI Gateway の画面で確認すると、キャッシュで返答したことが分かります

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (11)

レート制限

「設定」タブのレート制限から、1分間に1回のレート制限を設定してみます

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (12)

この状態で立て続けに呼び出すと、 "Rate limited" というメッセージが返ってきます

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (13)

AI Gateway のログ上でもエラーになっていることが確認できます

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (14)

分析

AI Gayeway の「分析」タグを開くと、使用状況がグラフ化されているのが確認できます

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (15)

API からのログ取得

Cloudflare API から AI Gateway のログを取得できます

API トークンの作成

Cloudflare の右上アイコンをクリックし、表示されるドロップダウンから「マイプロフィール」を開きます

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (16)

左メニューの「API トークン」を開き、「トークンを作成する」をクリックします

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (17)

遷移した画面の一番下「カスタムトークンを作成する」の右にある「始める」をクリックします

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (18)

トークン名を適当な値にして、アクセス許可で「AI Gateway」の「読み取り」を選択します

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (19)

画面一番下の「概要に進む」をクリックします

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (20)

内容を確認し、「トークンを作成する」をクリックします

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (21)

トークンの値が表示されるので、この値を Livebook で使用します

API の呼び出し

Livebook にトークン用の入力を作成し、トークンの値を設定します

cf_token_input = Kino.Input.password("CLOUDFLARE TOKEN")

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (22)

トークンを認証ヘッダーに設定し、 API を呼び出します

cf_api_url = "https://api.cloudflare.com/client/v4/accounts/#{cf_account_id}/ai-gateway/gateways/#{cf_gateway_name}/logs"cf_token = Kino.Input.read(cf_token_input)cf_api_headers = [ {"Authorization", "Bearer #{cf_token}"}, {"Content-Type", "application/json"}]result = Req.new(url: cf_api_url, headers: cf_api_headers) |> Req.get!() |> Map.get(:body)

実行結果

%{ "result" => [ %{ "cached" => false, "cost" => 0, "created_at" => "2024-08-28 09:05:19", "duration" => 8346, "id" => "01J6C3PX50TTFCQDE9NCXJDCFS", "metadata" => "", "model" => "", "path" => "bedrock-runtime/us-east-1/model/anthropic.claude-3-5-sonnet-20240620-v1%3A0/converse", "provider" => "aws-bedrock", "request" => "{\"messages\":[{\"content\":[{\"text\":\"「やせうま」とはどういう食べ物ですか\"}],\"role\":\"user\"}]}", "request_content_type" => "application/json", "request_type" => "provider", "response" => "{\"metrics\":{\"latencyMs\":7112},\"output\":{\"message\":{\"content\":[{\"text\":\"「やせうま」は、日本の伝統的な菓子の一種です。以下に「やせうま」の特徴をまとめます:\\n\\n1. 形状:細長い棒状", "response_content_type" => "application/json", "status_code" => 200, "step" => 0, "success" => true, "tokens_in" => 0, "tokens_out" => 0 }, ... ], "result_info" => %{"count" => 19, "page" => 1, "per_page" => 20, "total_count" => 19}, "success" => true}

リクエストやレスポンスの内容、時間、実行結果、キャッシュなどの情報が取得できました

まとめ

AI Gateway を使うことで、 Bedrock のログ取得やキャッシュ設定、レート制限などが簡単に実装できました

生成AIの利用実績を分析したい場合などには有効そうです

Elixir Livebook から Cloudflare の AI Gateway 経由で Amazon Bedrock を呼んでみる - Qiita (2024)
Top Articles
Latest Posts
Recommended Articles
Article information

Author: Dan Stracke

Last Updated:

Views: 6245

Rating: 4.2 / 5 (63 voted)

Reviews: 94% of readers found this page helpful

Author information

Name: Dan Stracke

Birthday: 1992-08-25

Address: 2253 Brown Springs, East Alla, OH 38634-0309

Phone: +398735162064

Job: Investor Government Associate

Hobby: Shopping, LARPing, Scrapbooking, Surfing, Slacklining, Dance, Glassblowing

Introduction: My name is Dan Stracke, I am a homely, gleaming, glamorous, inquisitive, homely, gorgeous, light person who loves writing and wants to share my knowledge and understanding with you.