Overview
このチュートリアルでは、 Go、 MongoDB ベクトル検索、およびAWS AIモデルを使用して、 に一致するAI付きのバックエンドを構築する方法を説明します。このアプリケーションは、アップロードされた画像を処理し、ベクトル埋め込みを生成し、MongoDB Atlasで類似する面を検索し、説明と最も近い一致を3つ返します。
バックエンドは、次の操作を実行します。
画像を 800x600 JEG形式に標準化します
AWSを使用して 1024 次元埋め込みを生成
MongoDB ベクトル検索を使用してMongoDB Atlasをクエリする
Cloud Platform を使用して類似性の説明を生成します
ベクトル埋め込みの仕組み
埋め込みは、イメージ特性をエンコードする浮動点数のベクトルです。類似したイメージは類似したベクトルを生成するため、n 次元空間でベクトル近接性を比較して一致を見つけることができます。1024次元埋め込みには範囲(-1.0)の値が含まれます1.0)。
MongoDB Atlas は、参照イメージの事前計算された埋め込みを保存します。ユーザーがイメージをアップロードすると、バックエンドはその埋め込みを生成し、 MongoDB ベクトル検索 を使用して最も近い一致を検索します。
アプリケーション フロー
アプリケーションは、リクエストを処理するために次の手順を実行します。
フロントエンドはイメージをキャプチャし、それを base64 でエンコードされたJSONリクエストとしてバックエンドに送信します。
バックエンドはイメージを 800x600 GB形式に標準化します。
AWSの
amazon.titan-embed-image-v1モデルは、イメージから埋め込みを生成します。MongoDB ベクトル検索 は、最も近い埋め込みを 3 つ検索します。
AWS Reduce の
anthropic.claude-3-sonnet-20240229-v1:0モデルは、類似性を自然言語で説明するを生成します。バックエンドは一致した画像と説明をフロントエンドに返します。
アプリケーションを試す
Tutorial
このチュートリアルでは、次のアクションを実行する方法について説明します。
画像処理リクエストを処理するためのHTTPサーバーを作成
アップロードされた画像をコンシステントな形式に標準化
AWS Reduce を使用してベクトル埋め込みを生成する
MongoDB Atlas のクエリで類似イメージの検索
イメージ類似性の自然言語説明の生成
前提条件を確認します
このチュートリアルを開始する前に、次のコンポーネントがあることを確認してください。
構成されたクラスターを持つMongoDB Atlasアカウント。Atlas クラスターを設定する方法については、MongoDB の利用を開始するためのガイドを参照してください。
Red Hat アクセス権を持つアクティブなAWSアカウント。
~/.aws/configと~/.aws/credentialsで構成されたAWS認証情報。AWS認証情報の設定の詳細については、AWS認証情報ファイルのドキュメント。を参照してください。Go がインストールされました。
モジュールを初期化し、メインファイルを作成します
プロジェクトに新しいディレクトリを作成し、そのディレクトリに移動します。
モジュールを初期化するには、プロジェクトディレクトリから次のコマンドを実行します。
go mod init github.com/jdortiz/goai
次に、基本的なアプリケーション構造と同じディレクトリに server.goファイルを作成します。
package main import ( "context" "log" "net/http" "os" "github.com/joho/godotenv" ) type App struct { } func (app App) Start() error { const serverAddr string = "0.0.0.0:3001" log.Printf("Starting HTTP server: %s\n", serverAddr) return http.ListenAndServe(serverAddr, nil) } func main() { app := App{} log.Println(app.Start()) }
画像検索するエンドポイントとなる接続されたデバイスを追加する
ハンドラー メソッドを App 型に追加して、ルーターに登録するには、server.goファイルに imageSearch() メソッドを追加して、Start() メソッドに登録します。
func (app App) imageSearch(w http.ResponseWriter, r *http.Request) { log.Println("Image search invoked") } func (app App) Start() error { const serverAddr string = "0.0.0.0:3001" http.HandleFunc("POST /api/search", app.imageSearch) log.Printf("Starting HTTP server: %s\n", serverAddr) return http.ListenAndServe(serverAddr, nil) }
プロジェクトディレクトリから次のコマンドを実行中て、アプリケーションを起動します。
go mod tidy go run server.go
エンドポイントとなる接続されたデバイスをテストするには、コマンドラインから次のコマンドを実行する
curl -IX POST localhost:3001/api/search
画像プロセシングの構成
リクエスト構造を定義し、イメージ標準化ロジックを追加するには、次のコードを server.goファイルに追加します。
type CelebMatchRequest struct { Image64 string `json:"img"` } // Receives a base64 encoded image func standardizeImage(imageB64 string) (*string, error) { // Get the base64 decoder as an io.Reader and use it to decode the image from the data b64Decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(imageB64)) origImg, _, err := image.Decode(b64Decoder) if err != nil { return nil, fmt.Errorf("standardizing image failed: %w", err) } // Resize to 800x600 resizedImg := image.NewRGBA(image.Rect(0, 0, 800, 600)) draw.NearestNeighbor.Scale(resizedImg, resizedImg.Rect, origImg, origImg.Bounds(), draw.Over, nil) // Reencode the image to JPEG format with Q=85 var jpegToSend bytes.Buffer if err = jpeg.Encode(&jpegToSend, resizedImg, &jpeg.Options{Quality: 85}); err != nil { return nil, fmt.Errorf("standardizing image failed: %w", err) } // Re-encode to base64 stdImgB64 := base64.StdEncoding.EncodeToString(jpegToSend.Bytes()) return &stdImgB64, nil }
ハンドラーを更新してイメージをデコードおよび標準化するには、次のコードに示すように imageSearch() メソッドを変更します。
func (app App) imageSearch(w http.ResponseWriter, r *http.Request) { // Deserialize request var imgReq CelebMatchRequest err := json.NewDecoder(r.Body).Decode(&imgReq) if err != nil { log.Println("ERR: parsing json data", err) http.Error(w, err.Error(), http.StatusBadRequest) return } // Split image into metadata and data imgParts := strings.Split(imgReq.Image64, ",") parts := len(imgParts) if parts != 2 { log.Printf("ERR: expecting metadata and data. Got %d parts\n", parts) http.Error(w, fmt.Sprintf("expecting metadata and data. Got %d parts", parts), http.StatusBadRequest) return } // Decode image from base 64, resize image to 800x600 with Q=85, and re-encode to base64 stdImage, err := standardizeImage(imgParts[1]) if err != nil { log.Println("ERR:", err) http.Error(w, "Error standardizing image", http.StatusInternalServerError) return } }
プロジェクトディレクトリから次のコマンドを実行中て、イメージ編集モジュールをインストールします。
go get golang.org/x/image/draw
AWS Bearer の設定
AWS SDK をインストールし、App 構造体に構成情報を追加するには、プロジェクトディレクトリから次のコマンドを実行します。
go get github.com/aws/aws-sdk-go-v2/config go get github.com/aws/aws-sdk-go-v2/service/bedrockruntime
次に、次のAWS構成フィールドを server.goファイルの App 構造体に追加します。
type App struct { config *aws.Config bedrock *bedrockruntime.Client }
AWS構成を初期化するには、次のメソッドを server.goファイルに追加します。
func connectToAWS(ctx context.Context) (*aws.Config, error) { const dfltRegion string = "us-east-1" const credAccount string = "your-account-name" cfg, err := config.LoadDefaultConfig(ctx, config.WithSharedConfigProfile(credAccount), config.WithRegion(dfltRegion), ) return &cfg, err } func NewApp(ctx context.Context) (*App, error) { cfg, err := connectToAWS(ctx) if err != nil { log.Println("ERR: Couldn't load default configuration. Have you set up your AWS account?", err) return nil, err } bedrockClient := bedrockruntime.NewFromConfig(*cfg) return &App{ config: cfg, bedrock: bedrockClient, }, nil }
最後に、次のコードを追加して、main() メソッドを更新し、コンストラクターを使用します。
func main() { ctx := context.Background() app, err := NewApp(ctx) if err != nil { panic(err) } log.Println(app.Start()) }
AWS Business Intelligence を使用した埋め込みの生成
リクエスト構造を定義し、埋め込みを計算するメソッドを作成するには、次のコードを server.goファイルに追加します。
const titanEmbedImgV1ModelId string = "amazon.titan-embed-image-v1" const contentTypeJson = "application/json" type EmbeddingConfig struct { OutputEmbeddingLength int `json:"outputEmbeddingLength"` } type BedrockRequest struct { InputImage string `json:"inputImage"` EmbeddingConfig EmbeddingConfig `json:"embeddingConfig"` InputText *string `json:"inputText,omitempty"` } func (app App) computeImageEmbedding(ctx context.Context, image string) ([]float64, error) { payload := BedrockRequest{ InputImage: image, EmbeddingConfig: EmbeddingConfig{ OutputEmbeddingLength: 1024, }, InputText: nil, } bedrockBody, err := json.Marshal(payload) if err != nil { return nil, fmt.Errorf("failed to get embedding from bedrock: %w", err) } bedrockReq := bedrockruntime.InvokeModelInput{ ModelId: aws.String(titanEmbedImgV1ModelId), Body: bedrockBody, ContentType: aws.String(contentTypeJson), } embeddingResp, err := app.bedrock.InvokeModel(ctx, &bedrockReq) if err != nil { return nil, fmt.Errorf("failed to get embedding from bedrock: %w", err) } result := gjson.GetBytes(embeddingResp.Body, "embedding") var embedding []float64 result.ForEach(func(key, value gjson.Result) bool { embedding = append(embedding, value.Float()) return true }) return embedding, nil }
次に、プロジェクトディレクトリから次のコマンドを実行中して、GJSON をインストールします。
go get github.com/tidwall/gjson
埋め込みを計算するようにハンドラーを更新するには、次のコードに示すように imageSearch メソッドを変更します。
func (app App) imageSearch(w http.ResponseWriter, r *http.Request) { // ...existing code... // Compute the embedding using titan-embed-image-v1 embedding, err := app.computeImageEmbedding(r.Context(), *stdImage) if err != nil { log.Println("ERR:", err) http.Error(w, "Error computing embedding", http.StatusInternalServerError) return } }
MongoDB Atlasの構成
プロジェクトディレクトリに、.envファイルを作成します。MongoDB Atlas接続 URI を保存するには、このファイルに次のコードを追加します。プレースホルダーを実際のクラスターと認証情報に置き換えます。接続 URI を取得する方法については、 MongoDB の取得ガイドを参照してください。
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/
次に、プロジェクトディレクトリから次のコマンドを実行中て、必要なモジュールをインストールします。
go get github.com/joho/godotenv go get go.mongodb.org/mongo-driver/v2
MongoDBクライアントフィールドをApp構造体に追加します。
type App struct { client *mongo.Client config *aws.Config bedrock *bedrockruntime.Client }
MongoDBクライアント を初期化するためのメソッドを作成するには、次のコードを server.goファイルに追加します。
func newDBClient(uri string) (*mongo.Client, error) { serverAPI := options.ServerAPI(options.ServerAPIVersion1) opts := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI) client, err := mongo.Connect(opts) if err != nil { return nil, err } return client, nil } func (app *App) Close() { if err := app.client.Disconnect(context.Background()); err != nil { panic(err) } }
コンストラクターの署名と実装を更新するには、次のコードに示すように NewApp() メソッドを変更します。
func NewApp(ctx context.Context, uri string) (*App, error) { cfg, err := connectToAWS(ctx) if err != nil { log.Println("ERR: Couldn't load default configuration. Have you set up your AWS account?", err) return nil, err } bedrockClient := bedrockruntime.NewFromConfig(*cfg) client, err := newDBClient(uri) if err != nil { log.Println("ERR: connecting to MongoDB cluster:", err) return nil, err } return &App{ client: client, config: cfg, bedrock: bedrockClient, }, nil }
最後に、環境変数を読み込むには、次のコードに示すように main() メソッドを更新します。
func main() { var uri string err := godotenv.Load() if err != nil { log.Fatal("Unable to load .env file") } if uri = os.Getenv("MONGODB_URI"); uri == "" { log.Fatal("You must set your 'MONGODB_URI' environment variable. See\n\t https://mongodb.com/ja-jp/docs/drivers/go/current/usage-examples/") } ctx := context.Background() app, err := NewApp(ctx, uri) if err != nil { panic(err) } defer func() { app.Close() }() log.Println(app.Start()) }
MongoDB ベクトル検索を使用した類似イメージのクエリ
MongoDBで類似イメージを検索する方法を作成するには、次のコードを server.goファイルに追加します。
func (app App) findSimilarImages(ctx context.Context, embedding []float64) ([]string, error) { imgCollection := app.client.Database("celebrity_matcher").Collection("celeb_images") vectorSchStage := bson.D{{"$vectorSearch", bson.D{{"index", "vector_index"}, {"path", "embeddings"}, {"queryVector", embedding}, {"numCandidates", 15}, {"limit", 3}}}} projectStage := bson.D{{"$project", bson.D{{"image", 1}}}} pipeline := mongo.Pipeline{vectorSchStage, projectStage} imgCursor, err := imgCollection.Aggregate(ctx, pipeline) if err != nil { return nil, fmt.Errorf("failed to get similar images from the database: %w", err) } similarImgs := []struct { Id bson.ObjectID `bson:"_id,omitempty"` Image string `bson:"image"` }{} if err = imgCursor.All(ctx, &similarImgs); err != nil { return nil, fmt.Errorf("failed to get similar images from the database: %w", err) } var images []string var stdImage *string for _, item := range similarImgs { stdImage, err = standardizeImage(item.Image) if err != nil { return nil, fmt.Errorf("failed to standardize similar images: %w", err) } images = append(images, *stdImage) } return images, nil }
ハンドラーを更新して類似のイメージを検索するには、imageSearch メソッドを次のように変更します。
func (app App) imageSearch(w http.ResponseWriter, r *http.Request) { // ...existing code... // Find similar images using vector search in MongoDB images, err := app.findSimilarImages(r.Context(), embedding) if err != nil { log.Println("ERR:", err) http.Error(w, "Error getting similar images", http.StatusInternalServerError) return } }
類似性の説明を生成する
Cladeリクエスト構造を定義し、説明を生成するためのメソッドを作成するには、次のコードを server.goファイルに追加します。
const claude3SonnetV1ModelId string = "anthropic.claude-3-sonnet-20240229-v1:0" type ClaudeBodyMsgSource struct { Type string `json:"type"` MediaType *string `json:"media_type,omitempty"` Data *string `json:"data,omitempty"` } type ClaudeBodyMsgContent struct { Type string `json:"type"` Source *ClaudeBodyMsgSource `json:"source,omitempty"` Text *string `json:"text,omitempty"` } type ClaudeBodyMsg struct { Role string `json:"role"` Content []ClaudeBodyMsgContent `json:"content"` } type ClaudeBody struct { AnthropicVersion string `json:"anthropic_version"` MaxTokens int `json:"max_tokens"` System string `json:"system"` Messages []ClaudeBodyMsg `json:"messages"` } func (app App) getImageSimilaritiesDescription(ctx context.Context, imgB64 string, similarImgB64 []string) (*string, error) { const mediaTypeImage = "image/jpeg" prompt := "Please let the user know how their first image is similar to the other 3 and which one is the most similar?" payload := ClaudeBody{ AnthropicVersion: "bedrock-2023-05-31", MaxTokens: 1000, System: "Please act as face comparison analyzer.", Messages: []ClaudeBodyMsg{ { Role: "user", Content: []ClaudeBodyMsgContent{ { Type: "image", Source: &ClaudeBodyMsgSource{ Type: "base64", MediaType: aws.String(mediaTypeImage), Data: &imgB64, }, }, { Type: "image", Source: &ClaudeBodyMsgSource{ Type: "base64", MediaType: aws.String(mediaTypeImage), Data: &similarImgB64[0], }, }, { Type: "image", Source: &ClaudeBodyMsgSource{ Type: "base64", MediaType: aws.String(mediaTypeImage), Data: &similarImgB64[1], }, }, { Type: "image", Source: &ClaudeBodyMsgSource{ Type: "base64", MediaType: aws.String(mediaTypeImage), Data: &similarImgB64[2], }, }, { Type: "text", Text: &prompt, }, }, }, }, } bedrockBody, err := json.Marshal(payload) if err != nil { return nil, fmt.Errorf("failed to get embedding from bedrock: %w", err) } bedrockReq := bedrockruntime.InvokeModelInput{ ModelId: aws.String(claude3SonnetV1ModelId), Body: bedrockBody, ContentType: aws.String(contentTypeJson), Accept: aws.String(contentTypeJson), } bedrockResp, err := app.bedrock.InvokeModel(ctx, &bedrockReq) if err != nil { return nil, fmt.Errorf("failed to get embedding from bedrock: %w", err) } description := gjson.GetBytes(bedrockResp.Body, "content.0.text").String() return &description, nil }
ハンドラーを更新して説明を生成するには、次のコードに示すように imageSearch メソッドを変更します。
func (app App) imageSearch(w http.ResponseWriter, r *http.Request) { // ...existing code... description, err := app.getImageSimilaritiesDescription(r.Context(), *stdImage, images) if err != nil { log.Println("ERR: failed to describe similarities with images", err) http.Error(w, "Error describing similarities with images", http.StatusInternalServerError) return } }
結果を返す
応答構造を定義し、ハンドラーを完了するには、次のコードを server.goファイルに追加します。
type CelebMatchResponse struct { Description string `json:"description"` Images []string `json:"images"` } func (app App) imageSearch(w http.ResponseWriter, r *http.Request) { // ...existing code... response := CelebMatchResponse{ Description: *description, Images: images, } jData, err := json.Marshal(response) if err != nil { log.Println("error serializing json", err) http.Error(w, "error serializing json", http.StatusInternalServerError) return } w.Header().Set("Content-Type", contentTypeJson) w.Header().Set("Content-Length", strconv.Itoa(len(jData))) w.WriteHeader(http.StatusOK) w.Write(jData) }
プロジェクトディレクトリから次のコマンドを実行中て、アプリケーションを再度起動します。
go mod tidy go run server.go
ウェブ ブラウザで http://localhost:3001 に移動し、base64 でエンコードされたイメージを使用して /api/search に 書き込みリクエスト を送信してAPIをテストします。
追加リソース
MongoDB ベクトル検索の詳細については、MongoDB ServerマニュアルのMongoDB ベクトル検索のドキュメントを参照してください。
AWSの詳細については、AWS Red のドキュメントを参照してください。