GEO /
Dense Retrieval Drops the Entities Your Customers Care About
TLDR: Vertical AI products live or die on returning the right document. On entity-heavy queries, fixed-α hybrid pays a 42-percentage-point R@1 tax for a 1-percentage-point R@10 gain. Per-query α removes the tax. Run the diagnostic. Three failed query cards — legal, clinical, financial — showing dense returning plausible-but-wrong results while BM25 surfaces the correct documents. Your customer pays you because you return the right opinion, the right citation, the right drug name, the right paragraph. Close enough is a churn event. This is what's actually going wrong on the queries that miss, and where the fix lives. The structural problem: Dense embeddings compress meaning into a fixed-dimensional vector. That's the point — it's how dense retrieval finds documents that share meaning with the query without sharing words. It's also why dense drops surface forms. The embedding doesn't preserve §1983 vs §1981. It doesn't preserve apixaban vs rivaroxaban. It doesn't preserve XBI vs biotech ETFs in general. For general-purpose retrieval, that's fine. For your domain, where the answer hinges on the specific entity, it's why your demo fails on the third query. Here's the same pattern in three verticals. Legal — statute numbers and case citations: A user asks, Controlling Ninth Circuit precedent for §1983 claims against municipal employees in cases citing Connick v. Thompson as a defense. Three lexical anchors: §1983, the circuit, the cited case. Dense compresses all three into adjacent neighborhoods. §1983 lives near §1981, §1985, and the rest of the civil-rights canon. Connick v. Thompson sits next to Monell and the qualified-immunity line. What dense returns is conceptually related to what the user needs and usually isn't what the user needs. BM25 scores on surface-form match. §1983, Ninth Circuit, and Connick are literal strings in the inverted index. They stay distinct. A reranker layered on top of dense can't fix this — if the right opinion isn't in the dense candidate set, there's nothing for the reranker to reshuffle. The ceiling is upstream of the reranker. Clinical — drug names and subgroup thresholds: A clinician asks, Apixaban PK and bleeding outcomes in non-valvular AF patients with CrCl 15–29 mL/min. The evidence here is narrow. ARISTOTLE excluded CrCl < 25. AVERROES had limited severe-renal representation. The papers the clinician needs are Wang et al. 2016 on apixaban PK in end-stage renal disease, the AXADIA-AFNET 8 hemodialysis trial, and the renal-gradient subgroup analyses of ARISTOTLE. Dense compresses apixaban into a direct oral anticoagulant region and CrCl 15–29 into a renal impairment region. The intersection is class-level: DOAC reviews, AF outcome studies, dose-adjustment guidance. BM25 keeps the strings distinct enough to surface the specific subgroup papers. There's a wrinkle: apixaban, Eliquis, DOAC, and factor Xa inhibitor are the same compound at different specificity levels. BM25 alone won't bridge the synonym set. A fusion that adapts the dense/BM25 weight per query handles both — lean BM25 when the query names the molecule, lean dense when the query names the class. A fixed weight can't do both on the same corpus. Financial — tickers and disclosure language: An analyst asks, Recent 10-K disclosures mentioning XBI exposure during the December 2024 biotech rate-cut window. XBI is the SPDR S&P Biotech ETF — a specific ticker some companies name explicitly as their biotech exposure vehicle. Dense compresses XBI into a generic biotech sector neighborhood. Filings with vague biotech-exposure language get pulled in. Filings that name XBI specifically can drop out of the top-K because the ticker contributes very little to a dense similarity score relative to surrounding sector discussion. BM25 keeps XBI distinct. The filings that name it in a risk-factor section or holdings table are exactly what BM25 surfaces first. The pattern: Your domain vocabulary contains entities whose surface form is the answer. Dense compresses meaning. It doesn't preserve surface forms. That's not a bug; it's the tradeoff dense made to be good at the things it's good at. Swapping encoders doesn't help, because every encoder makes the same tradeoff. Hybrid retrieval — dense plus BM25 — is the right family. Fixed-α hybrid is the wrong member of it, because no single weight serves both the lexical queries where BM25 wins and the semantic queries where dense does. Where the failure happens in the retrieval pipeline — the reranker can only reorder candidates the ANN retrieved, and the fix lives upstream at per-query α. The 42-percentage-point tax we measured on FEVER: FEVER is the BEIR fact-verification corpus — queries that hinge on specific entities. Across 10 encoders, with α swept on a 21-point grid, the best static α by R@10 sits at the balanced middle. At that fusion weight, R@10 lifts from 0.7628 dense to 0.7754, an increase of 1.3 percentage points. R@1 collapses from 0.6680 to 0.2623, a decrease of 42 percentage points. That's the cost of fixed-α on a corpus that looks like your workload. Every fixed-α hybrid stack pays it on the metric your product cares about most. Per-query α prediction, 0.40 ms and drop-in: R@1 0.6830 — beats dense, no tax. R@10 0.8548 — +9.2 percentage points. Mean rank 12.6 — vs dense at 33.4. On the Dasein-native variant, FEVER R@1 hits 0.8603 and mean rank drops to 1.9. The right answer is the second result, on average. NQ and the dense-dominant corpora are in the Dynamic Hybrid post. FEVER R@1 fixed-α vs Dynamic Hybrid, showing the 42-percentage-point gap that fixed-α leaves on the table. The encoder isn't the ceiling: A reasonable instinct is that maybe a better encoder fixes this. The data says it doesn't. Jina v3 on FEVER scores 23% NDCG@10 on its own. Wrap the same Jina v3 in the Dasein retrieval architecture and NDCG@10 hits 96%. Average rank of the right answer drops from 79 to 2.34. Same encoder. Different retrieval. If your team is on its third encoder fine-tune this year, the architecture is the lever they're missing. The stop-fine-tuning post has the data across three encoder families. Jina v3 on FEVER: 23% to 96% NDCG@10, average rank 79 to 2.34, in under 5 ms. Where reranking fits: Reranking is real and it's orthogonal. A reranker reshuffles what first-stage retrieval returned. If the right document isn't in the candidate set, the reranker has nothing to do. Per-query α changes what's in the candidate set. They compose — use per-query α to fix the floor, then layer reranking if you have the latency budget for it. Run the diagnostic this week: Ten queries your product missed last quarter. Run them through your current stack with logging — what dense returned, what the reranker promoted, where the right document landed. Then run the same ten through BM25 alone over your corpus. If the right document is in BM25 top-10 and missing from your dense top-10, you've got a fixed-α problem and per-query α is the fix. client.predict_dynamic(text, query_vector=...) returns the per-query α plus K cutoffs as three scalars in one HTTP call. Sub-millisecond. Once the diagnostic confirms the pattern on your corpus, integrating it in front of your current fusion is an afternoon.