Amazon Bedrockで生成したKubernetes Starter Kitのイメージ
大規模言語モデル(LLM)をアプリケーションに組み込むと、機能性も使い勝手も大きく向上します。とはいえ、既存のコードベースに無理なく統合し、しかもスケーラブルに実装するとなると、なかなか一筋縄ではいきません。
そこで本記事では、すぐに使い始められるスタータープロジェクトを紹介します。
なぜGoをベースにするのか
巷で見かける生成AIのサンプルはほとんどがPythonで書かれています。PythonはMachine LearningやData Scienceの共通言語ですから、これは自然な流れでしょう。ただし、自前でモデルを作ったり本格的なMachine Learningのタスクをこなしたりするわけではなく、既存のアプリケーションに一般的なAPI呼び出しでMachine Learningモデルを組み込みたいだけ、というケースもあります。
そうした用途では、Amazon BedrockやChatGPTといったMachine Learning APIをはじめとするWebサービスを呼び出す言語として、Goは非常に適しています。
本記事のスターターキットはAmazon Bedrockに焦点を当てたもので、以下のリポジトリで公開しています。
https://github.com/p-obrien/bedrock-microservice-starter
事前準備 — Amazon Bedrockのモデルを有効化する
Amazon Bedrockを利用するには、対象のFoundational Modelを有効化する必要があります。あいにく現時点(2024年7月)では、これはAWSコンソール上でしか操作できません。
Foundational Modelを有効化する手順は次のとおりです。
1. AWSコンソールにログインし、Amazon Bedrockに移動します。
2. 左側のナビゲーションペインを展開し、「Model access」までスクロールします。
3. 「Find model」ダイアログで有効化したいモデルを検索します。本スタータープロジェクトでは「Claude 3 Sonnet」を使用します。プロジェクトでまだ有効化されていない場合はアクセス申請が必要です。「request model access」リンクをクリックし、有効化したいモデルを選択して、続く画面に従って進めてください。
有効化が完了すると、次のように表示されます。

AWS Bedrock Base Models
注:モデルが利用できるリージョンは限られています。普段のworkloadsとは別のリージョンでモデルを動かすことも検討する必要があるかもしれません。
**スターターキットの概要**
このスターターキットは、ご自身のソリューションを組み立てる際の参考実装として用意したもので、次のことを行います。
- マネージドノードグループ付きのGravitonベースEKSクラスターをデプロイ
- AWS Load Balancer Controllerをデプロイ
- EKS Pod Identityとサービスアカウントをデプロイ
Goコードでは、次のようなマイクロサービスをデプロイします。
- 呼び出し元の認証にAPIキーを使用
- EKS Pod Identityを介してAmazon Bedrockと通信
- Amazon Bedrock(ここではClaude SonnetのFoundational Model)との会話結果を返却。カスタマイズも容易です
すでにEKSクラスターをお持ちであれば既存クラスター上でも動作させられますが、その場合はPod Identities Agentとサービスアカウントがデプロイ済みであることを確認してください。
**インフラ**
このスターターキットでは、TerraformまたはOpenTofuを使って新規にAWS EKSクラスターをデプロイします。使い方の詳細はInfrastructureフォルダー内のREADME.mdをご覧ください。
コンテナ内にアクセスキーやシークレットを置いたり、別の認証方式を使ったりする代わりに、AWS EKSのPod Identitiesという機能でBedrockへのアクセス権を付与します。
少し前まではこの用途にEKS IRSA(IAM Roles for Service Accounts)が使われていましたが、セットアップが複雑なうえ制約もありました。EKS Pod Identitiesはこうした課題を解消し、よりシンプルに使える仕組みになっています。詳しく知りたい方はこちらをご覧ください。
EKS Pod Identity
ステップ1 — EKS Pod Identityアドオンをデプロイする

ステップ2 — ロールを定義し、Pod Identityアドオンが引き受けられるようにする

ステップ3 — Kubernetesのサービスアカウントとロールを関連付ける

ステップ4 — EKS側にも対応するサービスアカウントがあることを確認する

コード
マイクロサービスからAmazon Bedrockへの通信はAmazon Bedrock Go API経由で行います。
注:本スタータープロジェクトで取り上げているよりも、はるかに高度なやり取りも実装可能です。
認証はシンプルに
EKS Pod Identitiesを使う利点は、アプリケーションからほぼ意識せずに済むことです。たとえば次のコードでは、Go Bedrock APIでクライアントを生成し、そのままEKS Pod Identitiesによる認証を利用しています。
func init() {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatalf("unable to load SDK config, %v", err)
}
brc = bedrockruntime.NewFromConfig(cfg)
}
シークレットの読み込みを気にする必要はありません。Pod側にAWS Profileの環境変数が自動的に設定されるため、Bedrockクライアントはデフォルト設定で初期化するだけで動きます。
RESTサーバー
Echo Frameworkを使い、認証付きのRESTエンドポイントを用意して呼び出し元からのリクエストを受け付けます。本スタータープロジェクトでは簡易的なAPIキーを使っています。
// Sample API Key please don't use this in production and consider something more robust
var apiKey string = "test-api-key"
var brc *bedrockruntime.Client
const modelID = "anthropic.claude-3-sonnet-20240229-v1:0"
func main() {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatal(err)
}
brc = bedrockruntime.NewFromConfig(cfg)
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) {
return key == apiKey, nil
}))
e.POST("/converse", converseHandler)
if err := e.Start(":8080"); err != http.ErrServerClosed {
log.Fatal(err)
}
}
続いて、Multipart Formの入力からプロンプトを取り出し、Bedrockに渡したうえで結果をクライアントに返します。
func converseHandler(c echo.Context) error {
// Initialize the Bedrock ConverseInput with the model ID
converseInput := &bedrockruntime.ConverseInput{
ModelId: aws.String(modelID),
}
// Get the user input from the form value
input := c.FormValue("message")
// Create the user's message
userMsg := types.Message{
Role: types.ConversationRoleUser,
Content: []types.ContentBlock{
&types.ContentBlockMemberText{
Value: input,
},
},
}
// Append the user's message to the conversation input
converseInput.Messages = append(converseInput.Messages, userMsg)
// Call the Bedrock Converse API
output, err := brc.Converse(context.Background(), converseInput)
if err != nil {
log.Fatal("Error calling Converse API:", err)
return c.String(http.StatusInternalServerError, "Internal Server Error")
}
// Extract the response from the assistant
response, ok := output.Output.(*types.ConverseOutputMemberMessage)
if !ok {
return c.String(http.StatusInternalServerError, "Failed to parse response")
}
responseContentBlock := response.Value.Content[0]
text, ok := responseContentBlock.(*types.ContentBlockMemberText)
if !ok {
return c.String(http.StatusInternalServerError, "Failed to parse response content")
}
// Create the assistant's message
assistantMsg := types.Message{
Role: types.ConversationRoleAssistant,
Content: response.Value.Content,
}
// Append the assistant's message to the conversation input
converseInput.Messages = append(converseInput.Messages, assistantMsg)
// Return the assistant's response to the client
return c.JSON(http.StatusOK, text.Value)
}
さらなる拡張
読者への宿題として残しておきたい改善点には、次のようなものがあります。
- ストリーミングレスポンス — Echo Frameworkはストリーミングレスポンスに対応しているので、Bedrockからの応答もストリーミングで受け取れると便利です
- システムプロンプト — クライアントとのやり取りに入る前に、特定のプロンプトでAmazon Bedrockを初期化するシステムプロンプトの追加
おわりに
お読みいただきありがとうございました。ご質問やフィードバックがあれば、お気軽にGitHubリポジトリにissueを立ててください。
DoiT Internationalをまだご存じない方は、ぜひ一度のぞいてみてください。私たちのチームは、お客様のクラウドエンジニアリングに関する課題をいつでもお伺いします。シニアクラスのEngineersのみで構成されたチームが、高度なクラウドコンサルティング、アーキテクチャ設計、デバッグに関するアドバイスを提供します。お気軽にお問い合わせください。