JMDC TECH BLOG

JMDCのエンジニアブログです

Amazon Bedrock✖️GenkitをGoで構築してみる

はじめに

皆様、こんにちは!今年8月からJMDCに入社した秦(しん)です。 今年、JMDCではアドベントカレンダーに参加しています。

qiita.com

本記事は、JMDC Advent Calendar 2025 の 21日目の記事です。


この記事では、Google が提供する AI アプリケーション開発フレームワーク「Genkit」を Go で使用し、AWS Bedrock と連携して生成 AI 機能を構築する方法についてサンプルを用いて解説します。

Genkit は Firebase/GCP との連携が注目されがちですが、実は AWS Bedrock とも組み合わせて使用できます。

この記事の内容

この記事では主に以下の内容を紹介します。

  • Genkit と AWS Bedrock の連携方法
  • Go のジェネリクスを活用した型安全な構造化データ生成
  • 構造化出力のスキーマ設計、信頼度スコアの活用など

目次

技術スタック

本記事で利用するライブラリは以下の通りです。

利用する技術 紹介
Go Google生まれのプログラミング言語、簡単に並行処理を書けるのが特徴。
Genkit Google傘下のFirebaseが作った生成AIメタフレームワーク。
プラグインによって多数の生成AIベンダーをサポートしています。
Go以外でJavascriptとPythonもサポートしています。
genkit-aws-bedrock-go GenkitのAWS Bedrock プラグイン、公式ではありませんが、開発は活発です 。
AWS SDK for Go v2 AWS 認証・接続に利用します。

環境構築

まず、プロジェクトに必要なパッケージをインストールします。

必要な依存関係

以下の3つのパッケージが必要です:

  • Genkit 本体: AI 生成の中核となるフレームワーク
  • Bedrock プラグイン: Genkit と AWS Bedrock を接続するアダプター
  • AWS SDK: AWS の認証・設定を管理
// go.mod
require (
    github.com/firebase/genkit/go v1.0.4
    github.com/xavidop/genkit-aws-bedrock-go v1.7.0
    github.com/aws/aws-sdk-go-v2/config v1.31.12
)
go get github.com/firebase/genkit/go
go get github.com/xavidop/genkit-aws-bedrock-go
go get github.com/aws/aws-sdk-go-v2/config

AWS 認証情報の設定

AWS Bedrock に接続するためには、適切な認証情報が必要です。開発環境と本番環境で異なる方法を選択できます。

1. AWS Profile を使用する場合(開発環境向け)

ローカル開発時は AWS Profile を使用するのが簡単です。

# ~/.aws/credentials
[your-profile]
aws_access_key_id = YOUR_ACCESS_KEY
aws_secret_access_key = YOUR_SECRET_KEY

# ~/.aws/config
[profile your-profile]
region = us-east-1

2. Bedrock へのアクセス権限

以下のような IAM ポリシーが必要です。上記で設定したプロファイルに権限を付与してください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "bedrock:InvokeModel",
                "bedrock:InvokeModelWithResponseStream"
            ],
            "Resource": "arn:aws:bedrock:*::foundation-model/*"
        }
    ]
}

アクセスできるモデルを確認してみる

以下のように、使えるモデルが見えていれば設定完了です。

## ここではanthropicのモデルに絞っている
aws bedrock list-foundation-models --by-provider anthropic --query 'modelSummaries[?modelLifecycle.status==`ACTIVE`].[modelId, modelLifecycle.status]' --output table --profile your-profile
##
------------------------------------------------------------
|                   ListFoundationModels                   |
+------------------------------------------------+---------+
|  anthropic.claude-sonnet-4-20250514-v1:0       |  ACTIVE |
|  anthropic.claude-haiku-4-5-20251001-v1:0      |  ACTIVE |
|  anthropic.claude-sonnet-4-5-20250929-v1:0     |  ACTIVE |
|  anthropic.claude-opus-4-5-20251101-v1:0       |  ACTIVE |
|  anthropic.claude-3-haiku-20240307-v1:0        |  ACTIVE |
|  anthropic.claude-3-5-sonnet-20240620-v1:0     |  ACTIVE |
|  anthropic.claude-3-sonnet-20240229-v1:0:28k   |  ACTIVE |
|  anthropic.claude-3-sonnet-20240229-v1:0:200k  |  ACTIVE |
|  anthropic.claude-3-sonnet-20240229-v1:0       |  ACTIVE |
|  anthropic.claude-3-5-sonnet-20241022-v2:0     |  ACTIVE |
|  anthropic.claude-3-7-sonnet-20250219-v1:0     |  ACTIVE |
+------------------------------------------------+---------+
##

Genkit クライアントの実装

ここからが本題です。Genkit を使って AWS Bedrock と連携するクライアントを実装していきます。

初期化処理では以下の流れで設定を行います:

  1. AWS Config を読み込む
  2. Bedrock プラグインを初期化する
  3. Genkit を初期化し、プラグインを登録する
  4. 使用するモデルを定義する
package genkit

import (
    "context"

    awsconfig "github.com/aws/aws-sdk-go-v2/config"
    "github.com/firebase/genkit/go/ai"
    "github.com/firebase/genkit/go/genkit"
    bedrock "github.com/xavidop/genkit-aws-bedrock-go"
)

type BedrockClient struct {
    genkit       *genkit.Genkit
    defaultModel *ai.Model
}

func NewBedrockClient(region, profile, bedrockRegion, modelName string) *BedrockClient {
    // AWS Config の読み込み
    options := []func(*awsconfig.LoadOptions) error{
        awsconfig.WithDefaultRegion(region),
    }
    if profile != "" {
        options = append(options, awsconfig.WithSharedConfigProfile(profile))
    }
    cfg, _ := awsconfig.LoadDefaultConfig(context.TODO(), options...)

    // Bedrock プラグインの初期化
    bedrockPlugin := &bedrock.Bedrock{
        Region:    bedrockRegion,
        AWSConfig: &cfg,
    }
    
    // Genkit の初期化
    g := genkit.Init(
        context.TODO(),
        genkit.WithPlugins(bedrockPlugin),
    )
    
    // デフォルトモデルの定義
    defaultModel := bedrockPlugin.DefineModel(g, bedrock.ModelDefinition{
        Name: modelName,  // 例: "anthropic.claude-3-sonnet-20240229-v1:0"
        Type: "text",
    }, nil)

    return &BedrockClient{
        genkit:       g,
        defaultModel: &defaultModel,
    }
}

func (c *BedrockClient) Client() *ai.Client {
    return c.genkit
}

テキスト生成メソッド

基本的なテキスト生成は genkit.Generate を呼び出すだけで実行できます。モデルを指定しない場合はデフォルトモデルが使用されます。

func (c *BedrockClient) GenerateText(ctx context.Context, prompt string) (string, error) {
    resp, err := genkit.Generate(ctx, c.genkit, 
        ai.WithPrompt(prompt), 
        ai.WithModel(*c.defaultModel),
    )
    if err != nil {
        return "", err
    }
    
    return resp.Text(), nil
}

構造化データ生成(Structured Output)

Genkit のいいところの一つとして、「構造化データ生成」がとても簡単にできます。通常、LLM の出力は自由形式のテキストですが、Genkit を使うと Go の構造体に直接マッピング できます。これにより、後続の処理でパースする必要がなくなり、型安全なコードが書けます。

レスポンスモデルの設計

まず、すべての生成結果を包む汎用的なレスポンス構造体を定義します。

ポイント:

  • Result フィールド : 生成するデータはGenerics Tとして定義します。これによって、ユーザーは自前のデータスキーマを定義できるようになります。
  • Confidence フィールド: これにより、LLM 自身に生成結果の信頼度を評価させることができます。ユーザーはこのフィールドによって、生成結果の利用の判断材料にできます
package models

import "github.com/firebase/genkit/go/ai"

type GenaiResponse[T any] struct {
    Result     T       `json:"result" jsonschema:"description=The generated result"`
    Confidence float32 `json:"confidence" jsonschema:"description=The confidence score of the generated result,minimum=0,maximum=1"`
}

type GenaiResult[T any] struct {
    Result     T
    Confidence float32
    Usage      ai.GenerationUsage
}

func NewGenaiResult[T any](response *GenaiResponse[T], usage ai.GenerationUsage) *GenaiResult[T] {
    return &GenaiResult[T]{
        Result:     response.Result,
        Usage:      usage,
        Confidence: response.Confidence,
    }
}

ジェネリック関数による構造化データ生成

次に、任意の型 O に対して構造化データを生成するジェネリック関数を実装します。これはGoのジェネリックス機能を利用したもので、構造化データの生成とデータスキーマの分離が綺麗に書けるようになっています。

ポイント:

  • genkit.GenerateData[T] を使うと、LLM の出力が自動的に型 T にマッピングされるので、関数の利用者は具体的な生成ロジックを知らなくても、ほしいデータの構造を渡すだけで、該当型で受け取ることが可能になります。
package genkit

import (
    "context"
    "strings"

    "github.com/firebase/genkit/go/ai"
    "github.com/firebase/genkit/go/genkit"
)

// GenerateData は構造化されたデータを生成するジェネリック関数です
func GenerateData[O any](
    ctx context.Context, 
    client BedrockClient, 
    prompt string, 
    model *ai.Model, 
    systemPrompt *string,
) (*GenaiResult[O], error) {
    var resp *GenaiResponse[O]
    var modelResponse *ai.ModelResponse
    var err error
    
    // システムプロンプトの有無で呼び出しを分岐
    if systemPrompt != nil {
        resp, modelResponse, err = genkit.GenerateData[GenaiResponse[O]](
            ctx, client.Client(),
            ai.WithPrompt(prompt),
            ai.WithSystem(*systemPrompt),
            ai.WithModel(*model),
        )
    } else {
        resp, modelResponse, err = genkit.GenerateData[GenaiResponse[O]](
            ctx, client.Client(),
            ai.WithPrompt(prompt),
            ai.WithModel(*model),
        )
    }
    
    if err != nil {
        // スキーマ不一致エラーの場合は空の結果を返す
        if strings.Contains(err.Error(), "model failed to generate output matching expected schema") {
            return &GenaiResult[O]{}, nil
        }
        return nil, err
    }
    
    // トークン使用量をログ出力(コスト管理に有用)
    Logger().InfoContext(ctx, "generate data",
        "confidence", resp.Confidence,
        "inputToken", modelResponse.Usage.InputTokens,
        "outputToken", modelResponse.Usage.OutputTokens,
        "totalToken", modelResponse.Usage.TotalTokens,
    )

    return NewGenaiResult(resp, *modelResponse.Usage), nil
}

実際に使ってみる

ここまでの実装を組み合わせて、実際に動くコードを書いてみましょう。

シンプルな使用例

以下は、Genkit クライアントを初期化して構造化データを生成する最小限のコード例です。

ポイント:

  • Profile 構造体の jsonschema タグで、LLM に期待する出力形式を指定
  • GenerateData[Profile] で型パラメータを指定するだけで、自動的にその型にマッピング
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log/slog"
    "os"
)

// 出力スキーマの定義
type Profile struct {
    Name    string   `json:"name" jsonschema:"description=ユーザー名"`
    Age     int      `json:"age" jsonschema:"description=年齢,minimum=0,maximum=99"`
    Hobbies []string `json:"hobbies" jsonschema:"description=趣味のリスト"`
}

func main() {
    ctx := context.Background()
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

    // クライアントの初期化
    client := genkit.NewBedrockClient(
        "ap-northeast-1",                              // AWS リージョン
        "",                                            // プロファイル(空の場合はデフォルト)
        "ap-northeast-1",                                   // Bedrock リージョン
        "anthropic.claude-sonnet-4-20250514-v1:0",     // モデル名
        logger,
    )

    // プロンプト
    prompt := "ジャムダックは3歳でジャム作りが好きです。"

    // 構造化データの生成
    result, err := genkit.GenerateData[Profile](ctx, client, prompt, nil, nil)
    if err != nil {
        panic(err)
    }

    // 結果の出力
    jsonBytes, _ := json.MarshalIndent(result.Result, "", "  ")
    fmt.Printf("生成結果:\n%s\n", string(jsonBytes))
    fmt.Printf("信頼度: %.2f\n", result.Confidence)
    fmt.Printf("使用トークン: %d\n", result.Usage.TotalTokens)
}

出力例

{
  "name": "ジャムダック",
  "age": 3,
  "hobbies": ["ジャムづくり"]
}
信頼度: 0.95
使用トークン: 156

Tips

実装してみて得られた知見を共有させてください。

1. 出力スキーマ設計のコツ

jsonschema タグの書き方で、LLM の出力品質が大きく変わります。以下のポイントを意識してください。

  • description: 該当フィールドで生成する内容物、内容が守るべきルールを詳細に記述する
  • enum: 該当フィールドに詰められるデータの選択が有限の場合、できるだけenumとして列挙しておくと、より正確な結果が得られます。
  • minimum / maximum: 該当フィールドが数値型の場合、値の範囲を指定すると、より正確な結果が得られます。

2. Confidence Score の活用

Confidence フィールドを活用することで、生成結果の品質を判断できます。信頼度が低い場合は、ユーザーに再入力を求めるなどの対応が可能です。また、一緒にフロントエンドに返すことによって、ユーザーに警告することも可能です。

result, _ := GenerateData[MyOutput](ctx, client, prompt, nil, nil)

if result.Confidence < 0.5 {
    // 信頼度が低い場合は追加の確認を求める
    return nil, errors.New("生成結果の信頼度が低いため、より具体的な入力をお願いします")
}

まとめ

Genkit + AWS Bedrock の組み合わせのメリット

  1. 型安全な構造化出力: Go のジェネリクスと JSON Schema を活用した型安全な AI 出力
  2. AWS エコシステムとの親和性: 既存の AWS インフラとシームレスに統合可能
  3. 柔軟なモデル選択: Bedrock で利用可能な複数のモデル(Claude, Titan など)を切り替え可能

今後の発展可能性

  • ストリーミング出力への対応: Genkitで簡単にストリーミング出力を扱うことができるので、大規模な生成に対応できます。
  • マルチモーダル対応(画像の出力など): Genkitはマルチモーダルにも対応しているので、画像入出力の要件にも簡単に対応できます。

参考リンク

最後に

明日22日目は、大出さんによる「クラウド移行プロジェクトを振り返って: 見える化と共通認識づくりの効果」です。お楽しみに!


JMDCでは、ヘルスケア領域の課題解決に一緒に取り組んでいただける方を積極採用中です!フロントエンド /バックエンド/ データベースエンジニア等、様々なポジションで募集をしています。詳細は下記の募集一覧からご確認ください。 hrmos.co まずはカジュアルにJMDCメンバーと話してみたい/経験が活かせそうなポジションの話を聞いてみたい等ございましたら、下記よりエントリーいただけますと幸いです。 hrmos.co ★最新記事のお知らせはぜひ X(Twitter)、またはBlueskyをご覧ください! twitter.com bsky.app