Publish site
Browse files- index.html +21 -1
- listings.json +2 -0
- posts/hybrid-search-retrieval-bm25-modernbert.html +671 -0
- posts/index.html +21 -1
- search.json +51 -2
- sitemap.xml +4 -0
index.html
CHANGED
|
@@ -200,7 +200,27 @@ window.Quarto = {
|
|
| 200 |
|
| 201 |
<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
|
| 202 |
<div class="list quarto-listing-default">
|
| 203 |
-
<div class="quarto-post image-right" data-index="0" data-listing-date-sort="1773806400000" data-listing-file-modified-sort="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
<div class="body">
|
| 205 |
<h3 class="no-anchor listing-title">
|
| 206 |
<a href="./posts/hello.html" class="no-external">Hello</a>
|
|
|
|
| 200 |
|
| 201 |
<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
|
| 202 |
<div class="list quarto-listing-default">
|
| 203 |
+
<div class="quarto-post image-right" data-index="0" data-categories="c2VhcmNoJTJDcmV0cmlldmFsJTJDYm0yNSUyQ2VtYmVkZGluZ3M=" data-listing-date-sort="1773806400000" data-listing-file-modified-sort="1773878066120" data-listing-date-modified-sort="NaN" data-listing-reading-time-sort="4" data-listing-word-count-sort="632">
|
| 204 |
+
<div class="body">
|
| 205 |
+
<h3 class="no-anchor listing-title">
|
| 206 |
+
<a href="./posts/hybrid-search-retrieval-bm25-modernbert.html" class="no-external">Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples</a>
|
| 207 |
+
</h3>
|
| 208 |
+
<div class="delink listing-description"><a href="./posts/hybrid-search-retrieval-bm25-modernbert.html" class="no-external">
|
| 209 |
+
<p>How to combine ModernBERT embedding similarity with BM25, plus failure modes (negation and identifiers).</p>
|
| 210 |
+
</a></div>
|
| 211 |
+
</div>
|
| 212 |
+
<div class="metadata">
|
| 213 |
+
<a href="./posts/hybrid-search-retrieval-bm25-modernbert.html" class="no-external">
|
| 214 |
+
<div class="listing-date">
|
| 215 |
+
Mar 18, 2026
|
| 216 |
+
</div>
|
| 217 |
+
<div class="listing-author">
|
| 218 |
+
Redis AI Research
|
| 219 |
+
</div>
|
| 220 |
+
</a>
|
| 221 |
+
</div>
|
| 222 |
+
</div>
|
| 223 |
+
<div class="quarto-post image-right" data-index="1" data-listing-date-sort="1773806400000" data-listing-file-modified-sort="1773875458545" data-listing-date-modified-sort="NaN" data-listing-reading-time-sort="1" data-listing-word-count-sort="14">
|
| 224 |
<div class="body">
|
| 225 |
<h3 class="no-anchor listing-title">
|
| 226 |
<a href="./posts/hello.html" class="no-external">Hello</a>
|
listings.json
CHANGED
|
@@ -2,12 +2,14 @@
|
|
| 2 |
{
|
| 3 |
"listing": "/index.html",
|
| 4 |
"items": [
|
|
|
|
| 5 |
"/posts/hello.html"
|
| 6 |
]
|
| 7 |
},
|
| 8 |
{
|
| 9 |
"listing": "/posts/index.html",
|
| 10 |
"items": [
|
|
|
|
| 11 |
"/posts/hello.html"
|
| 12 |
]
|
| 13 |
}
|
|
|
|
| 2 |
{
|
| 3 |
"listing": "/index.html",
|
| 4 |
"items": [
|
| 5 |
+
"/posts/hybrid-search-retrieval-bm25-modernbert.html",
|
| 6 |
"/posts/hello.html"
|
| 7 |
]
|
| 8 |
},
|
| 9 |
{
|
| 10 |
"listing": "/posts/index.html",
|
| 11 |
"items": [
|
| 12 |
+
"/posts/hybrid-search-retrieval-bm25-modernbert.html",
|
| 13 |
"/posts/hello.html"
|
| 14 |
]
|
| 15 |
}
|
posts/hybrid-search-retrieval-bm25-modernbert.html
ADDED
|
@@ -0,0 +1,671 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
|
| 3 |
+
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="generator" content="quarto-1.8.27">
|
| 6 |
+
|
| 7 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
| 8 |
+
|
| 9 |
+
<meta name="author" content="Redis AI Research">
|
| 10 |
+
<meta name="dcterms.date" content="2026-03-18">
|
| 11 |
+
<meta name="description" content="How to combine ModernBERT embedding similarity with BM25, plus failure modes (negation and identifiers).">
|
| 12 |
+
|
| 13 |
+
<title>Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples – AI Research @Redis</title>
|
| 14 |
+
<style>
|
| 15 |
+
code{white-space: pre-wrap;}
|
| 16 |
+
span.smallcaps{font-variant: small-caps;}
|
| 17 |
+
div.columns{display: flex; gap: min(4vw, 1.5em);}
|
| 18 |
+
div.column{flex: auto; overflow-x: auto;}
|
| 19 |
+
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
| 20 |
+
ul.task-list{list-style: none;}
|
| 21 |
+
ul.task-list li input[type="checkbox"] {
|
| 22 |
+
width: 0.8em;
|
| 23 |
+
margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */
|
| 24 |
+
vertical-align: middle;
|
| 25 |
+
}
|
| 26 |
+
</style>
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
<script src="../site_libs/quarto-nav/quarto-nav.js"></script>
|
| 30 |
+
<script src="../site_libs/quarto-nav/headroom.min.js"></script>
|
| 31 |
+
<script src="../site_libs/clipboard/clipboard.min.js"></script>
|
| 32 |
+
<script src="../site_libs/quarto-search/autocomplete.umd.js"></script>
|
| 33 |
+
<script src="../site_libs/quarto-search/fuse.min.js"></script>
|
| 34 |
+
<script src="../site_libs/quarto-search/quarto-search.js"></script>
|
| 35 |
+
<meta name="quarto:offset" content="../">
|
| 36 |
+
<script src="../site_libs/quarto-html/quarto.js" type="module"></script>
|
| 37 |
+
<script src="../site_libs/quarto-html/tabsets/tabsets.js" type="module"></script>
|
| 38 |
+
<script src="../site_libs/quarto-html/axe/axe-check.js" type="module"></script>
|
| 39 |
+
<script src="../site_libs/quarto-html/popper.min.js"></script>
|
| 40 |
+
<script src="../site_libs/quarto-html/tippy.umd.min.js"></script>
|
| 41 |
+
<script src="../site_libs/quarto-html/anchor.min.js"></script>
|
| 42 |
+
<link href="../site_libs/quarto-html/tippy.css" rel="stylesheet">
|
| 43 |
+
<link href="../site_libs/quarto-html/quarto-syntax-highlighting-ed96de9b727972fe78a7b5d16c58bf87.css" rel="stylesheet" id="quarto-text-highlighting-styles">
|
| 44 |
+
<script src="../site_libs/bootstrap/bootstrap.min.js"></script>
|
| 45 |
+
<link href="../site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
|
| 46 |
+
<link href="../site_libs/bootstrap/bootstrap-a8976c3e89df70b272bdfba3d2fda974.min.css" rel="stylesheet" append-hash="true" id="quarto-bootstrap" data-mode="light">
|
| 47 |
+
<script id="quarto-search-options" type="application/json">{
|
| 48 |
+
"location": "navbar",
|
| 49 |
+
"copy-button": false,
|
| 50 |
+
"collapse-after": 3,
|
| 51 |
+
"panel-placement": "end",
|
| 52 |
+
"type": "overlay",
|
| 53 |
+
"limit": 50,
|
| 54 |
+
"keyboard-shortcut": [
|
| 55 |
+
"f",
|
| 56 |
+
"/",
|
| 57 |
+
"s"
|
| 58 |
+
],
|
| 59 |
+
"show-item-context": false,
|
| 60 |
+
"language": {
|
| 61 |
+
"search-no-results-text": "No results",
|
| 62 |
+
"search-matching-documents-text": "matching documents",
|
| 63 |
+
"search-copy-link-title": "Copy link to search",
|
| 64 |
+
"search-hide-matches-text": "Hide additional matches",
|
| 65 |
+
"search-more-match-text": "more match in this document",
|
| 66 |
+
"search-more-matches-text": "more matches in this document",
|
| 67 |
+
"search-clear-button-title": "Clear",
|
| 68 |
+
"search-text-placeholder": "",
|
| 69 |
+
"search-detached-cancel-button-title": "Cancel",
|
| 70 |
+
"search-submit-button-title": "Submit",
|
| 71 |
+
"search-label": "Search"
|
| 72 |
+
}
|
| 73 |
+
}</script>
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
</head>
|
| 77 |
+
|
| 78 |
+
<body class="nav-fixed quarto-light">
|
| 79 |
+
|
| 80 |
+
<div id="quarto-search-results"></div>
|
| 81 |
+
<header id="quarto-header" class="headroom fixed-top">
|
| 82 |
+
<nav class="navbar navbar-expand-lg " data-bs-theme="dark">
|
| 83 |
+
<div class="navbar-container container-fluid">
|
| 84 |
+
<div class="navbar-brand-container mx-auto">
|
| 85 |
+
<a class="navbar-brand" href="../index.html">
|
| 86 |
+
<span class="navbar-title">AI Research <span class="citation" data-cites="Redis">@Redis</span></span>
|
| 87 |
+
</a>
|
| 88 |
+
</div>
|
| 89 |
+
<div id="quarto-search" class="" title="Search"></div>
|
| 90 |
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" role="menu" aria-expanded="false" aria-label="Toggle navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
|
| 91 |
+
<span class="navbar-toggler-icon"></span>
|
| 92 |
+
</button>
|
| 93 |
+
<div class="collapse navbar-collapse" id="navbarCollapse">
|
| 94 |
+
<ul class="navbar-nav navbar-nav-scroll me-auto">
|
| 95 |
+
<li class="nav-item">
|
| 96 |
+
<a class="nav-link" href="../index.html">
|
| 97 |
+
<span class="menu-text">Home</span></a>
|
| 98 |
+
</li>
|
| 99 |
+
<li class="nav-item">
|
| 100 |
+
<a class="nav-link" href="../posts/index.html">
|
| 101 |
+
<span class="menu-text">Posts</span></a>
|
| 102 |
+
</li>
|
| 103 |
+
</ul>
|
| 104 |
+
</div> <!-- /navcollapse -->
|
| 105 |
+
<div class="quarto-navbar-tools">
|
| 106 |
+
</div>
|
| 107 |
+
</div> <!-- /container-fluid -->
|
| 108 |
+
</nav>
|
| 109 |
+
</header>
|
| 110 |
+
<!-- content -->
|
| 111 |
+
<div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article page-navbar">
|
| 112 |
+
<!-- sidebar -->
|
| 113 |
+
<!-- margin-sidebar -->
|
| 114 |
+
<div id="quarto-margin-sidebar" class="sidebar margin-sidebar">
|
| 115 |
+
<nav id="TOC" role="doc-toc" class="toc-active">
|
| 116 |
+
<h2 id="toc-title">On this page</h2>
|
| 117 |
+
|
| 118 |
+
<ul>
|
| 119 |
+
<li><a href="#example-1" id="toc-example-1" class="nav-link active" data-scroll-target="#example-1">Example 1</a></li>
|
| 120 |
+
<li><a href="#example-2" id="toc-example-2" class="nav-link" data-scroll-target="#example-2">Example 2</a></li>
|
| 121 |
+
<li><a href="#failure-mode-1---negationsdirectionality" id="toc-failure-mode-1---negationsdirectionality" class="nav-link" data-scroll-target="#failure-mode-1---negationsdirectionality">Failure mode 1 - Negations/directionality</a></li>
|
| 122 |
+
<li><a href="#failure-mode-2---attributesidentifiers" id="toc-failure-mode-2---attributesidentifiers" class="nav-link" data-scroll-target="#failure-mode-2---attributesidentifiers">Failure mode 2 - Attributes/Identifiers</a></li>
|
| 123 |
+
<li><a href="#how-does-the-hybrid-score-and-rrf-fare" id="toc-how-does-the-hybrid-score-and-rrf-fare" class="nav-link" data-scroll-target="#how-does-the-hybrid-score-and-rrf-fare">How does the hybrid score and RRF fare?</a></li>
|
| 124 |
+
<li><a href="#but-what-did-we-want-to-achieve-with-bm25" id="toc-but-what-did-we-want-to-achieve-with-bm25" class="nav-link" data-scroll-target="#but-what-did-we-want-to-achieve-with-bm25">But what did we want to achieve with BM25?</a></li>
|
| 125 |
+
</ul>
|
| 126 |
+
</nav>
|
| 127 |
+
</div>
|
| 128 |
+
<!-- main -->
|
| 129 |
+
<main class="content" id="quarto-document-content">
|
| 130 |
+
|
| 131 |
+
<header id="title-block-header" class="quarto-title-block default">
|
| 132 |
+
<div class="quarto-title">
|
| 133 |
+
<h1 class="title">Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples</h1>
|
| 134 |
+
<div class="quarto-categories">
|
| 135 |
+
<div class="quarto-category">search</div>
|
| 136 |
+
<div class="quarto-category">retrieval</div>
|
| 137 |
+
<div class="quarto-category">bm25</div>
|
| 138 |
+
<div class="quarto-category">embeddings</div>
|
| 139 |
+
</div>
|
| 140 |
+
</div>
|
| 141 |
+
|
| 142 |
+
<div>
|
| 143 |
+
<div class="description">
|
| 144 |
+
How to combine ModernBERT embedding similarity with BM25, plus failure modes (negation and identifiers).
|
| 145 |
+
</div>
|
| 146 |
+
</div>
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
<div class="quarto-title-meta">
|
| 150 |
+
|
| 151 |
+
<div>
|
| 152 |
+
<div class="quarto-title-meta-heading">Author</div>
|
| 153 |
+
<div class="quarto-title-meta-contents">
|
| 154 |
+
<p>Redis AI Research </p>
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
+
|
| 158 |
+
<div>
|
| 159 |
+
<div class="quarto-title-meta-heading">Published</div>
|
| 160 |
+
<div class="quarto-title-meta-contents">
|
| 161 |
+
<p class="date">March 18, 2026</p>
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
</div>
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
</header>
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
<p>Source: https://redislabs.atlassian.net/wiki/spaces/RED/pages/5798592518/Hybrid+Search+Retrieval+Combining+Semantic+Search+and+BM25+Algorithm+with+Examples</p>
|
| 174 |
+
<p>The examples below illustrate a hybrid search retrieval of a query from a corpus of documents by combining semantic search using ModernBERT embeddings and the BM25 algorithm.</p>
|
| 175 |
+
<p>The scores from each are normalized between 0 and 1 in order to create a weighted average hybrid score. The raw embedding score is also shown in the columns for diagnostics. The ranks in each measure are then combined using RRF to create another score that can be used for retrieval.</p>
|
| 176 |
+
<p>The most correct match for each query is highlighted in each example.</p>
|
| 177 |
+
<section id="example-1" class="level2">
|
| 178 |
+
<h2 class="anchored" data-anchor-id="example-1">Example 1</h2>
|
| 179 |
+
<p>(Screenshot reference: <code>Screenshot 2026-01-08 at 9.07.36 PM.png</code>)</p>
|
| 180 |
+
</section>
|
| 181 |
+
<section id="example-2" class="level2">
|
| 182 |
+
<h2 class="anchored" data-anchor-id="example-2">Example 2</h2>
|
| 183 |
+
<p>(Screenshot reference: <code>Screenshot 2026-01-08 at 9.11.31 PM.png</code>)</p>
|
| 184 |
+
</section>
|
| 185 |
+
<section id="failure-mode-1---negationsdirectionality" class="level2">
|
| 186 |
+
<h2 class="anchored" data-anchor-id="failure-mode-1---negationsdirectionality">Failure mode 1 - Negations/directionality</h2>
|
| 187 |
+
<p>BM25 is not keyword search; it up-weights rare terms and not just keywords.</p>
|
| 188 |
+
<p>In example 1, the key term of interest or the attribute/identifier is <code>#124</code>. However, the other terms in the query <code>semantic</code>, <code>search</code>, <code>for</code>, <code>doc</code> are all relatively rare and contribute to higher BM25 scores as evidenced in the first two records in the table index 8 and 11. These have negated intents however and therefore incorrect matches. BM25 has no ability to identify negations.</p>
|
| 189 |
+
<p>The similarity scores fortunately are not the highest but will most likely exceed most commonly used thresholds. Semantic search can fail to identify negated intent.</p>
|
| 190 |
+
</section>
|
| 191 |
+
<section id="failure-mode-2---attributesidentifiers" class="level2">
|
| 192 |
+
<h2 class="anchored" data-anchor-id="failure-mode-2---attributesidentifiers">Failure mode 2 - Attributes/Identifiers</h2>
|
| 193 |
+
<p>“The next three docs given by ids 5, 7 and 6 have the same phrasing but with the wrong identifier of #123, #1243 and #1233 respectively which lowers their BM25 score a bit but certainly higher than all the other documents. BM25 can detect a different attribute/identifier but the actual magnitude of this will depend on what the rest of the sentence looks like.”</p>
|
| 194 |
+
<ul>
|
| 195 |
+
<li>If there is strong lexical overlap but only the identifier is different, there will be a small drop in the BM25 score.</li>
|
| 196 |
+
<li>If the rest of the sentence is NULL then the BM25 score is 0.</li>
|
| 197 |
+
<li>If there is very little overlap in the rest of the query and the doc.</li>
|
| 198 |
+
</ul>
|
| 199 |
+
<p>BM25 can identify attribute/identifier changes but is hard to put a threshold on this score.</p>
|
| 200 |
+
<p>W.r.t embeddings two things are notable:</p>
|
| 201 |
+
<ul>
|
| 202 |
+
<li>There are two forces at play here: the global semantic intent match and the identifier match.</li>
|
| 203 |
+
<li>In 8 and 11, there is negated intent whereas there is an identifier match.</li>
|
| 204 |
+
<li>In 5, 6 and 7 the general intent is correct but the identifier is incorrect.</li>
|
| 205 |
+
<li>The resulting score is a complex interaction of the two and the answer to “Can we identify negations or attribute changes with a threshold?” is heavily data-dependent.</li>
|
| 206 |
+
</ul>
|
| 207 |
+
<p>Note that the embedding scores for 5, 7 and 6 are some of the highest because attribute/identifier differences are something that embeddings struggle to detect.</p>
|
| 208 |
+
</section>
|
| 209 |
+
<section id="how-does-the-hybrid-score-and-rrf-fare" class="level2">
|
| 210 |
+
<h2 class="anchored" data-anchor-id="how-does-the-hybrid-score-and-rrf-fare">How does the hybrid score and RRF fare?</h2>
|
| 211 |
+
<p>Averaging out with a weighted hybrid score or RRF has no impact here. In example 1, record id 9 which is the closest match still does not rank highly in either of these scenarios.</p>
|
| 212 |
+
<p>To summarize the above for both approaches for the failure modes, it seems that embeddings seem to have the slight edge here.</p>
|
| 213 |
+
<table class="caption-top table">
|
| 214 |
+
<thead>
|
| 215 |
+
<tr class="header">
|
| 216 |
+
<th>Method</th>
|
| 217 |
+
<th>Negation</th>
|
| 218 |
+
<th>Directionality</th>
|
| 219 |
+
<th>Attribute/Identifier</th>
|
| 220 |
+
</tr>
|
| 221 |
+
</thead>
|
| 222 |
+
<tbody>
|
| 223 |
+
<tr class="odd">
|
| 224 |
+
<td>BM25</td>
|
| 225 |
+
<td>N</td>
|
| 226 |
+
<td>N</td>
|
| 227 |
+
<td>N (hard to put a threshold)</td>
|
| 228 |
+
</tr>
|
| 229 |
+
<tr class="even">
|
| 230 |
+
<td>Embedding</td>
|
| 231 |
+
<td>Sometimes</td>
|
| 232 |
+
<td>N</td>
|
| 233 |
+
<td>Sometimes</td>
|
| 234 |
+
</tr>
|
| 235 |
+
</tbody>
|
| 236 |
+
</table>
|
| 237 |
+
<p>We can’t eliminate all the issues, but we can hope to mitigate based on our understanding of the data.</p>
|
| 238 |
+
</section>
|
| 239 |
+
<section id="but-what-did-we-want-to-achieve-with-bm25" class="level2">
|
| 240 |
+
<h2 class="anchored" data-anchor-id="but-what-did-we-want-to-achieve-with-bm25">But what did we want to achieve with BM25?</h2>
|
| 241 |
+
<p>We are really looking to filter out matches that do not have the right attributes/identifiers we seek. BM25 looks like it could provide that, but does more than attribute matching.</p>
|
| 242 |
+
<ul>
|
| 243 |
+
<li>Extract identifiers from query.</li>
|
| 244 |
+
<li>Perform embedding based retrieval of query + docs to retrieve set M.</li>
|
| 245 |
+
<li>Filter out docs where identifiers do not match in set M.</li>
|
| 246 |
+
<li>Unfortunately, this does nothing to address the negation/directionality limitation.</li>
|
| 247 |
+
</ul>
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
</section>
|
| 251 |
+
|
| 252 |
+
</main> <!-- /main -->
|
| 253 |
+
<script id="quarto-html-after-body" type="application/javascript">
|
| 254 |
+
window.document.addEventListener("DOMContentLoaded", function (event) {
|
| 255 |
+
const icon = "";
|
| 256 |
+
const anchorJS = new window.AnchorJS();
|
| 257 |
+
anchorJS.options = {
|
| 258 |
+
placement: 'right',
|
| 259 |
+
icon: icon
|
| 260 |
+
};
|
| 261 |
+
anchorJS.add('.anchored');
|
| 262 |
+
const isCodeAnnotation = (el) => {
|
| 263 |
+
for (const clz of el.classList) {
|
| 264 |
+
if (clz.startsWith('code-annotation-')) {
|
| 265 |
+
return true;
|
| 266 |
+
}
|
| 267 |
+
}
|
| 268 |
+
return false;
|
| 269 |
+
}
|
| 270 |
+
const onCopySuccess = function(e) {
|
| 271 |
+
// button target
|
| 272 |
+
const button = e.trigger;
|
| 273 |
+
// don't keep focus
|
| 274 |
+
button.blur();
|
| 275 |
+
// flash "checked"
|
| 276 |
+
button.classList.add('code-copy-button-checked');
|
| 277 |
+
var currentTitle = button.getAttribute("title");
|
| 278 |
+
button.setAttribute("title", "Copied!");
|
| 279 |
+
let tooltip;
|
| 280 |
+
if (window.bootstrap) {
|
| 281 |
+
button.setAttribute("data-bs-toggle", "tooltip");
|
| 282 |
+
button.setAttribute("data-bs-placement", "left");
|
| 283 |
+
button.setAttribute("data-bs-title", "Copied!");
|
| 284 |
+
tooltip = new bootstrap.Tooltip(button,
|
| 285 |
+
{ trigger: "manual",
|
| 286 |
+
customClass: "code-copy-button-tooltip",
|
| 287 |
+
offset: [0, -8]});
|
| 288 |
+
tooltip.show();
|
| 289 |
+
}
|
| 290 |
+
setTimeout(function() {
|
| 291 |
+
if (tooltip) {
|
| 292 |
+
tooltip.hide();
|
| 293 |
+
button.removeAttribute("data-bs-title");
|
| 294 |
+
button.removeAttribute("data-bs-toggle");
|
| 295 |
+
button.removeAttribute("data-bs-placement");
|
| 296 |
+
}
|
| 297 |
+
button.setAttribute("title", currentTitle);
|
| 298 |
+
button.classList.remove('code-copy-button-checked');
|
| 299 |
+
}, 1000);
|
| 300 |
+
// clear code selection
|
| 301 |
+
e.clearSelection();
|
| 302 |
+
}
|
| 303 |
+
const getTextToCopy = function(trigger) {
|
| 304 |
+
const outerScaffold = trigger.parentElement.cloneNode(true);
|
| 305 |
+
const codeEl = outerScaffold.querySelector('code');
|
| 306 |
+
for (const childEl of codeEl.children) {
|
| 307 |
+
if (isCodeAnnotation(childEl)) {
|
| 308 |
+
childEl.remove();
|
| 309 |
+
}
|
| 310 |
+
}
|
| 311 |
+
return codeEl.innerText;
|
| 312 |
+
}
|
| 313 |
+
const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', {
|
| 314 |
+
text: getTextToCopy
|
| 315 |
+
});
|
| 316 |
+
clipboard.on('success', onCopySuccess);
|
| 317 |
+
if (window.document.getElementById('quarto-embedded-source-code-modal')) {
|
| 318 |
+
const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', {
|
| 319 |
+
text: getTextToCopy,
|
| 320 |
+
container: window.document.getElementById('quarto-embedded-source-code-modal')
|
| 321 |
+
});
|
| 322 |
+
clipboardModal.on('success', onCopySuccess);
|
| 323 |
+
}
|
| 324 |
+
var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
|
| 325 |
+
var mailtoRegex = new RegExp(/^mailto:/);
|
| 326 |
+
var filterRegex = new RegExp("https:\/\/srijithrajamohan-ai-research-redis\.static\.hf\.space");
|
| 327 |
+
var isInternal = (href) => {
|
| 328 |
+
return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
|
| 329 |
+
}
|
| 330 |
+
// Inspect non-navigation links and adorn them if external
|
| 331 |
+
var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)');
|
| 332 |
+
for (var i=0; i<links.length; i++) {
|
| 333 |
+
const link = links[i];
|
| 334 |
+
if (!isInternal(link.href)) {
|
| 335 |
+
// undo the damage that might have been done by quarto-nav.js in the case of
|
| 336 |
+
// links that we want to consider external
|
| 337 |
+
if (link.dataset.originalHref !== undefined) {
|
| 338 |
+
link.href = link.dataset.originalHref;
|
| 339 |
+
}
|
| 340 |
+
}
|
| 341 |
+
}
|
| 342 |
+
function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
|
| 343 |
+
const config = {
|
| 344 |
+
allowHTML: true,
|
| 345 |
+
maxWidth: 500,
|
| 346 |
+
delay: 100,
|
| 347 |
+
arrow: false,
|
| 348 |
+
appendTo: function(el) {
|
| 349 |
+
return el.parentElement;
|
| 350 |
+
},
|
| 351 |
+
interactive: true,
|
| 352 |
+
interactiveBorder: 10,
|
| 353 |
+
theme: 'quarto',
|
| 354 |
+
placement: 'bottom-start',
|
| 355 |
+
};
|
| 356 |
+
if (contentFn) {
|
| 357 |
+
config.content = contentFn;
|
| 358 |
+
}
|
| 359 |
+
if (onTriggerFn) {
|
| 360 |
+
config.onTrigger = onTriggerFn;
|
| 361 |
+
}
|
| 362 |
+
if (onUntriggerFn) {
|
| 363 |
+
config.onUntrigger = onUntriggerFn;
|
| 364 |
+
}
|
| 365 |
+
window.tippy(el, config);
|
| 366 |
+
}
|
| 367 |
+
const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
|
| 368 |
+
for (var i=0; i<noterefs.length; i++) {
|
| 369 |
+
const ref = noterefs[i];
|
| 370 |
+
tippyHover(ref, function() {
|
| 371 |
+
// use id or data attribute instead here
|
| 372 |
+
let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
|
| 373 |
+
try { href = new URL(href).hash; } catch {}
|
| 374 |
+
const id = href.replace(/^#\/?/, "");
|
| 375 |
+
const note = window.document.getElementById(id);
|
| 376 |
+
if (note) {
|
| 377 |
+
return note.innerHTML;
|
| 378 |
+
} else {
|
| 379 |
+
return "";
|
| 380 |
+
}
|
| 381 |
+
});
|
| 382 |
+
}
|
| 383 |
+
const xrefs = window.document.querySelectorAll('a.quarto-xref');
|
| 384 |
+
const processXRef = (id, note) => {
|
| 385 |
+
// Strip column container classes
|
| 386 |
+
const stripColumnClz = (el) => {
|
| 387 |
+
el.classList.remove("page-full", "page-columns");
|
| 388 |
+
if (el.children) {
|
| 389 |
+
for (const child of el.children) {
|
| 390 |
+
stripColumnClz(child);
|
| 391 |
+
}
|
| 392 |
+
}
|
| 393 |
+
}
|
| 394 |
+
stripColumnClz(note)
|
| 395 |
+
if (id === null || id.startsWith('sec-')) {
|
| 396 |
+
// Special case sections, only their first couple elements
|
| 397 |
+
const container = document.createElement("div");
|
| 398 |
+
if (note.children && note.children.length > 2) {
|
| 399 |
+
container.appendChild(note.children[0].cloneNode(true));
|
| 400 |
+
for (let i = 1; i < note.children.length; i++) {
|
| 401 |
+
const child = note.children[i];
|
| 402 |
+
if (child.tagName === "P" && child.innerText === "") {
|
| 403 |
+
continue;
|
| 404 |
+
} else {
|
| 405 |
+
container.appendChild(child.cloneNode(true));
|
| 406 |
+
break;
|
| 407 |
+
}
|
| 408 |
+
}
|
| 409 |
+
if (window.Quarto?.typesetMath) {
|
| 410 |
+
window.Quarto.typesetMath(container);
|
| 411 |
+
}
|
| 412 |
+
return container.innerHTML
|
| 413 |
+
} else {
|
| 414 |
+
if (window.Quarto?.typesetMath) {
|
| 415 |
+
window.Quarto.typesetMath(note);
|
| 416 |
+
}
|
| 417 |
+
return note.innerHTML;
|
| 418 |
+
}
|
| 419 |
+
} else {
|
| 420 |
+
// Remove any anchor links if they are present
|
| 421 |
+
const anchorLink = note.querySelector('a.anchorjs-link');
|
| 422 |
+
if (anchorLink) {
|
| 423 |
+
anchorLink.remove();
|
| 424 |
+
}
|
| 425 |
+
if (window.Quarto?.typesetMath) {
|
| 426 |
+
window.Quarto.typesetMath(note);
|
| 427 |
+
}
|
| 428 |
+
if (note.classList.contains("callout")) {
|
| 429 |
+
return note.outerHTML;
|
| 430 |
+
} else {
|
| 431 |
+
return note.innerHTML;
|
| 432 |
+
}
|
| 433 |
+
}
|
| 434 |
+
}
|
| 435 |
+
for (var i=0; i<xrefs.length; i++) {
|
| 436 |
+
const xref = xrefs[i];
|
| 437 |
+
tippyHover(xref, undefined, function(instance) {
|
| 438 |
+
instance.disable();
|
| 439 |
+
let url = xref.getAttribute('href');
|
| 440 |
+
let hash = undefined;
|
| 441 |
+
if (url.startsWith('#')) {
|
| 442 |
+
hash = url;
|
| 443 |
+
} else {
|
| 444 |
+
try { hash = new URL(url).hash; } catch {}
|
| 445 |
+
}
|
| 446 |
+
if (hash) {
|
| 447 |
+
const id = hash.replace(/^#\/?/, "");
|
| 448 |
+
const note = window.document.getElementById(id);
|
| 449 |
+
if (note !== null) {
|
| 450 |
+
try {
|
| 451 |
+
const html = processXRef(id, note.cloneNode(true));
|
| 452 |
+
instance.setContent(html);
|
| 453 |
+
} finally {
|
| 454 |
+
instance.enable();
|
| 455 |
+
instance.show();
|
| 456 |
+
}
|
| 457 |
+
} else {
|
| 458 |
+
// See if we can fetch this
|
| 459 |
+
fetch(url.split('#')[0])
|
| 460 |
+
.then(res => res.text())
|
| 461 |
+
.then(html => {
|
| 462 |
+
const parser = new DOMParser();
|
| 463 |
+
const htmlDoc = parser.parseFromString(html, "text/html");
|
| 464 |
+
const note = htmlDoc.getElementById(id);
|
| 465 |
+
if (note !== null) {
|
| 466 |
+
const html = processXRef(id, note);
|
| 467 |
+
instance.setContent(html);
|
| 468 |
+
}
|
| 469 |
+
}).finally(() => {
|
| 470 |
+
instance.enable();
|
| 471 |
+
instance.show();
|
| 472 |
+
});
|
| 473 |
+
}
|
| 474 |
+
} else {
|
| 475 |
+
// See if we can fetch a full url (with no hash to target)
|
| 476 |
+
// This is a special case and we should probably do some content thinning / targeting
|
| 477 |
+
fetch(url)
|
| 478 |
+
.then(res => res.text())
|
| 479 |
+
.then(html => {
|
| 480 |
+
const parser = new DOMParser();
|
| 481 |
+
const htmlDoc = parser.parseFromString(html, "text/html");
|
| 482 |
+
const note = htmlDoc.querySelector('main.content');
|
| 483 |
+
if (note !== null) {
|
| 484 |
+
// This should only happen for chapter cross references
|
| 485 |
+
// (since there is no id in the URL)
|
| 486 |
+
// remove the first header
|
| 487 |
+
if (note.children.length > 0 && note.children[0].tagName === "HEADER") {
|
| 488 |
+
note.children[0].remove();
|
| 489 |
+
}
|
| 490 |
+
const html = processXRef(null, note);
|
| 491 |
+
instance.setContent(html);
|
| 492 |
+
}
|
| 493 |
+
}).finally(() => {
|
| 494 |
+
instance.enable();
|
| 495 |
+
instance.show();
|
| 496 |
+
});
|
| 497 |
+
}
|
| 498 |
+
}, function(instance) {
|
| 499 |
+
});
|
| 500 |
+
}
|
| 501 |
+
let selectedAnnoteEl;
|
| 502 |
+
const selectorForAnnotation = ( cell, annotation) => {
|
| 503 |
+
let cellAttr = 'data-code-cell="' + cell + '"';
|
| 504 |
+
let lineAttr = 'data-code-annotation="' + annotation + '"';
|
| 505 |
+
const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
|
| 506 |
+
return selector;
|
| 507 |
+
}
|
| 508 |
+
const selectCodeLines = (annoteEl) => {
|
| 509 |
+
const doc = window.document;
|
| 510 |
+
const targetCell = annoteEl.getAttribute("data-target-cell");
|
| 511 |
+
const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
|
| 512 |
+
const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
|
| 513 |
+
const lines = annoteSpan.getAttribute("data-code-lines").split(",");
|
| 514 |
+
const lineIds = lines.map((line) => {
|
| 515 |
+
return targetCell + "-" + line;
|
| 516 |
+
})
|
| 517 |
+
let top = null;
|
| 518 |
+
let height = null;
|
| 519 |
+
let parent = null;
|
| 520 |
+
if (lineIds.length > 0) {
|
| 521 |
+
//compute the position of the single el (top and bottom and make a div)
|
| 522 |
+
const el = window.document.getElementById(lineIds[0]);
|
| 523 |
+
top = el.offsetTop;
|
| 524 |
+
height = el.offsetHeight;
|
| 525 |
+
parent = el.parentElement.parentElement;
|
| 526 |
+
if (lineIds.length > 1) {
|
| 527 |
+
const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
|
| 528 |
+
const bottom = lastEl.offsetTop + lastEl.offsetHeight;
|
| 529 |
+
height = bottom - top;
|
| 530 |
+
}
|
| 531 |
+
if (top !== null && height !== null && parent !== null) {
|
| 532 |
+
// cook up a div (if necessary) and position it
|
| 533 |
+
let div = window.document.getElementById("code-annotation-line-highlight");
|
| 534 |
+
if (div === null) {
|
| 535 |
+
div = window.document.createElement("div");
|
| 536 |
+
div.setAttribute("id", "code-annotation-line-highlight");
|
| 537 |
+
div.style.position = 'absolute';
|
| 538 |
+
parent.appendChild(div);
|
| 539 |
+
}
|
| 540 |
+
div.style.top = top - 2 + "px";
|
| 541 |
+
div.style.height = height + 4 + "px";
|
| 542 |
+
div.style.left = 0;
|
| 543 |
+
let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
|
| 544 |
+
if (gutterDiv === null) {
|
| 545 |
+
gutterDiv = window.document.createElement("div");
|
| 546 |
+
gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
|
| 547 |
+
gutterDiv.style.position = 'absolute';
|
| 548 |
+
const codeCell = window.document.getElementById(targetCell);
|
| 549 |
+
const gutter = codeCell.querySelector('.code-annotation-gutter');
|
| 550 |
+
gutter.appendChild(gutterDiv);
|
| 551 |
+
}
|
| 552 |
+
gutterDiv.style.top = top - 2 + "px";
|
| 553 |
+
gutterDiv.style.height = height + 4 + "px";
|
| 554 |
+
}
|
| 555 |
+
selectedAnnoteEl = annoteEl;
|
| 556 |
+
}
|
| 557 |
+
};
|
| 558 |
+
const unselectCodeLines = () => {
|
| 559 |
+
const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
|
| 560 |
+
elementsIds.forEach((elId) => {
|
| 561 |
+
const div = window.document.getElementById(elId);
|
| 562 |
+
if (div) {
|
| 563 |
+
div.remove();
|
| 564 |
+
}
|
| 565 |
+
});
|
| 566 |
+
selectedAnnoteEl = undefined;
|
| 567 |
+
};
|
| 568 |
+
// Handle positioning of the toggle
|
| 569 |
+
window.addEventListener(
|
| 570 |
+
"resize",
|
| 571 |
+
throttle(() => {
|
| 572 |
+
elRect = undefined;
|
| 573 |
+
if (selectedAnnoteEl) {
|
| 574 |
+
selectCodeLines(selectedAnnoteEl);
|
| 575 |
+
}
|
| 576 |
+
}, 10)
|
| 577 |
+
);
|
| 578 |
+
function throttle(fn, ms) {
|
| 579 |
+
let throttle = false;
|
| 580 |
+
let timer;
|
| 581 |
+
return (...args) => {
|
| 582 |
+
if(!throttle) { // first call gets through
|
| 583 |
+
fn.apply(this, args);
|
| 584 |
+
throttle = true;
|
| 585 |
+
} else { // all the others get throttled
|
| 586 |
+
if(timer) clearTimeout(timer); // cancel #2
|
| 587 |
+
timer = setTimeout(() => {
|
| 588 |
+
fn.apply(this, args);
|
| 589 |
+
timer = throttle = false;
|
| 590 |
+
}, ms);
|
| 591 |
+
}
|
| 592 |
+
};
|
| 593 |
+
}
|
| 594 |
+
// Attach click handler to the DT
|
| 595 |
+
const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
|
| 596 |
+
for (const annoteDlNode of annoteDls) {
|
| 597 |
+
annoteDlNode.addEventListener('click', (event) => {
|
| 598 |
+
const clickedEl = event.target;
|
| 599 |
+
if (clickedEl !== selectedAnnoteEl) {
|
| 600 |
+
unselectCodeLines();
|
| 601 |
+
const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
|
| 602 |
+
if (activeEl) {
|
| 603 |
+
activeEl.classList.remove('code-annotation-active');
|
| 604 |
+
}
|
| 605 |
+
selectCodeLines(clickedEl);
|
| 606 |
+
clickedEl.classList.add('code-annotation-active');
|
| 607 |
+
} else {
|
| 608 |
+
// Unselect the line
|
| 609 |
+
unselectCodeLines();
|
| 610 |
+
clickedEl.classList.remove('code-annotation-active');
|
| 611 |
+
}
|
| 612 |
+
});
|
| 613 |
+
}
|
| 614 |
+
const findCites = (el) => {
|
| 615 |
+
const parentEl = el.parentElement;
|
| 616 |
+
if (parentEl) {
|
| 617 |
+
const cites = parentEl.dataset.cites;
|
| 618 |
+
if (cites) {
|
| 619 |
+
return {
|
| 620 |
+
el,
|
| 621 |
+
cites: cites.split(' ')
|
| 622 |
+
};
|
| 623 |
+
} else {
|
| 624 |
+
return findCites(el.parentElement)
|
| 625 |
+
}
|
| 626 |
+
} else {
|
| 627 |
+
return undefined;
|
| 628 |
+
}
|
| 629 |
+
};
|
| 630 |
+
var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
|
| 631 |
+
for (var i=0; i<bibliorefs.length; i++) {
|
| 632 |
+
const ref = bibliorefs[i];
|
| 633 |
+
const citeInfo = findCites(ref);
|
| 634 |
+
if (citeInfo) {
|
| 635 |
+
tippyHover(citeInfo.el, function() {
|
| 636 |
+
var popup = window.document.createElement('div');
|
| 637 |
+
citeInfo.cites.forEach(function(cite) {
|
| 638 |
+
var citeDiv = window.document.createElement('div');
|
| 639 |
+
citeDiv.classList.add('hanging-indent');
|
| 640 |
+
citeDiv.classList.add('csl-entry');
|
| 641 |
+
var biblioDiv = window.document.getElementById('ref-' + cite);
|
| 642 |
+
if (biblioDiv) {
|
| 643 |
+
citeDiv.innerHTML = biblioDiv.innerHTML;
|
| 644 |
+
}
|
| 645 |
+
popup.appendChild(citeDiv);
|
| 646 |
+
});
|
| 647 |
+
return popup.innerHTML;
|
| 648 |
+
});
|
| 649 |
+
}
|
| 650 |
+
}
|
| 651 |
+
});
|
| 652 |
+
</script>
|
| 653 |
+
</div> <!-- /content -->
|
| 654 |
+
<footer class="footer">
|
| 655 |
+
<div class="nav-footer">
|
| 656 |
+
<div class="nav-footer-left">
|
| 657 |
+
<p>Discover this project on Hugging Face Spaces: https://huggingface.co/spaces/srijithrajamohan/ai-research-redis</p>
|
| 658 |
+
</div>
|
| 659 |
+
<div class="nav-footer-center">
|
| 660 |
+
|
| 661 |
+
</div>
|
| 662 |
+
<div class="nav-footer-right">
|
| 663 |
+
|
| 664 |
+
</div>
|
| 665 |
+
</div>
|
| 666 |
+
</footer>
|
| 667 |
+
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
|
| 671 |
+
</body></html>
|
posts/index.html
CHANGED
|
@@ -199,7 +199,27 @@ window.Quarto = {
|
|
| 199 |
|
| 200 |
<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
|
| 201 |
<div class="list quarto-listing-default">
|
| 202 |
-
<div class="quarto-post image-right" data-index="0" data-listing-date-sort="1773806400000" data-listing-file-modified-sort="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
<div class="body">
|
| 204 |
<h3 class="no-anchor listing-title">
|
| 205 |
<a href="../posts/hello.html" class="no-external">Hello</a>
|
|
|
|
| 199 |
|
| 200 |
<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
|
| 201 |
<div class="list quarto-listing-default">
|
| 202 |
+
<div class="quarto-post image-right" data-index="0" data-categories="c2VhcmNoJTJDcmV0cmlldmFsJTJDYm0yNSUyQ2VtYmVkZGluZ3M=" data-listing-date-sort="1773806400000" data-listing-file-modified-sort="1773878066120" data-listing-date-modified-sort="NaN" data-listing-reading-time-sort="4" data-listing-word-count-sort="632">
|
| 203 |
+
<div class="body">
|
| 204 |
+
<h3 class="no-anchor listing-title">
|
| 205 |
+
<a href="../posts/hybrid-search-retrieval-bm25-modernbert.html" class="no-external">Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples</a>
|
| 206 |
+
</h3>
|
| 207 |
+
<div class="delink listing-description"><a href="../posts/hybrid-search-retrieval-bm25-modernbert.html" class="no-external">
|
| 208 |
+
<p>How to combine ModernBERT embedding similarity with BM25, plus failure modes (negation and identifiers).</p>
|
| 209 |
+
</a></div>
|
| 210 |
+
</div>
|
| 211 |
+
<div class="metadata">
|
| 212 |
+
<a href="../posts/hybrid-search-retrieval-bm25-modernbert.html" class="no-external">
|
| 213 |
+
<div class="listing-date">
|
| 214 |
+
Mar 18, 2026
|
| 215 |
+
</div>
|
| 216 |
+
<div class="listing-author">
|
| 217 |
+
Redis AI Research
|
| 218 |
+
</div>
|
| 219 |
+
</a>
|
| 220 |
+
</div>
|
| 221 |
+
</div>
|
| 222 |
+
<div class="quarto-post image-right" data-index="1" data-listing-date-sort="1773806400000" data-listing-file-modified-sort="1773875458545" data-listing-date-modified-sort="NaN" data-listing-reading-time-sort="1" data-listing-word-count-sort="14">
|
| 223 |
<div class="body">
|
| 224 |
<h3 class="no-anchor listing-title">
|
| 225 |
<a href="../posts/hello.html" class="no-external">Hello</a>
|
search.json
CHANGED
|
@@ -4,14 +4,63 @@
|
|
| 4 |
"href": "index.html",
|
| 5 |
"title": "AI Research @Redis",
|
| 6 |
"section": "",
|
| 7 |
-
"text": "Homepage of the AI Research team at Redis.\n\n\n\n\n\n\n\n\n\nHello\n\n\nFirst Quarto post (template).\n\n\n\n\n\nMar 18, 2026\n\n\nRedis AI Research\n\n\n\n\n\nNo matching items"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
},
|
| 9 |
{
|
| 10 |
"objectID": "posts/index.html",
|
| 11 |
"href": "posts/index.html",
|
| 12 |
"title": "Posts",
|
| 13 |
"section": "",
|
| 14 |
-
"text": "
|
| 15 |
},
|
| 16 |
{
|
| 17 |
"objectID": "posts/hello.html",
|
|
|
|
| 4 |
"href": "index.html",
|
| 5 |
"title": "AI Research @Redis",
|
| 6 |
"section": "",
|
| 7 |
+
"text": "Homepage of the AI Research team at Redis.\n\n\n\n\n\n\n\n\n\nHybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples\n\n\nHow to combine ModernBERT embedding similarity with BM25, plus failure modes (negation and identifiers).\n\n\n\n\n\nMar 18, 2026\n\n\nRedis AI Research\n\n\n\n\n\n\n\nHello\n\n\nFirst Quarto post (template).\n\n\n\n\n\nMar 18, 2026\n\n\nRedis AI Research\n\n\n\n\n\nNo matching items"
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"objectID": "posts/hybrid-search-retrieval-bm25-modernbert.html",
|
| 11 |
+
"href": "posts/hybrid-search-retrieval-bm25-modernbert.html",
|
| 12 |
+
"title": "Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples",
|
| 13 |
+
"section": "",
|
| 14 |
+
"text": "Source: https://redislabs.atlassian.net/wiki/spaces/RED/pages/5798592518/Hybrid+Search+Retrieval+Combining+Semantic+Search+and+BM25+Algorithm+with+Examples\nThe examples below illustrate a hybrid search retrieval of a query from a corpus of documents by combining semantic search using ModernBERT embeddings and the BM25 algorithm.\nThe scores from each are normalized between 0 and 1 in order to create a weighted average hybrid score. The raw embedding score is also shown in the columns for diagnostics. The ranks in each measure are then combined using RRF to create another score that can be used for retrieval.\nThe most correct match for each query is highlighted in each example."
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"objectID": "posts/hybrid-search-retrieval-bm25-modernbert.html#example-1",
|
| 18 |
+
"href": "posts/hybrid-search-retrieval-bm25-modernbert.html#example-1",
|
| 19 |
+
"title": "Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples",
|
| 20 |
+
"section": "Example 1",
|
| 21 |
+
"text": "Example 1\n(Screenshot reference: Screenshot 2026-01-08 at 9.07.36 PM.png)"
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"objectID": "posts/hybrid-search-retrieval-bm25-modernbert.html#example-2",
|
| 25 |
+
"href": "posts/hybrid-search-retrieval-bm25-modernbert.html#example-2",
|
| 26 |
+
"title": "Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples",
|
| 27 |
+
"section": "Example 2",
|
| 28 |
+
"text": "Example 2\n(Screenshot reference: Screenshot 2026-01-08 at 9.11.31 PM.png)"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"objectID": "posts/hybrid-search-retrieval-bm25-modernbert.html#failure-mode-1---negationsdirectionality",
|
| 32 |
+
"href": "posts/hybrid-search-retrieval-bm25-modernbert.html#failure-mode-1---negationsdirectionality",
|
| 33 |
+
"title": "Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples",
|
| 34 |
+
"section": "Failure mode 1 - Negations/directionality",
|
| 35 |
+
"text": "Failure mode 1 - Negations/directionality\nBM25 is not keyword search; it up-weights rare terms and not just keywords.\nIn example 1, the key term of interest or the attribute/identifier is #124. However, the other terms in the query semantic, search, for, doc are all relatively rare and contribute to higher BM25 scores as evidenced in the first two records in the table index 8 and 11. These have negated intents however and therefore incorrect matches. BM25 has no ability to identify negations.\nThe similarity scores fortunately are not the highest but will most likely exceed most commonly used thresholds. Semantic search can fail to identify negated intent."
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"objectID": "posts/hybrid-search-retrieval-bm25-modernbert.html#failure-mode-2---attributesidentifiers",
|
| 39 |
+
"href": "posts/hybrid-search-retrieval-bm25-modernbert.html#failure-mode-2---attributesidentifiers",
|
| 40 |
+
"title": "Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples",
|
| 41 |
+
"section": "Failure mode 2 - Attributes/Identifiers",
|
| 42 |
+
"text": "Failure mode 2 - Attributes/Identifiers\n“The next three docs given by ids 5, 7 and 6 have the same phrasing but with the wrong identifier of #123, #1243 and #1233 respectively which lowers their BM25 score a bit but certainly higher than all the other documents. BM25 can detect a different attribute/identifier but the actual magnitude of this will depend on what the rest of the sentence looks like.”\n\nIf there is strong lexical overlap but only the identifier is different, there will be a small drop in the BM25 score.\nIf the rest of the sentence is NULL then the BM25 score is 0.\nIf there is very little overlap in the rest of the query and the doc.\n\nBM25 can identify attribute/identifier changes but is hard to put a threshold on this score.\nW.r.t embeddings two things are notable:\n\nThere are two forces at play here: the global semantic intent match and the identifier match.\nIn 8 and 11, there is negated intent whereas there is an identifier match.\nIn 5, 6 and 7 the general intent is correct but the identifier is incorrect.\nThe resulting score is a complex interaction of the two and the answer to “Can we identify negations or attribute changes with a threshold?” is heavily data-dependent.\n\nNote that the embedding scores for 5, 7 and 6 are some of the highest because attribute/identifier differences are something that embeddings struggle to detect."
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"objectID": "posts/hybrid-search-retrieval-bm25-modernbert.html#how-does-the-hybrid-score-and-rrf-fare",
|
| 46 |
+
"href": "posts/hybrid-search-retrieval-bm25-modernbert.html#how-does-the-hybrid-score-and-rrf-fare",
|
| 47 |
+
"title": "Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples",
|
| 48 |
+
"section": "How does the hybrid score and RRF fare?",
|
| 49 |
+
"text": "How does the hybrid score and RRF fare?\nAveraging out with a weighted hybrid score or RRF has no impact here. In example 1, record id 9 which is the closest match still does not rank highly in either of these scenarios.\nTo summarize the above for both approaches for the failure modes, it seems that embeddings seem to have the slight edge here.\n\n\n\nMethod\nNegation\nDirectionality\nAttribute/Identifier\n\n\n\n\nBM25\nN\nN\nN (hard to put a threshold)\n\n\nEmbedding\nSometimes\nN\nSometimes\n\n\n\nWe can’t eliminate all the issues, but we can hope to mitigate based on our understanding of the data."
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"objectID": "posts/hybrid-search-retrieval-bm25-modernbert.html#but-what-did-we-want-to-achieve-with-bm25",
|
| 53 |
+
"href": "posts/hybrid-search-retrieval-bm25-modernbert.html#but-what-did-we-want-to-achieve-with-bm25",
|
| 54 |
+
"title": "Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples",
|
| 55 |
+
"section": "But what did we want to achieve with BM25?",
|
| 56 |
+
"text": "But what did we want to achieve with BM25?\nWe are really looking to filter out matches that do not have the right attributes/identifiers we seek. BM25 looks like it could provide that, but does more than attribute matching.\n\nExtract identifiers from query.\nPerform embedding based retrieval of query + docs to retrieve set M.\nFilter out docs where identifiers do not match in set M.\nUnfortunately, this does nothing to address the negation/directionality limitation."
|
| 57 |
},
|
| 58 |
{
|
| 59 |
"objectID": "posts/index.html",
|
| 60 |
"href": "posts/index.html",
|
| 61 |
"title": "Posts",
|
| 62 |
"section": "",
|
| 63 |
+
"text": "Hybrid Search Retrieval: Combining Semantic Search and BM25 Algorithm with Examples\n\n\nHow to combine ModernBERT embedding similarity with BM25, plus failure modes (negation and identifiers).\n\n\n\n\n\nMar 18, 2026\n\n\nRedis AI Research\n\n\n\n\n\n\n\nHello\n\n\nFirst Quarto post (template).\n\n\n\n\n\nMar 18, 2026\n\n\nRedis AI Research\n\n\n\n\n\nNo matching items"
|
| 64 |
},
|
| 65 |
{
|
| 66 |
"objectID": "posts/hello.html",
|
sitemap.xml
CHANGED
|
@@ -4,6 +4,10 @@
|
|
| 4 |
<loc>https://srijithrajamohan-ai-research-redis.static.hf.space/index.html</loc>
|
| 5 |
<lastmod>2026-03-18T23:10:24.034Z</lastmod>
|
| 6 |
</url>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
<url>
|
| 8 |
<loc>https://srijithrajamohan-ai-research-redis.static.hf.space/posts/index.html</loc>
|
| 9 |
<lastmod>2026-03-18T23:10:26.244Z</lastmod>
|
|
|
|
| 4 |
<loc>https://srijithrajamohan-ai-research-redis.static.hf.space/index.html</loc>
|
| 5 |
<lastmod>2026-03-18T23:10:24.034Z</lastmod>
|
| 6 |
</url>
|
| 7 |
+
<url>
|
| 8 |
+
<loc>https://srijithrajamohan-ai-research-redis.static.hf.space/posts/hybrid-search-retrieval-bm25-modernbert.html</loc>
|
| 9 |
+
<lastmod>2026-03-18T23:54:26.120Z</lastmod>
|
| 10 |
+
</url>
|
| 11 |
<url>
|
| 12 |
<loc>https://srijithrajamohan-ai-research-redis.static.hf.space/posts/index.html</loc>
|
| 13 |
<lastmod>2026-03-18T23:10:26.244Z</lastmod>
|