MongoDB.local SF, Jan 15: See the speaker lineup & ship your AI vision faster. Use WEB50 to save 50%
Find out more >
Docs Menu
Docs Home
/ /

チュートリアル: ベクトル検索とAWS Bedrock の統合

このチュートリアルでは、 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 ベクトル検索 を使用して最も近い一致を検索します。

アプリケーションは、リクエストを処理するために次の手順を実行します。

  1. フロントエンドはイメージをキャプチャし、それを base64 でエンコードされたJSONリクエストとしてバックエンドに送信します。

  2. バックエンドはイメージを 800x600 GB形式に標準化します。

  3. AWSのamazon.titan-embed-image-v1モデルは、イメージから埋め込みを生成します。

  4. MongoDB ベクトル検索 は、最も近い埋め込みを 3 つ検索します。

  5. AWS Reduce の anthropic.claude-3-sonnet-20240229-v1:0 モデルは、類似性を自然言語で説明するを生成します。

  6. バックエンドは一致した画像と説明をフロントエンドに返します。

このアプリケーションはmongodb-celeb-search.comでテストできます。

このチュートリアルでは、次のアクションを実行する方法について説明します。

  • 画像処理リクエストを処理するためのHTTPサーバーを作成

  • アップロードされた画像をコンシステントな形式に標準化

  • AWS Reduce を使用してベクトル埋め込みを生成する

  • MongoDB Atlas のクエリで類似イメージの検索

  • イメージ類似性の自然言語説明の生成

1

このチュートリアルを開始する前に、次のコンポーネントがあることを確認してください。

2

プロジェクトに新しいディレクトリを作成し、そのディレクトリに移動します。

モジュールを初期化するには、プロジェクトディレクトリから次のコマンドを実行します。

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())
}
3

ハンドラー メソッドを 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
4

リクエスト構造を定義し、イメージ標準化ロジックを追加するには、次のコードを 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
5

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())
}
6

リクエスト構造を定義し、埋め込みを計算するメソッドを作成するには、次のコードを 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
}
}
7

プロジェクトディレクトリに、.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())
}
8

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
}
}
9

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
}
}
10

応答構造を定義し、ハンドラーを完了するには、次のコードを 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 のドキュメントを参照してください。

戻る

TLS セキュリティ プロトコル