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
Erie, Pa. to host first General Tire Team Series B&W Trailer Hitches Challenge Cup Presented by Toyota
Pronóstico del tiempo de 10 días para Diébù Xiàn, Gansu, República Popular China - The Weather Channel | weather.com
Explore Tarot: Your Ultimate Tarot Cheat Sheet for Beginners
What Happened To Dr Ray On Dr Pol
How To Get Free Credits On Smartjailmail
Poplar | Genus, Description, Major Species, & Facts
What is international trade and explain its types?
Elden Ring Dex/Int Build
Paketshops | PAKET.net
Smokeland West Warwick
Call Follower Osrs
4Chan Louisville
Cincinnati Bearcats roll to 66-13 win over Eastern Kentucky in season-opener
Jack Daniels Pop Tarts
charleston cars & trucks - by owner - craigslist
065106619
Dirt Removal in Burnet, TX ~ Instant Upfront Pricing
Craigslist In Visalia California
Gopher Hockey Forum
Skip The Games Fairbanks Alaska
What Channel Is Court Tv On Verizon Fios
Doki The Banker
Filthy Rich Boys (Rich Boys Of Burberry Prep #1) - C.M. Stunich [PDF] | Online Book Share
Lost Pizza Nutrition
Valic Eremit
Chicago Based Pizza Chain Familiarly
Divina Rapsing
The Boogeyman (Film, 2023) - MovieMeter.nl
Ticket To Paradise Showtimes Near Cinemark Mall Del Norte
Rek Funerals
Kleinerer: in Sinntal | markt.de
The Bold and the Beautiful
Beth Moore 2023
Green Bay Crime Reports Police Fire And Rescue
Craigslist Georgia Homes For Sale By Owner
KM to M (Kilometer to Meter) Converter, 1 km is 1000 m
Mckinley rugzak - Mode accessoires kopen? Ruime keuze
Puretalkusa.com/Amac
Andrew Lee Torres
Nid Lcms
Sand Castle Parents Guide
Www Craigslist Com Atlanta Ga
Poe Self Chill
Pike County Buy Sale And Trade
Ups Authorized Shipping Provider Price Photos
Contico Tuff Box Replacement Locks
Rite Aid | Employee Benefits | Login / Register | Benefits Account Manager
Bank Of America Appointments Near Me
Muni Metro Schedule
Ingersoll Greenwood Funeral Home Obituaries
Latest Posts
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.