Visão geral
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
Como funcionam as incorporações vetoriais
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.
Fluxo do Aplicativo
O aplicação executa as seguintes etapas para processar solicitações:
O frontend captura uma imagem e a envia para o backend como uma solicitação JSON codificada de base64.
O backend padroniza a imagem para o formato 800x600.
O modelo
amazon.titan-embed-image-v1do AWS Layer gera uma incorporação a partir da imagem.A Vector Search do MongoDB encontra as três incorporações correspondentes mais próximas.
O modelo
anthropic.claude-3-sonnet-20240229-v1:0do AWSBedrock gera uma explicação de linguagem natural das similaridades.O backend retorna as imagens correspondentes e a explicação para o frontend.
Experimente o aplicativo
Você pode testar o aplicação em mongodb-celeb-search.com.
Tutorial
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
Verifique os pré-requisitos
Antes de iniciar este tutorial, verifique se você tem os seguintes componentes:
Uma conta do MongoDB Atlas com um cluster configurado. Para saber como configurar um cluster do Atlas , consulte o guia de Introdução ao MongoDB .
Uma conta ativa da AWS com acesso Cama do Rock.
Credenciais AWS configuradas em
~/.aws/config~/.aws/credentialse. Para saber mais sobre como configurar credenciais da AWS, consulte a documentação do arquivo de credenciais da AWS.Go instalado.
Inicialize o módulo e crie o arquivo principal
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()) }
Adicione o endpoint de pesquisa de imagem
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
Configurar processamento de imagem
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
Configurar o AWS BaseRock
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()) }
Gerar embeddings com AWS BaseRock
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 } }
Configurar MongoDB Atlas
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()) }
Consulte imagens semelhantes usando o MongoDB Vector Search
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 } }
Gere explicações de similaridade com laudo
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 } }
Retornar os resultados
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.
Recursos adicionais
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 .