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

Hybrid Search: Combining Vector and Keyword Queries in MongoDB

Learn about hybrid search and how to utilize it in MongoDB.
· 6 phút đọc

Khám phá cùng AI

Đôi khi, chỉ dùng tìm kiếm toàn văn bản đơn giản hoặc chỉ tìm kiếm vector là chưa đủ để truy vấn cơ sở dữ liệu và nhận đúng kết quả bạn mong muốn. Sự kết hợp cả hai rất tuyệt vời khi nhà phát triển làm việc với lượng dữ liệu đa phương thức, phi cấu trúc lớn vốn hưởng lợi từ cả hai loại tìm kiếm. Đây được gọi là tìm kiếm lai, và nó mang đến cho nhà phát triển một lời giải hiệu quả cho một thách thức khó.

Tìm kiếm lai chính xác là gì?

Để hiểu đúng về tìm kiếm lai, trước hết cần hiểu tìm kiếm toàn văn bản là gì.

Tìm kiếm toàn văn bản là cách tìm kiếm khớp các thuật ngữ nguyên văn trong truy vấn của bạn với tài liệu. Đây là kiểu tìm kiếm truyền thống mà nhiều nhà phát triển đã rất quen thuộc.

Ví dụ, nếu bạn tìm “quán cà phê dễ thương có chỗ ngồi ngoài trời”, công cụ tìm kiếm sẽ tìm đúng những từ đó trong cơ sở dữ liệu. Nói đơn giản, tìm kiếm toàn văn bản cực kỳ chính xác và hiệu quả, nhưng hoạt động không tốt nếu bạn muốn có kết quả tương tự khi tìm từ đồng nghĩa, diễn đạt lại, hoặc thậm chí khi truy vấn có lỗi chính tả.

Ngược lại, tìm kiếm vector chuyển toàn bộ dữ liệu thành các con số, hay embedding. Thay vì khớp từ ngữ chính xác, tìm kiếm vector so sánh ý nghĩa ngữ nghĩa của truy vấn với các tài liệu được lưu trong cơ sở dữ liệu.

Tìm “quán cà phê dễ thương có chỗ ngồi ngoài trời” có thể trả về “bánh ngọt và cà phê ngoài trời” ngay cả khi không dùng đúng các từ. Tìm kiếm vector không chỉ mang tính ngữ nghĩa; nó còn rất linh hoạt, nhưng đôi khi có thể trả về kết quả quá rộng so với truy vấn đã định.

Vậy tìm kiếm lai phát huy tác dụng ở đâu? Nó kết hợp cả tìm kiếm toàn văn bản và tìm kiếm vector. Điều này có nghĩa các nhà phát triển có thể tận dụng trí tuệ ngữ nghĩa của vector đồng thời giữ lại khả năng lọc cực kỳ chính xác của tìm kiếm toàn văn bản. Thực sự là “điểm cộng từ cả hai thế giới”. Điều này cực hữu ích khi làm việc với các tập dữ liệu phi cấu trúc lớn.

Vì sao tìm kiếm lai quan trọng

Tìm kiếm lai hữu ích trong nhiều ứng dụng thực tế như thương mại điện tử, y tế, và cả tuyển dụng.

Với thương mại điện tử, hãy tưởng tượng bạn truy cập trang yêu thích và tìm “ghế bàn làm việc êm dưới $100”. Với tìm kiếm vector, bạn sẽ thấy các mặt hàng tương đồng về ngữ nghĩa (như ghế văn phòng công thái học), trong khi tìm kiếm toàn văn bản tập trung vào từ “ghế” và giúp áp giá trần.

Với tuyển dụng, nếu một nhà tuyển dụng tìm trong hồ sơ “kỹ sư có kinh nghiệm NLP”, tìm kiếm lai có thể bắt được cả “xử lý ngôn ngữ tự nhiên” lẫn từ khóa chính xác “kỹ sư”.

Điều này có nghĩa bạn sẽ nhận được kết quả phù hợp và đáng tin cậy hơn so với khi chỉ dùng một cách tiếp cận riêng lẻ.

Tìm kiếm lai trong MongoDB

Hãy cùng xem cách thực hiện tìm kiếm lai trong MongoDB Atlas. Để theo dõi thành công hướng dẫn này, bạn cần một vài điều kiện tiên quyết:

  1. Một tài khoản MongoDB Atlas
  2. Một cụm MongoDB Atlas gói miễn phí
  3. mongosh đã tải xuống trong terminal của bạn

Trong hướng dẫn này, chúng ta sẽ tập trung vào bộ sưu tập phim embedded trong cơ sở dữ liệu sample_mflix, và sẽ làm theo hướng dẫn tìm kiếm lai $rankFusion, với một vài điều chỉnh nhỏ.

Mặc dù toán tử $rankFusion mới có sẵn trên các cụm phiên bản 8.1+, giúp tìm kiếm lai trong MongoDB dễ dàng hơn rất nhiều, nhưng hiện vẫn ở giai đoạn Public Preview và các cụm gói miễn phí tự động ở phiên bản 8.0. Vì vậy chúng ta sẽ làm theo hướng dẫn, thay đổi vài chỗ, và hoàn tất tốt đẹp.

Cấp phát cụm của bạn

Khi cấp phát cụm, hãy đảm bảo bạn tải bộ dữ liệu sample_mflix. Đây là bộ dữ liệu chúng ta sẽ sử dụng xuyên suốt hướng dẫn. Chúng ta tập trung vào bộ sưu tập sample_mflix.embedded_movies.

Kiểm tra nhanh để đảm bảo bạn đã tải mongosh.

mongosh --version

Trong hướng dẫn này, tôi dùng bản 2.4.2.

Giờ hãy kết nối tới cụm MongoDB Atlas của bạn:

mongosh "mongodb+srv://<yourclusterhere>.mongodb.net/" --apiVersion 1 --username <yourusername> 

Bạn sẽ được nhắc nhập mật khẩu, và khi kết nối xong, màn hình sẽ như sau:

Giờ chúng ta có thể kết nối tới cơ sở dữ liệu. Chạy lệnh:

use sample_mflix

Kết quả sẽ là:

switched to db sample_mflix

Tạo chỉ mục

Chúng ta sẽ tạo hai chỉ mục cần thiết cho hướng dẫn này: chỉ mục vector và chỉ mục tìm kiếm toàn văn bản. Lưu ý rằng cả hai chỉ mục này được tạo trên bộ sưu tập embedded_movies.

Chỉ mục vector:

db.embedded_movies.createSearchIndex(
  "hybrid-vector-search",
  "vectorSearch",
  {
    fields: [
      { type: "vector", path: "plot_embedding_voyage_3_large", numDimensions: 2048, similarity: "dotProduct" }
    ]
  }
)

Chỉ mục tìm kiếm toàn văn bản:

db.embedded_movies.createSearchIndex(
  "hybrid-full-text-search",
  "search",
  { mappings: { dynamic: true } }
)

Kiểm tra lại trong cụm MongoDB Atlas để đảm bảo các chỉ mục đã sẵn sàng:

Truy vấn cơ sở dữ liệu

Giờ chúng ta muốn truy vấn dữ liệu trong bộ sưu tập sample_mflix.embedded_movies cho “star wars” trong trường plot_embedding_voyage_3_large.

Theo hướng dẫn, vì không phải dùng API nào, ta có thể lưu tất cả embedding cần thiết vào một tệp riêng tên query_embeddings.js.

Giờ ta có thể nạp embedding để dùng trong truy vấn. Chạy lệnh sau:

load('/Users/<PATH NAME>/query_embeddings.js')
const queryVec = STAR_WARS_EMBEDDING

Để đảm bảo embedding đã nạp thành công, chạy lệnh:

STAR_WARS_EMBEDDING.length

Kết quả 2048 là đúng.

Pipeline tổng hợp

Giờ chúng ta có thể tạo pipeline tổng hợp cho tìm kiếm lai. Lưu ý có một vài hạn chế và lý do vì sao ta phải tiếp cận pipeline theo cách này.

Như đã nói ở trên, dù $rankFusion có trong MongoDB Atlas, hiện vẫn ở giai đoạn Public Preview, và bạn cần cụm 8.0 hoặc cao hơn. Với cụm gói miễn phí, bạn chưa thể dùng $rankFusion lúc này. Điều đó gây chút phức tạp vì MongoDB Atlas sẽ không tự hợp nhất xếp hạng văn bản và vector cho ta. Nghĩa là ta phải tự hợp nhất, hay trộn có trọng số hai danh sách đã xếp hạng.

Vị trí các stage trong pipeline tổng hợp cũng rất nghiêm ngặt:

  • $search phải là stage đầu tiên của pipeline.
  • $vectorSearch phải ở đầu một pipeline và không được nằm trong $facet.

Cách xử lý? $vectorSearch có thể là stage đầu của một subpipeline bên trong $unionWith, vì subpipeline này được coi là một pipeline riêng.

Vì vậy, ta phải bắt đầu với $search để kết quả tìm kiếm toàn văn bản giữ được điểm, rồi dùng $unionWith làm mini-pipeline thứ hai bắt đầu bằng $vectorSearch để nó cũng giữ được điểm.

Sau đó, ta cần group theo _id để không bị trùng lặp và để dùng được điểm tốt nhất từ mỗi $search và $vectorSearch.

Xong bước đó, ta cần xác định điểm tìm kiếm lai: điểm tốt nhất từ $search, điểm tốt nhất từ $vectorSearch, rồi nhân với trọng số do ta chọn (tôi chọn 0,35), và sắp xếp theo đó.

Trọng số quyết định mức độ ảnh hưởng của tìm kiếm vector so với tìm kiếm toàn văn bản trong kết quả cuối cùng.

Công thức lai của chúng ta là: hybrid_score = text_score + (vector_score x weight). Nghĩa là trọng số cao hơn (ví dụ 0,7–1,0) khiến tìm kiếm vector chi phối, ưu tiên độ tương đồng ngữ nghĩa.

Ngược lại, trọng số thấp (0,1–0,3) khiến tìm kiếm toàn văn bản chi phối và ưu tiên khớp từ khóa chính xác.

Sao chép và dán pipeline tổng hợp này vào cửa sổ terminal của bạn:

const WEIGHT = 0.35;

db.embedded_movies.aggregate([
// Stage 1: full-text search
  { $search: { index: "hybrid-full-text-search", text: { query: "star wars", path: ["title","plot"] } } },
  { $set: { t: { $meta: "searchScore" } } },
  { $project: { _id: 1, title: 1, plot: 1, t: 1 } },
// Stage 2: Union with vector search
  { $unionWith: {
      coll: "embedded_movies",
      pipeline: [
// Vector search subpipeline
        { $vectorSearch: {
            index: "hybrid-vector-search",
            path: "plot_embedding_voyage_3_large",
            queryVector: queryVec,
            numCandidates: 200,
            limit: 150
        }},
        { $project: { _id: 1, title: 1, plot: 1, v: { $meta: "vectorSearchScore" } } }
      ]
  }},
// Stage 3: Combine and rank results
  { $group: { _id: "$_id", title: { $first: "$title" }, plot: { $first: "$plot" }, t: { $max: "$t" }, v: { $max: "$v" } } },
  { $set: { h: { $add: [ { $ifNull: ["$t", 0] }, { $multiply: [ { $ifNull: ["$v", 0] }, WEIGHT ] } ] } } },
  { $sort: { h: -1 } },
  { $limit: 20 }
]).toArray()

Đầu ra của chúng ta như sau:

  {
    _id: ObjectId('573a139af29313caabcf124d'),
    title: 'Star Wars: Episode III - Revenge of the Sith',
    plot: 'As the Clone Wars near an end, the Sith Lord Darth Sidious steps out of the shadows, at which time Anakin succumbs to his emotions, becoming Darth Vader and putting his relationships with Obi-Wan and Padme at risk.',
    t: 4.957413673400879,
    v: 0.756283700466156,
    h: 5.2221129685640335
  },
  {
    _id: ObjectId('573a13a6f29313caabd17d08'),
    title: 'Star',
    plot: 'The Driver now carries an arrogant rock star who is visiting a major city (not Pittsburgh as earlier believed). Played by Madonna, this title character wants to get away from her bodyguards...',
    t: 5.151784420013428,
    v: null,
    h: 5.151784420013428
  },
  {
    _id: ObjectId('573a1397f29313caabce8cdb'),
    title: 'Star Wars: Episode VI - Return of the Jedi',
    plot: 'After rescuing Han Solo from the palace of Jabba the Hutt, the rebels attempt to destroy the second Death Star, while Luke struggles to make Vader return from the dark side of the Force.',
    t: 4.726564407348633,
    v: 0.7782549858093262,
    h: 4.998953652381897
  },
  {
    _id: ObjectId('573a13aef29313caabd2da15'),
    title: 'Star Runner',
    plot: 'Get ready for the ultimate martial arts competition, where anything goes and lives are bought and sold. Tank is the celebrated Champion Star Runner and is deemed invincible among the ...',
    t: 4.702453136444092,
    v: 0.696668267250061,
    h: 4.9462870299816135
  },
  {
    _id: ObjectId('573a13c0f29313caabd62f62'),
    title: 'Star Wars: The Clone Wars',
    plot: 'Anakin Skywalker and Ahsoka Tano must rescue the kidnapped son of Jabba the Hutt, but political intrigue complicates their mission.',
    t: 4.537533760070801,
    v: 0.7561323642730713,
    h: 4.802180087566375
  },
  {
    _id: ObjectId('573a1397f29313caabce6f53'),
    title: 'Message from Space',
    plot: 'In this Star Wars take-off, the peaceful planet of Jillucia has been nearly wiped out by the Gavanas, whose leader takes orders from his mother (played a comic actor in drag) rather than ...',
    t: 4.401333808898926,
    v: 0.7913960218429565,
    h: 4.678322416543961
  },
  {
    _id: ObjectId('573a139df29313caabcfa90b'),
    title: 'Message from Space',
    plot: 'In this Star Wars take-off, the peaceful planet of Jillucia has been nearly wiped out by the Gavanas, whose leader takes orders from his mother (played a comic actor in drag) rather than ...',
    t: 4.401333808898926,
    v: 0.7913960218429565,
    h: 4.678322416543961
  },
  {
    _id: ObjectId('573a1398f29313caabce9851'),
    title: 'Gymkata',
    plot: 'Johnathan Cabot is a champion gymnast. In the tiny, yet savage, country of Parmistan, there is a perfect spot for a "star wars" site. For the US to get this site, they must compete in the ...',
    t: 4.284864902496338,
    v: 0.7193170189857483,
    h: 4.53662585914135
  },
  {
    _id: ObjectId('573a1397f29313caabce68f6'),
    title: 'Star Wars: Episode IV - A New Hope',
    plot: "Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, a wookiee and two droids to save the universe from the Empire's world-destroying battle-station, while also attempting to rescue Princess Leia from the evil Darth Vader.",
    t: 2.9629626274108887,
    v: 0.7987099885940552,
    h: 3.242511123418808
  },
  {
    _id: ObjectId('573a139af29313caabcf0f5f'),
    title: 'Star Wars: Episode I - The Phantom Menace',
    plot: 'Two Jedi Knights escape a hostile blockade to find allies and come across a young boy who may bring balance to the Force, but the long dormant Sith resurface to reclaim their old glory.',
    t: 2.9629626274108887,
    v: 0.7771433591842651,
    h: 3.2349628031253816
  },
  {
    _id: ObjectId('573a1397f29313caabce77d9'),
    title: 'Star Wars: Episode V - The Empire Strikes Back',
    plot: 'After the rebels have been brutally overpowered by the Empire on their newly established base, Luke Skywalker takes advanced Jedi training with Master Yoda, while his friends are pursued by Darth Vader as part of his plan to capture Luke.',
    t: 2.7174296379089355,
    v: 0.7810181975364685,
    h: 2.9907860070466996
  },
  {
    _id: ObjectId('573a139af29313caabcf1258'),
    title: 'Star Wars: Episode II - Attack of the Clones',
    plot: 'Ten years after initially meeting, Anakin Skywalker shares a forbidden romance with Padmè, while Obi-Wan investigates an assassination attempt on the Senator and discovers a secret clone army crafted for the Jedi.',
    t: 2.7174296379089355,
    v: 0.7400004267692566,
    h: 2.9764297872781755
  },
  {
    _id: ObjectId('573a1394f29313caabcdf65b'),
    title: 'Ugetsu',
    plot: 'A fantastic tale of war, love, family and ambition set in the midst of the Japanese Civil Wars of the sixteenth century.',
    t: 2.858368396759033,
    v: null,
    h: 2.858368396759033
  },
  {
    _id: ObjectId('573a13a4f29313caabd1137f'),
    title: 'S1m0ne',
    plot: "A producer's film is endangered when his star walks off, so he decides to digitally create an actress to substitute for the star, becoming an overnight sensation that everyone thinks is a real person.",
    t: 2.842216730117798,
    v: null,
    h: 2.842216730117798
  },
  {
    _id: ObjectId('573a13b8f29313caabd4c3c3'),
    title: 'Star Trek',
    plot: "The brash James T. Kirk tries to live up to his father's legacy with Mr. Spock keeping him in check as a vengeful, time-traveling Romulan creates black holes to destroy the Federation one planet at a time.",
    t: 2.577816963195801,
    v: 0.7295459508895874,
    h: 2.8331580460071564
  },
  {
    _id: ObjectId('573a13b5f29313caabd42e99'),
    title: 'Sars Wars',
    plot: "The fourth generation of the virus SARS is found in Africa! It's more dangerous and causes the patients to transform into bloodthirsty zombies. The virus quickly lands to Thailand, Dr. ...",
    t: 2.826810598373413,
    v: null,
    h: 2.826810598373413
  },
  {
    _id: ObjectId('573a13b5f29313caabd42e1b'),
    title: 'Sars Wars',
    plot: "The fourth generation of the virus SARS is found in Africa! It's more dangerous and causes the patients to transform into bloodthirsty zombies. The virus quickly lands to Thailand, Dr. ...",
    t: 2.826810598373413,
    v: null,
    h: 2.826810598373413
  }

As we can see, we have some results that are identical to our query, “star wars,” and other results that are clearly off of meaning.

Conclusion 

Congratulations! You have successfully completed hybrid search in MongoDB. While $rankFusion will simplify this process, this method shows a workaround while the operator is still in Public Preview. Through this tutorial, we have successfully incorporated both full-text search and vector search into one pipeline to retrieve the most optimal results for our given query. For more information on hybrid search in MongoDB, please refer to the MongoDB documentation. If you’re still getting up to speed with MongoDB, I recommend the Introduction to MongoDB in Python course.

MongoDB Hybrid Search FAQs

Do I need MongoDB 8.1 and `$rankFusion` to do hybrid search

No. On 8.0, you can actually fuse the scores yourself. Do this by running $search and $vectorSearch in two pipelines and combine them with $unionWith + $group and a weighted formula. On 8.1+, $rankFusion will do this for you.

What is hybrid search?

It’s combining full-text search (exact words) and vector search (meaning) for the most optimal results possible from a given query.

When should I use hybrid search?

Use hybrid search when your text is varied (synonyms, paraphrases, typos) but you still want exact terms.

How do I pick the weight I want to use?

It’s best practice to begin around 0.2-0.5. The lower is more for full-text search influence, and higher is for semantic influence. It’s important to tune the weight after testing and viewing the results provided.

Can hybrid search work with image and audio data?

Yes! As long as your data can be turned into vector embeddings and you can combine them with any specific wording constraints, you can perform hybrid search on a dataset.

Chủ đề

Liên quan