API Gatewayを利用したSageMaker runtime(Multipart/Form-data)の外部公開のための構築
はじめに
Nishikaでは、AI文字起こしサービスとして、SecureMemoおよびSecureMemoCloudを提供しています。これに加え、APIを通じたサービスの提供も積極的に行っています。
その一環として、リアルタイム推論APIの性能検証のためにSageMaker runtimeのエンドポイントをAPI Gateway経由で接続できるようにしたところ、設定に結構ハマったのでメモがてら共有いたします。
API Gatewayでは、SageMaker runtimeと直接接続することができるのですが、何か込み入ったことをやろうとすると途端に間にLambdaを挟もうと誘惑されます。
しかし、今回は、遅延を極力減らしたいため、Lambdaを挟まないように構築しています。
本記事に含まれるもの
- 以下のAPI Gateway関連の設定
- Multipart/Form-dataのSageMaker runtimeの外部公開(REST)
- API keyを利用した接続制限
- カスタムヘッダーを用いた識別子の渡し方
- マッピングテンプレートによるエラー時のレスポンス形式の変更
本記事に含まれないもの
- SageMaker runtime側の実装の詳細
- カスタムSageMakerエンドポイント
- FastAPIを用いて音声ファイルとパラメータを受け付け、文字起こし結果を返すもの
API Gateway(REST)構築手順
SageMaker runtime(Multipart/Form-data)との接続
カスタムしたエンドポイントにMultipart/Form-dataのリクエストをパススルーで渡すREST APIを設定いたします。
-
AWSコンソールでAPI Gatewayを開き、API->APIを作成を押下
-
REST APIの構築を押下
-
適当なAPI名と説明を入力し、他はデフォルトでAPI作成を押下
-
APIが作成され、リソースの画面が開かれるので、必要に応じてリソースの作成
- 今回はエンドポイントが一種類のため作成せずに、ルートにPostする形にします
-
メソッドを作成を押下して、以下の画面のように入力し、保存を押下
- AWSのサービス:
SageMaker Runtime
- アクションタイプは
パスオーバーライドを使用
を選択 - パスオーバーライドに
endpoints/{エンドポイント名}/invocations
を入力 - 実行ロールはエンドポイントを利用可能なロールを指定
- AWSのサービス:
-
Postメソッドが作られるので、メソッドリクエストの設定の編集を押下し、入力後、保存を押下
- HTTPリクエストヘッダーに
Content-Type
を追加
- HTTPリクエストヘッダーに
-
統合リクエストタブに移動し、統合リクエストの設定の編集を押下、入力後、保存を押下
- URLリクエストヘッダーのパラメータに以下を入力
- 名前:
Content-Type
- マッピング元:
method.request.header.Content-Type
- 名前:
- リクエストのContent-Typeをエンドポイントまでパススルーする設定
- メソッドリクエストで先に設定しておくことで、こちらのマッピング元の項目として設定できます
- URLリクエストヘッダーのパラメータに以下を入力
-
APIをデプロイを押下し、適当なステージで公開
- レスポンスはパススルー設定のため、特に設定はなしで大丈夫です
- 正常系の設定は以上となりますので、適当なツールで叩いてみて動作確認します
API Keyを利用した接続制限
特定クライアント向けのため、API Keyで接続制限をかけます。
-
当該メソッド(今回はルートのPOST)を押下して、メソッドリクエストの設定の編集を押下
-
APIキーは必須ですにチェックをつけて、保存
-
サイドメニューの使用量プランを開き、使用量プランを作成を押下
-
使用量プランの名前を入力し、今回はレート・リクエスト数の制限をかけませんので、スロットリングとクォータをオフにして保存を押下
-
関連づけられたステージのステージを追加を押下し、作成したAPIを選択し、必要なステージを選択し、使用量プランに追加を押下
-
関連づけられたAPIキータブに移動し、APIキーの作成を押下
-
新しいキーを作成して追加を選択し、APIキーの名前を入力し、キーを自動生成でAPIキーを追加を押下
-
該当APIに戻り、APIを再デプロイして、リクエストヘッダーに以下を追加し、動作確認
X-API-Key:{自動生成されたAPIキーの値}
カスタムヘッダーを用いた識別子の渡し方
SageMakerでは、指定されたパラメータのみ受け付けます
(うっかり自分で設定したカスタムヘッダーを利用しようとして、あれ?なんで渡せないの??となったのは私以外にもいると思いたい)
ユーザーが利用できるカスタムヘッダーとしては、X-Amzn-SageMaker-Custom-Attributes
が用意されています。最大1024文字までですので、込み入ったことをしようとすると足りなくなるかもしれません。
今回の要望は、クライアントから処理を追うための識別子をリクエストヘッダーに追加して送るので、後々の分析用途で使えるログとして出力するというものです
クライアント側からは、X-TransactionId
を数十文字のIDとして送ってもらうことにします
エンドポイント上のFastAPIでは、X-Amzn-SageMaker-Custom-Attributes
として受け取りますので、API Gatewayのリクエスト部分でX-TransactionId
=>X-Amzn-SageMaker-Custom-Attributes
とマッピングする必要があります
ヘッダーのマッピングを設定していきます
- 該当メソッドを開いて、メソッドリクストの設定の編集を押下
- HTTPリクエストヘッダーにクライアントから受け取る予定のヘッダーのKey(X-TransactionId)を入力し、保存
- 統合リクエストのタブに移動し、統合リクエストの設定の編集を押下
- URLリクエストヘッダーのパラメータを以下のように入力し、保存を押下
- 名前:
X-Amzn-SageMaker-Custom-Attributes
- マッピング元:
method.request.header.X-TransactionId
- 名前:
- APIを再デプロイし、リクエストヘッダーに以下を追加し、動作を確認
X-TransactionId:{適当なサンプルTransactionID}
マッピングテンプレートによるエラー時のレスポンス形式の変更
クライアント側からエラーレスポンスについて、形式の指定があったのでマッピングテンプレートを用いて成形いたします。
レスポンスはパススルー設定のため、FastAPIのエラーハンドリングでレスポンス形式を成形するだけだと思っていたのですが、SageMakerを通したエラーレスポンスは以下のような出力でした。私が成形したエラーレスポンスはOriginalMessage
の中にJSONを文字列化した形ではいっておりました。
{
"ErrorCode": "CLIENT_ERROR_FROM_MODEL",
"LogStreamArn": "arn:aws:logs:ap-northeast-1:111111111:log-group:/aws/sagemaker/Endpoints/hogehoge-endpoint",
"Message": "Received client error (400) from primary with message \"{JSON形式の文字列化された元々のエラーメッセージ}". See https://ap-northeast-1.console.aws.amazon.com/cloudwatch/home?region=ap-northeast-1#logEventViewer:group=/aws/sagemaker/Endpoints/hogehoge-endpoint in account 111111111 for more information.",
"OriginalMessage": {JSON形式の文字列化された元々のエラーメッセージ},
"OriginalStatusCode": 400
}
OriginalMessageの中身が元々のエラーレスポンス形式なので、再マッピングしていきます
また、4XX系と5XX系の2つを設定しますが、ほぼ同じ内容なので、今回は4XX系のエラーのみ記載いたします
- 当該メソッドを開いて、メソッドレスポンスタブを開き、レスポンスを作成を押下
- HTTPステータスコードに400と入力し、保存を押下
- 統合レスポンスタブに移動し、レスポンスを作成を押下
- HTTPステータスの正規表現とメソッドレスポンスのステータスコードを入力して、作成を押下
- 4XX系のエラーを400で出してしまう省力設定なのでご注意ください
- 4XX系のエラーを400で出してしまう省力設定なのでご注意ください
- 作成した4\d\dレスポンスの編集を押下し、マッピングテンプレートを以下のように追加して保存を押下
コンテンツタイプ: application/json
#set($body = $input.path('$'))
#set($originalMessage = $util.parseJson($body.OriginalMessage))
$originalMessage
一行目のsetでは、エラーレスポンスを$bodyという変数で受け取ってます
二行目の$util.parseJsonでは、文字列化されていたOriginalMessageをJsonオブジェクトとしてパースします
三行目で、元々のエラーレスポンスのテンプレート通りにマッピングします
また、ここでは割愛しますが、サイドメニューのゲートウェイのレスポンスから、API Keyが不正等の理由でAPI Gatewayが直接返してしまうエラーレスポンス形式についても設定が可能です。個別に設定する場合は、各個別項目を設定し、一括で設定する場合は、DEFAULT 4XX/5XXを設定いたします。
終わりに
SageMaker runtimeとAPI Gatewayの組み合わせに関する設定には予想以上に時間がかかりました。
その経験から、この記事を備忘録として、そして他の方の参考になればと考えてまとめました。
API Gatewayの設定について多くの資料を参照しましたが、Lambdaを介さずにSageMakerと直接連携する内容は比較的少ないように思われました。この記事が少しでも皆さんのお役に立てば幸いです。
We're hiring!
Nishikaテックチームでは、「テクノロジーを、普段テクノロジーからは縁の遠い人にとっても当たり前の存在としていく」を目指し、音声AIプロダクトの開発・生成AIを活用した課題解決ソリューションの構築を行なっています。
興味をお持ちいただけた方は、以下リンクからご応募お待ちしています。インターンも募集しております!
Discussion