Overview
本教程向您展示如何使用Go、 MongoDB Vector Search 和 AWS Bedrock AI模型构建由AI驱动的后端来匹配人脸。该应用程序程序处理上传的图像,生成向量嵌入,在MongoDB Atlas中搜索相似的人脸,并返回三个最接近的匹配项并附上解释。
后端执行以下操作:
将图像标准化为 800x600 JPEG 格式
使用 AWS 基岩版生成 1024 维嵌入
使用MongoDB Vector Search 查询MongoDB Atlas
使用 Claude 生成相似性解释
向量嵌入的工作原理
嵌入是对图像特征进行编码的点向量。相似的图像会生成相似的向量,使您能够通过比较 n 维空间中的向量邻近度来查找匹配项。 1024 维嵌入包含 (-1.0, 1.0)范围内的值。
MongoDB Atlas存储参考图像的预先计算的嵌入。当用户上传图像时,后端会生成其嵌入,并使用MongoDB Vector Search 来查找最接近的匹配项。
应用程序流程
应用程序执行以下步骤来进程请求:
前端捕获图像并将其作为基本64编码的JSON请求发送到后端。
后端将图像标准化为 800x600 JPEG 格式。
AWS Bedrock 的
amazon.titan-embed-image-v1模型从图像生成嵌入。MongoDB Vector Search 可找到三个最接近匹配的嵌入。
AWS Bedrock 的
anthropic.claude-3-sonnet-20240229-v1:0模型会生成对相似性的自然语言解释。后端将匹配的图像和说明返回给前端。
试用应用程序
Tutorial
本教程向您展示如何执行以下操作:
创建HTTP服务器来处理图像处理请求
将上传的图像标准化为一致的格式
使用 AWS 基岩生成向量嵌入
查询MongoDB Atlas以查找相似图像
生成图像相似度的自然语言解释
验证先决条件
在开始本教程之前,请确保您拥有以下组件:
在
~/.aws/config和 中配置的 AWS凭证。要学习;了解有关设置~/.aws/credentialsAWS凭证的更多信息,请参阅 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 类型并将其注册到路由器,请将 imageSearch() 方法添加到 server.go文件,然后在 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 基岩
要安装 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 基岩生成嵌入
要定义请求结构并创建计算嵌入的方法,请将以下代码添加到您的 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/zh-cn/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 Vector Search 查询相似图像
要创建在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 } }
使用 Claude 生成相似性解释
要定义 Claude请求结构并创建生成解释的方法,请将以下代码添加到您的 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,并使用基本64编码的图像向 /api/search 发送 POST请求来测试API 。
其他资源
要学习;了解有关MongoDB Vector Search 的更多信息,请参阅MongoDB Server手册中的MongoDB Vector Search 文档。
要学习;了解有关 AWS 基岩的更多信息,请参阅 基岩 文档。