Chuyển đến nội dung chính

Cách tìm kiếm hình ảnh và văn bản bằng MongoDB Vector Search với FastAPI

Xây dựng ứng dụng vector search end-to-end với FastAPI và MongoDB Atlas. Học cách tạo, lưu trữ và truy vấn embedding cho văn bản và hình ảnh trong hướng dẫn thực hành này.
· 10 phút đọc

Khám phá cùng AI

Tìm kiếm dựa trên từ khóa hoạt động tốt cho các tra cứu đơn giản, nhưng nhanh chóng bộc lộ hạn chế khi chúng ta quan tâm đến ý nghĩa thay vì câu chữ chính xác. Nếu một tài liệu đề cập đến “tăng tốc thời gian phản hồi” và tài liệu khác nói về “cải thiện hiệu năng”, công cụ tìm kiếm truyền thống sẽ coi chúng không liên quan, dù thực chất mô tả cùng một ý tưởng. Chúng ta đánh mất mối quan hệ ngữ nghĩa chỉ vì cách diễn đạt khác nhau.

Vector search giải quyết hạn chế này bằng cách biểu diễn văn bản và hình ảnh dưới dạng embedding: các vector số được tạo bởi mô hình học máy, nắm bắt bối cảnh, ý định và độ tương đồng ngữ nghĩa. Khi đã lưu embedding trong MongoDB, chúng ta có thể truy vấn để tìm các mục thực sự liên quan, bất kể những thuật ngữ chính xác được dùng là gì.

Trong bài hướng dẫn này, chúng ta sẽ xây dựng một ví dụ thực tiễn end-to-end cho quy trình này bằng FastAPI và MongoDB Atlas Vector Search. Chúng ta sẽ tạo embedding văn bản bằng SentenceTransformers và embedding hình ảnh bằng mô hình dựa trên CLIP, lưu cả hai vào MongoDB và thực hiện tìm kiếm theo độ tương đồng. Kết thúc, bạn sẽ có một nền tảng hoạt động được để hỗ trợ tìm kiếm bài viết theo ngữ nghĩa, gợi ý sản phẩm, công cụ tìm hình tương tự và nhiều ứng dụng thực tế khác.

Vậy thì, hãy bắt đầu.

Yêu cầu tiên quyết

Trước khi bắt tay xây dựng, chúng ta cần chuẩn bị một vài thứ. Hướng dẫn này giả định bạn quen thuộc cơ bản với Python, FastAPI và MongoDB, nhưng sẽ không dựa vào kiến thức nâng cao—mọi thứ cần dùng đều sẽ được giới thiệu dần.

Những gì cần có:

  • Python 3.10+
  • Một cụm MongoDB Atlas với Vector Search được bật
  • Môi trường FastAPI
  • Postman hoặc cURL
  • Kết nối Internet để tải và nhúng mô hình

Thư viện Python sẽ dùng

Chúng ta sẽ cài trong phần thiết lập:

  • FastAPI: web framework
  • uvicorn: máy chủ phát triển
  • pymongo: driver MongoDB
  • sentence-transformers: mô hình embedding văn bản
  • open_clip: mô hình embedding hình ảnh dựa trên CLIP
  • torch: yêu cầu bởi CLIP và SentenceTransformers
  • Pillow: hỗ trợ xử lý ảnh (PIL)

Những công cụ này cung cấp mọi thứ cần thiết để tạo embedding, lưu vào MongoDB và truy vấn hiệu quả bằng vector search.

Tìm hiểu Vector Search

Trước khi viết mã, sẽ hữu ích nếu hiểu rõ điều gì diễn ra bên dưới quy trình. Vector search không phức tạp khi ta tách nhỏ ra, và nắm được nền tảng sẽ giúp phần còn lại dễ theo dõi hơn nhiều.

Embedding là gì?

Một embedding đơn giản là một danh sách số (một vector) đại diện cho ý nghĩa của một dữ liệu.

Với văn bản, embedding nắm bắt các yếu tố như bối cảnh và ý định. Với hình ảnh, nó nắm bắt các mẫu và đặc trưng thị giác.

Một ví dụ đơn giản hóa về embedding trông như sau:

[0.21, -0.17, 0.89, ...]

Hai thứ tương đồng về ngữ nghĩa—ví dụ, hai câu mô tả cùng một ý, hoặc hai hình có đối tượng tương tự—sẽ tạo ra các vector gần nhau về mặt toán học. Vector search hoạt động bằng cách đo lường mức độ gần này.

MongoDB sử dụng embedding như thế nào

Bản thân MongoDB không tạo embedding. Thay vào đó, nó tập trung vào thế mạnh:

  • Lưu trữ các vector embedding
  • Đánh chỉ mục bằng vector index
  • Truy xuất láng giềng gần nhất một cách hiệu quả

Nói cách khác, mô hình của chúng ta tạo embedding, còn MongoDB đảm trách phần tìm kiếm.

Sự tách bạch rõ ràng này giúp hệ thống linh hoạt và dễ mở rộng.

Các loại mô hình embedding

Chúng ta có thể chọn bất kỳ mô hình embedding nào miễn là đầu ra là vector số. Các mô hình khác nhau chuyên biệt cho từng loại dữ liệu:

Loại dữ liệu

Mô hình ví dụ

Ghi chú

Văn bản

all-MiniLM-L6-v2

Nhẹ và được dùng rộng rãi cho NLP

Hình ảnh

CLIP/OpenCLIP

Chuyển đổi hình ảnh thành vector ngữ nghĩa

Đa phương thức

Multimodal

Xử lý cả văn bản và hình ảnh cùng nhau

Đây là quy trình chúng ta sẽ triển khai bằng FastAPI và MongoDB Atlas.

Khi thiết lập xong, chúng ta có thể tìm kiếm văn bản và hình ảnh theo ý nghĩa—điều mà tìm kiếm từ khóa truyền thống không làm được.

Thiết lập MongoDB Atlas

Trước khi xây dựng ứng dụng FastAPI, chúng ta cần nơi để lưu trữ và truy vấn embedding. MongoDB Atlas giúp việc này đơn giản, và tạo vector index chỉ mất vài phút.

1. Tạo cụm

Nếu bạn chưa có, hãy tạo cụm mới trong MongoDB Atlas. Cụm miễn phí (M0) là đủ cho hướng dẫn này. Truy cập tài liệu MongoDB để biết thêm chi tiết về cách tạo cụm.

2. Tạo cơ sở dữ liệu và collection

Bên trong cụm:

  1. Tạo cơ sở dữ liệu. Chúng ta sẽ gọi là vector_db. Để biết thêm chi tiết, xem tài liệu.
  2. Tạo hai collection bên trong:
    • texts: nơi lưu tài liệu văn bản và embedding của chúng
    • images: nơi lưu tên tệp ảnh và embedding ảnh

Truy cập tài liệu để biết thêm chi tiết về cách tạo collection.

Chúng ta tách riêng collection hình ảnh và văn bản vì chúng dùng các mô hình embedding và số chiều vector khác nhau.

3. Tạo vector index

MongoDB cần vector index để thực hiện tìm kiếm độ tương đồng nhanh. Chúng ta sẽ tạo một index cho embedding văn bản và một cho embedding hình ảnh.

Bạn có thể tạo trong Atlas → Collections → Search Indexes. Hãy bắt đầu thiết lập cho collection văn bản.

Hình ảnh sau giải thích rõ hơn quy trình.

Bảng điều khiển MongoDB Atlas giải thích cách tạo search index

Ở màn tiếp theo, nhấp Vector Search ->JSON Editor -> Next. Hình dưới sẽ giúp bạn hiểu quy trình hơn

Bảng điều khiển MongoDB Atlas giải thích cách tạo search index. Bước một của quy trình.

Bảng điều khiển MongoDB Atlas giải thích cách tạo search index. Bước hai của quy trình.

Vector index cho văn bản

Trong vector search index, chỉnh sửa JSON để khớp với mã của chúng ta.

Chúng ta dùng mô hình all-MiniLM-L6-v2 cho văn bản, tạo ra embedding 384 chiều.

Đây là định nghĩa JSON:

{
  "fields": [
    {
      "type": "vector",
      "path": "embedding",
      "numDimensions": 384,
      "similarity": "cosine"
    }
  ]
}

Đặt tên index này: vector_texts_search và lưu.

Điều này cho phép MongoDB thực hiện tìm kiếm theo cosine similarity trên embedding văn bản. Hình bên dưới minh họa chi tiết hơn.Bảng điều khiển MongoDB Atlas giải thích cách tạo search index. Bước ba của quy trình.

Image contains a JSON text editor for the JSON definition of the text search.

Vector index cho hình ảnh

Với hình ảnh, chúng ta dùng mô hình dựa trên CLIP cho ra vector 512 chiều, vì vậy index hình ảnh cần khớp số chiều đó. Định nghĩa JSON cần giống đoạn mã dưới đây. Ngoài ra, phần còn lại của luồng là như nhau. 

{
  "fields": [
    {
      "type": "vector",
      "path": "embedding",
      "numDimensions": 512,
      "similarity": "cosine"
    }
  ]
}

Đặt tên index này: vector_images_search.

Khi cả hai index hoạt động, MongoDB Atlas đã sẵn sàng cho các truy vấn vector search.

Thiết lập dự án FastAPI

Khi cụm MongoDB Atlas và vector index đã sẵn sàng, chúng ta có thể thiết lập ứng dụng FastAPI để tạo embedding và lưu vào MongoDB. Chúng ta sẽ giữ cấu trúc dự án gọn nhẹ, tập trung vào quy trình thay vì xây dựng dịch vụ sản xuất hoàn chỉnh.

1. Cài đặt các phụ thuộc cần thiết

Chúng ta sẽ cài FastAPI, Uvicorn, driver Python của MongoDB và các thư viện để tạo embedding văn bản và hình ảnh. Chạy:

pip install fastapi uvicorn pymongo python-dotenv sentence-transformers torch pillow open_clip_torch python-multipart

Giải thích nhanh vì sao cần các gói này:

  • fastapi—framework API của chúng ta
  • uvicorn—máy chủ ASGI để chạy FastAPI
  • pymongo—driver MongoDB
  • python-dotenv—tải biến môi trường từ .env
  • sentence-transformers—mô hình embedding văn bản
  • torch—yêu cầu bởi các mô hình embedding
  • pillow—xử lý ảnh cơ bản
  • open_clip_torch—mô hình embedding hình ảnh dựa trên CLIP
  • python-multipart—cần cho tải tệp qua FastAPI

Sau khi cài xong, chúng ta sẵn sàng bố trí dự án.

2. Cấu trúc dự án

Chúng ta sẽ để mọi thứ trong một tệp Python, cấu hình thông qua biến môi trường:

fastapi-mongodb-vector-search/
│── main.py
│── .env
│── venv/

main.py sẽ chứa toàn bộ ứng dụng: các endpoint cho văn bản, hình ảnh và kết nối MongoDB.

MONGODB_URI="your connection string"
DB_NAME="vector_db"
TEXT_COLLECTION="texts"
IMAGE_COLLECTION="images"
  • MONGODB_URI—URI Atlas cho cụm của bạn
  • DB_NAME—cơ sở dữ liệu chúng ta đã tạo
  • TEXT_COLLECTION—nơi lưu văn bản + embedding
  • IMAGE_COLLECTION—nơi lưu embedding hình ảnh

FastAPI sẽ tải các giá trị này khi ứng dụng khởi động, tách biệt cấu hình khỏi mã nguồn.

Triển khai tìm kiếm văn bản

Với dự án FastAPI đã sẵn sàng, chúng ta bắt đầu với quy trình tìm kiếm văn bản. Phần này giới thiệu ý tưởng cốt lõi của vector search: Tạo embedding cho văn bản, lưu vào MongoDB và truy vấn bằng vector index đã tạo trước đó.

Chúng ta sẽ chia thành hai bước nhỏ để dễ kiểm thử và gỡ lỗi.

 a. Thêm văn bản và tạo embedding

Endpoint đầu tiên sẽ nhận tiêu đề và nội dung. Chúng ta sẽ gộp hai trường thành một chuỗi, tạo embedding bằng SentenceTransformers và lưu tài liệu kèm vector embedding.

Mã minh họa:

# main.py (excerpt)

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from pymongo import MongoClient
from sentence_transformers import SentenceTransformer
from dotenv import load_dotenv
import os

load_dotenv()

app = FastAPI()

# --- DB setup ---
client = MongoClient(os.getenv("MONGODB_URI"))
db = client[os.getenv("DB_NAME")]
texts_coll = db[os.getenv("TEXT_COLLECTION")]
Images_coll = db[os.getenv("IMAGE_COLLECTION")]


# --- text embedding model ---
MODEL_NAME = "all-MiniLM-L6-v2"
text_model = SentenceTransformer(MODEL_NAME)

# --- request schema ---
class AddTextRequest(BaseModel):
    title: str
    content: str

@app.post("/add-text")
def add_text(payload: AddTextRequest):
    try:
        # Combine fields for richer embeddings
        text = f"{payload.title}\n\n{payload.content}"

        # Generate text embedding
        embedding = text_model.encode(text).tolist()

        # Store document in MongoDB
        doc = {
            "title": payload.title,
            "content": payload.content,
            "embedding": embedding,
            "model": MODEL_NAME,
        }

        result = texts_coll.insert_one(doc)

        return {
            "inserted_id": str(result.inserted_id),
            "title": payload.title
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

Cách kiểm thử endpoint này

Có thể dùng cURL, Postman hoặc các công cụ kiểm thử API tương tự. Trong hướng dẫn này, chúng ta sẽ kiểm thử bằng Postman.

Chúng ta cũng cần mở endpoint FastAPI ra Internet để kiểm thử bằng Postman trên web. Sẽ dùng Ngrok cho việc này. Nếu bạn mới dùng Ngrok, truy cập tài liệu. Để kiểm thử, trước tiên chạy mã FastAPI bằng lệnh dưới đây:

uvicorn main:app --reload

Sau đó, tạo đường hầm từ localhost ra Internet bằng lệnh Ngrok sau:

Ngrok http 8000

Lệnh này sẽ tạo URL để kiểm thử, như hình dưới:

Ảnh màn hình terminal chạy máy chủ Ngrok. 
Chứa URL localhost và URL web được tạo đường hầm

Gửi yêu cầu POST bằng Postman tới endpoint add-text. Hình dưới minh họa quy trình gửi yêu cầu và phản hồi mong đợi.

Ảnh cuộc gọi API bằng Postman, URL Ngrok và phản hồi từ API.

Phản hồi kỳ vọng:

{
  "inserted_id": "65f...",
  "title": "Why FastAPI is great."
}

Điều này xác nhận mô hình embedding đã tải đúng và MongoDB đã lưu vector. Nếu chạy được, pipeline văn bản của bạn đang hoạt động.

b. Tìm kiếm văn bản bằng vector search

Giờ khi đã thêm được văn bản, bước tiếp theo là tìm kiếm bằng $vectorSearch của MongoDB. Ở đây, chúng ta:

  1. Chuyển truy vấn của người dùng thành embedding.
  2. Yêu cầu MongoDB truy xuất các vector gần nhất.
  3. Trả về các tài liệu tương tự nhất.

Thêm endpoint sau vào main.py:

class SearchTextRequest(BaseModel):
    query: str
    limit: int = 5

@app.post("/search-text")
def search_text(payload: SearchTextRequest):
    try:
        # Convert search query to embedding
        query_vector = text_model.encode(payload.query).tolist()

        # Vector search pipeline
        pipeline = [
            {
                "$vectorSearch": {
                    "queryVector": query_vector,
                    "path": "embedding",
                    "numCandidates": 100,
                    "limit": payload.limit,
                    "index": "vector_texts_search" # field MUST match the index name in Atlas exactly


                }
            },
            {
                "$project": {
                    "title": 1,
                    "content": 1,
                    "score": {"$meta": "vectorSearchScore"},
                    "_id": 0
                }
            }
        ]

        results = list(texts_coll.aggregate(pipeline))
        return {"results": results}

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

> Tham số numCandidates xác định có bao nhiêu ứng viên được đánh giá trước khi trả về kết quả hàng đầu. 

> Giá trị cao hơn cải thiện độ chính xác nhưng tăng thời gian truy vấn. Kinh nghiệm: đặt khoảng 10–20 lần giới hạn trả về.

Cách kiểm thử tìm kiếm văn bản

Sau khi thêm một hoặc nhiều tài liệu, gửi yêu cầu POST tới endpoint /search-text qua Postman dùng liên kết Ngrok đã tạo, như hình dưới:

Ảnh cuộc gọi API bằng Postman, URL Ngrok và phản hồi từ API.

Đầu ra kỳ vọng:

{
  "results": [
    {
      "title": "Why FastAPI is great",
      "content": "FastAPI makes building APIs simple and fast.",
      "score": 0.85
    }
  ]
}

Nếu bạn thấy kết quả có điểm tương đồng, pipeline vector search cho văn bản đã hoạt động end-to-end. Khi phần này ổn định, chúng ta sẵn sàng chuyển sang quy trình hình ảnh.

Triển khai tìm kiếm hình ảnh

Khi quy trình văn bản đã chạy trơn tru, chúng ta mở rộng cùng ý tưởng cho hình ảnh. Luồng tổng thể tương tự—điểm khác biệt lớn là cách tạo embedding. Thay vì SentenceTransformers, ta dùng mô hình dựa trên CLIP, được thiết kế để chuyển ảnh thành vector 512 chiều.

Giống như trước, ta sẽ triển khai từng bước để dễ kiểm thử và gỡ lỗi.

a. Thêm hình ảnh và tạo embedding

Endpoint này nhận tệp ảnh tải lên, chuyển thành tensor mà CLIP hiểu được, tạo embedding 512 chiều và lưu vào MongoDB.

Mã minh họa:

# --- CLIP model setup ---
import torch
import open_clip
from PIL import Image
import io
from fastapi import UploadFile, File

# Load CLIP model once when the app starts
clip_model, _, preprocess = open_clip.create_model_and_transforms(
    "ViT-B-32",
    pretrained="openai"
)
device = "cuda" if torch.cuda.is_available() else "cpu"
clip_model.to(device)

@app.post("/add-image")
async def add_image(file: UploadFile = File(...)):
    """
    Upload an image, generate its CLIP embedding, and store in MongoDB.
    """
    try:
        # Read file contents
        image_bytes = await file.read()
        image = Image.open(io.BytesIO(image_bytes)).convert("RGB")

        # Preprocess for CLIP and encode
        image_tensor = preprocess(image).unsqueeze(0).to(device)
        with torch.no_grad():
            image_embedding = clip_model.encode_image(image_tensor)

        # Normalize the embedding (important for cosine similarity)
        image_embedding /= image_embedding.norm(dim=-1, keepdim=True)
        vector = image_embedding.cpu().numpy().flatten().tolist()

        # Store in MongoDB
        doc = {
            "filename": file.filename,
            "embedding": vector
        }
        result = images_coll.insert_one(doc)

        return {
            "inserted_id": str(result.inserted_id),
            "filename": file.filename
        }

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

Vì sao chuẩn hóa quan trọng

Embedding của CLIP hoạt động tốt nhất với cosine similarity. Để so sánh chính xác và ổn định, chúng ta chuẩn hóa vector:

image_embedding /= image_embedding.norm(dim=-1, keepdim=True)

Việc này đảm bảo khoảng cách cosine phản ánh đúng độ tương đồng.

Kiểm thử endpoint tải ảnh

Để kiểm thử trong Postman, gửi yêu cầu POST tới endpoint /add-image. Gửi form-data trong phần thân yêu cầu kèm bất kỳ ảnh .jpg hoặc .png. Hình dưới minh họa thêm.

Ảnh cuộc gọi API bằng Postman, URL Ngrok và phản hồi từ API.

Nếu mọi thứ hoạt động, bạn sẽ nhận được:

{
  "inserted_id": "692454f6156fbb762ed865a0",
  "filename": "file-name.png"
}

Lúc này embedding hình ảnh của bạn đã được lưu thành công vào MongoDB.

b. Tìm kiếm hình ảnh bằng vector search

Giờ chúng ta xây dựng endpoint nhận ảnh truy vấn được tải lên, chuyển thành embedding và tìm các ảnh lưu trữ gần nhất bằng vector index cho ảnh.

Thêm vào main.py:

@app.post("/search-image")
async def search_image(file: UploadFile = File(...)):
    """
    Upload an image and find the most visually similar images in MongoDB.
    """
    try:
        # Read the uploaded file
        image_bytes = await file.read()
        image = Image.open(io.BytesIO(image_bytes)).convert("RGB")

        # Generate embedding for the query image
        image_tensor = preprocess(image).unsqueeze(0).to(device)
        with torch.no_grad():
            query_embedding = clip_model.encode_image(image_tensor)

        query_embedding /= query_embedding.norm(dim=-1, keepdim=True)
        query_vector = query_embedding.cpu().numpy().flatten().tolist()

        # Vector search pipeline
        pipeline = [
            {
                "$vectorSearch": {
                    "queryVector": query_vector,
                    "path": "embedding",
                    "numCandidates": 100,
                    "limit": 5,
                    "index": "vector_images_search"
                }
            },
            {
                "$project": {
                    "filename": 1,
                    "score": {"$meta": "vectorSearchScore"},
                    "_id": 0
                }
            }
        ]

        results = list(images_coll.aggregate(pipeline))
        return {"results": results}

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

Kiểm thử endpoint tìm kiếm ảnh

Để kiểm thử trong Postman, gửi yêu cầu POST tới endpoint /search-image. Gửi form-data trong phần thân với ảnh tương tự những gì bạn đã lưu trước đó. Hình dưới minh họa thêm.

Ảnh cuộc gọi API bằng Postman, URL Ngrok tới tuyến /search-image và phản hồi từ API.

Đầu ra kỳ vọng như sau:

{
  "results": [
    {
      "filename": "image1.png",
      "score": 1.0
    },
    {
      "filename": "image2.png",
      "score": 0.81
    }
  ]
}

Điểm 1.0 nghĩa là trùng khớp chính xác cùng ảnh. Bất kỳ giá trị gần (0,7–0,9) cho thấy độ tương đồng thị giác cao.

Nếu bạn thấy một mảng kết quả, tìm kiếm vector cho hình ảnh của bạn đã hoạt động.

Tại thời điểm này, nếu mọi thứ chạy tốt, chúng ta đã có hệ thống tìm kiếm ngữ nghĩa hoàn chỉnh cho cả văn bản lẫn hình ảnh.

Kết luận

Trong hướng dẫn này, chúng ta đã xây dựng quy trình tìm kiếm ngữ nghĩa hoàn chỉnh bằng FastAPI và MongoDB Atlas. Chúng ta tạo embedding văn bản với SentenceTransformers, tạo embedding hình ảnh bằng mô hình CLIP, lưu mọi thứ vào MongoDB và truy vấn bằng Vector Search của MongoDB và Postman. Mẫu này là nền tảng cho các ứng dụng như tìm kiếm tài liệu theo ngữ nghĩa, công cụ tìm ảnh tương tự, hệ thống gợi ý và tính năng AI đa phương thức.

Điều làm cho kiến trúc này mạnh mẽ là tính linh hoạt: MongoDB không khóa bạn vào các mô hình cụ thể. Miễn là embedding là vector số và số chiều khớp cấu hình index, bạn có thể cắm bất kỳ mô hình embedding văn bản, hình ảnh hoặc đa phương thức nào phù hợp với bài toán.

Với nền tảng này, bạn có thể mở rộng dự án—lưu metadata, xây dựng logic xếp hạng, hoặc tích hợp với ứng dụng hướng người dùng. Quy trình tương tự cũng mở rộng dễ dàng nhờ dịch vụ quản lý toàn phần của MongoDB Atlas.

Bạn có thể tìm dự án hoàn chỉnh hoạt động trên GitHub.

Moses Anumadu

Tác giả

Moses Anumadu

Kỹ sư Phần mềm

Chủ đề

Liên quan