Overview
Este tutorial muestra cómo crear un backend basado en IA que identifica rostros mediante Go, MongoDB Vector Search y modelos de IA de AWS Bedrock. La aplicación procesa una imagen cargada, genera una incrustación vectorial, busca rostros similares en MongoDB Atlas y devuelve las tres coincidencias más cercanas con explicaciones.
El backend realiza las siguientes operaciones:
Estandariza las imágenes al formato JPEG 800x600
Genera incrustaciones de 1024dimensiones mediante AWS Bedrock
Consulta MongoDB Atlas mediante MongoDB Vector Search
Genera explicaciones de similitud mediante el uso de Claude
Cómo funcionan las incrustaciones vectoriales
Una incrustación es un vector de números de punto flotante que codifica las características de una imagen. Imágenes similares producen vectores similares, lo que permite encontrar coincidencias comparando la proximidad de vectores en un espacio n-dimensional. Las incrustaciones de 1024-dimensionales contienen valores en el rango (-1.0, 1.0).
MongoDB Atlas almacena incrustaciones precalculadas para imágenes de referencia. Cuando un usuario sube una imagen, el backend genera su incrustación y utiliza MongoDB Vector Search para encontrar las coincidencias más cercanas.
Flujo de aplicación
La aplicación realiza los siguientes pasos para procesar las solicitudes:
El frontend captura una imagen y la envía al backend como una solicitud JSON codificada en base64.
El backend estandariza la imagen al formato JPEG 800x600.
AWS Bedrock
amazon.titan-embed-image-v1El modelo genera una incrustación a partir de la imagen.MongoDB Vector Search encuentra las tres incrustaciones más coincidentes.
El modelo
anthropic.claude-3-sonnet-20240229-v1:0de AWS Bedrock genera una explicación en lenguaje natural de las similitudes.El backend devuelve las imágenes coincidentes y la explicación al frontend.
Prueba la aplicación
Puedes probar la aplicación en mongodb-celeb-search.com.
Tutorial
Este tutorial le muestra cómo realizar las siguientes acciones:
Crear un servidor HTTP para gestionar solicitudes de procesamiento de imágenes
Estandarizar las imágenes cargadas a un formato consistente
Generar incrustaciones vectoriales mediante AWS Bedrock
Consulta MongoDB Atlas para encontrar imágenes similares
Generar explicaciones en lenguaje natural sobre similitudes de imágenes.
Verificar los prerrequisitos
Antes de comenzar este tutorial, asegúrese de tener los siguientes componentes:
Una cuenta de MongoDB Atlas con un clúster configurado. Para aprender a configurar un clúster de Atlas, consulte la guía de introducción a MongoDB.
Una cuenta de AWS activa con acceso a Bedrock.
Credenciales de AWS configuradas
~/.aws/configen~/.aws/credentialsy. Para obtener más información sobre cómo configurar las credenciales de AWS, consulte la documentación del archivo de credenciales de AWS.Go instalado.
Inicializar el módulo y crear el archivo principal
Crea un nuevo directorio para tu proyecto y navega hasta él.
Para inicializar el módulo, ejecute el siguiente comando desde el directorio de su proyecto:
go mod init github.com/jdortiz/goai
Luego, cree un archivo server.go en el mismo directorio con la estructura básica de la aplicación:
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()) }
Agregar el punto final de búsqueda de imágenes
Para agregar un método de controlador al tipo App y registrarlo con el enrutador, agregue un método imageSearch() a su archivo server.go y regístrelo en el 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 la aplicación ejecutando el siguiente comando desde el directorio de su proyecto:
go mod tidy go run server.go
Para probar el punto final, ejecute el siguiente comando desde la línea de comandos:
curl -IX POST localhost:3001/api/search
Configurar el procesamiento de imágenes
Para definir la estructura de la solicitud y agregar lógica de estandarización de imágenes, agregue el siguiente código a su archivo 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 actualizar el controlador para decodificar y estandarizar la imagen, modifique el método imageSearch() como se muestra en el siguiente código:
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 el módulo de edición de imágenes ejecutando el siguiente comando desde el directorio de su proyecto:
go get golang.org/x/image/draw
Configurar AWS Bedrock
Para instalar el AWS SDK y añadir la información de configuración al struct App, ejecute los siguientes comandos desde el directorio de su proyecto:
go get github.com/aws/aws-sdk-go-v2/config go get github.com/aws/aws-sdk-go-v2/service/bedrockruntime
Luego, agregue los siguientes campos de configuración de AWS a la estructura App en su archivo server.go:
type App struct { config *aws.Config bedrock *bedrockruntime.Client }
Para inicializar la configuración de AWS, agregue los siguientes métodos a su archivo 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 }
Por último, actualice el método main() para usar el constructor agregando el siguiente código:
func main() { ctx := context.Background() app, err := NewApp(ctx) if err != nil { panic(err) } log.Println(app.Start()) }
Generar incrustaciones con AWS Bedrock
Para definir las estructuras de solicitud y crear un método para calcular incrustaciones, agregue el siguiente código a su archivo 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 }
A continuación, instale GJSON ejecutando el siguiente comando desde el directorio de su proyecto:
go get github.com/tidwall/gjson
Para actualizar el controlador para calcular la incrustación, modifique el método imageSearch como se muestra en el siguiente código:
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
En tu directorio de proyecto, crea un archivo .env. Agrega el siguiente código a este archivo para almacenar el URI de conexión de MongoDB Atlas. Reemplaza los marcadores de posición con tu información real de clúster y credenciales. Para aprender a recuperar tu URI de conexión, consulta la guía de MongoDB Comienza aquí.
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/
A continuación, instale los módulos necesarios ejecutando los siguientes comandos desde el directorio de su proyecto:
go get github.com/joho/godotenv go get go.mongodb.org/mongo-driver/v2
Agregue un campo de cliente MongoDB a la estructura App:
type App struct { client *mongo.Client config *aws.Config bedrock *bedrockruntime.Client }
Para crear métodos para inicializar el cliente MongoDB, agregue el siguiente código a su archivo 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 actualizar la firma y la implementación del constructor, modifique el método NewApp() como se muestra en el siguiente código:
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 }
Por último, para cargar variables de entorno, actualice el método main() como se muestra a continuación:
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/es/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()) }
Consulta imágenes similares mediante MongoDB Vector Search
Para crear un método para encontrar imágenes similares en MongoDB, añade el siguiente código a tu archivo 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 actualizar el controlador para encontrar imágenes similares, modifique el método imageSearch de la siguiente manera:
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 } }
Generar explicaciones de similitud con Claude
Para definir las estructuras de solicitud de Claude y crear un método para generar explicaciones, agregue el siguiente código a su archivo 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 actualizar el controlador para generar la descripción, modifique el método imageSearch como se muestra en el siguiente 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 } }
Devolver los resultados
Para definir la estructura de respuesta y completar el controlador, agregue el siguiente código a su archivo 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 la aplicación una vez más ejecutando el siguiente comando desde el directorio de su proyecto:
go mod tidy go run server.go
Navegue a http://localhost:3001 en su navegador web y pruebe la API enviando una solicitud POST a /api/search con una imagen codificada base64.
Recursos adicionales
Para obtener más información sobre MongoDB Vector Search, consulte la documentación de MongoDB Vector Search en el manual de MongoDB Server.
Para obtener más información sobre AWS Bedrock, consulte la documentación de AWS Bedrock.