Menu Docs
Página inicial do Docs
/ /

Tutorial: Integração do Vector Search com o AWS FeedRock

Este tutorial mostra como criar um back-end com base em IA que corresponda a interfaces usando os modelos de IA do Go, MongoDB Vector Search e AWSBedRock. O aplicação processa uma imagem carregada, gera uma incorporação vetorial, pesquisa face semelhante no MongoDB Atlas e retorna as três correspondências mais próximas com explicações.

O backend executa as seguintes operações:

  • Padroniza imagens para o formato 800x600 IPv

  • Gera incorporações dimensionais 1024usando o AWS Camas do Rock

  • Faz queries no MongoDB Atlas usando o MongoDB Vector Search

  • Gera explicações de similaridade usando Class

Uma incorporação é um vetor de números de ponto flutuante que codifica características de imagem. Imagens semelhantes produzem vetores semelhantes, permitindo que você encontre correspondências comparando a proximidade vetorial no espaço n-dimensional. As incorporações dimensionais 1024contêm valores no intervalo (-1.0, 1.0).

O MongoDB Atlas armazena incorporações pré-computadas para imagens de referência. Quando um usuário carrega uma imagem, o backend gera sua incorporação e usa o MongoDB Vector Search para encontrar as correspondências mais próximas.

O aplicação executa as seguintes etapas para processar solicitações:

  1. O frontend captura uma imagem e a envia para o backend como uma solicitação JSON codificada de base64.

  2. O backend padroniza a imagem para o formato 800x600.

  3. O modelo amazon.titan-embed-image-v1 do AWS Layer gera uma incorporação a partir da imagem.

  4. A Vector Search do MongoDB encontra as três incorporações correspondentes mais próximas.

  5. O modelo anthropic.claude-3-sonnet-20240229-v1:0 do AWSBedrock gera uma explicação de linguagem natural das similaridades.

  6. O backend retorna as imagens correspondentes e a explicação para o frontend.

Você pode testar o aplicação em mongodb-celeb-search.com.

Este tutorial mostra como executar as seguintes ações:

  • Crie um servidor HTTP para lidar com solicitações de processamento de imagem

  • Padronize as imagens carregadas em um formato consistente

  • Gere incorporações vetoriais usando o AWS CloudRock

  • Consulte o MongoDB Atlas para encontrar imagens semelhantes

  • Gere explicações em linguagem natural sobre similaridades de imagens

1

Antes de iniciar este tutorial, verifique se você tem os seguintes componentes:

2

Crie um novo diretório para o seu projeto e navegue até ele.

Para inicializar o módulo, execute o seguinte comando a partir do diretório do projeto :

go mod init github.com/jdortiz/goai

Em seguida, crie um arquivo server.go no mesmo diretório com a estrutura básica do aplicação :

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

Para adicionar um método manipulador ao tipo App e registrá-lo com o roteador, adicione um método imageSearch() ao arquivo server.go e registre-o no método 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)
}

Inicie o aplicação executando o seguinte comando a partir do diretório do projeto :

go mod tidy
go run server.go

Para testar o endpoint, execute o seguinte comando na linha de comando:

curl -IX POST localhost:3001/api/search
4

Para definir a estrutura da solicitação e adicionar a lógica de padronização da imagem, adicione o seguinte código ao seu arquivo 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
}

Para atualizar o manipulador para decodificar e padronizar a imagem, modifique o método imageSearch() conforme mostrado no código a seguir:

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

Instale o módulo de edição de imagem executando o seguinte comando a partir do diretório do projeto :

go get golang.org/x/image/draw
5

Para instalar o AWS SDK e adicionar informações de configuração à estrutura App, execute os seguintes comandos a partir do diretório do seu projeto :

go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/bedrockruntime

Em seguida, adicione os seguintes campos de configuração da AWS à estrutura App em seu arquivo server.go:

type App struct {
config *aws.Config
bedrock *bedrockruntime.Client
}

Para inicializar a configuração AWS, adicione os seguintes métodos ao seu arquivo 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
}

Finalmente, atualize o método main() para utilizar o construtor adicionando o seguinte código:

func main() {
ctx := context.Background()
app, err := NewApp(ctx)
if err != nil {
panic(err)
}
log.Println(app.Start())
}
6

Para definir as estruturas de solicitação e criar um método para calcular incorporações, adicione o seguinte código ao seu arquivo 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
}

Em seguida, instale o GJSON executando o seguinte comando a partir do seu diretório de projeto :

go get github.com/tidwall/gjson

Para atualizar o manipulador para calcular a incorporação, modifique o método imageSearch conforme mostrado no código a seguir:

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

No diretório do projeto , crie um .env arquivo. Adicione o seguinte código a este arquivo para armazenar seu URI de conexão do MongoDB Atlas . Substitua os espaços reservados por seu cluster real e informações de credenciais. Para saber como recuperar seu URI de conexão, consulte o guia de Introdução ao MongoDB .

MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/

Em seguida, instale os módulos necessários executando os seguintes comandos no diretório do projeto :

go get github.com/joho/godotenv
go get go.mongodb.org/mongo-driver/v2

Adicione um campo de cliente MongoDB à estrutura App:

type App struct {
client *mongo.Client
config *aws.Config
bedrock *bedrockruntime.Client
}

Para criar métodos para inicializar o cliente MongoDB , adicione o seguinte código ao seu arquivo 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)
}
}

Para atualizar a assinatura e a implementação do construtor, modifique o método NewApp() conforme mostrado no código a seguir:

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
}

Finalmente, para carregar variáveis de ambiente, atualize o método main() conforme mostrado no código a seguir:

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/pt-br/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

Para criar um método para encontrar imagens semelhantes no MongoDB, adicione o seguinte código ao seu arquivo 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
}

Para atualizar o manipulador para localizar imagens semelhantes, modifique o método imageSearch da seguinte forma:

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

Para definir as estruturas da solicitação do Class e criar um método para gerar explicações, adicione o seguinte código ao seu arquivo 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
}

Para atualizar o manipulador para gerar a descrição, modifique o método imageSearch como mostrado no seguinte código:

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

Para definir a estrutura de resposta e completar o manipulador, adicione o seguinte código ao seu arquivo 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)
}

Inicie o aplicação mais uma vez executando o seguinte comando a partir do diretório do projeto :

go mod tidy
go run server.go

Navegue até http://localhost:3001 em seu navegador da web e teste a API enviando uma solicitação POST para /api/search com uma imagem codificada de base64.

Para saber mais sobre a Vector Search do MongoDB , consulte a documentação da Vector Search do MongoDB no manual do MongoDB Server .

Para saber mais sobre o AWSBedRock, consulte a documentação do AWSBedRock .

Voltar

Protocolo de segurança TLS