LanceDB: 全文检索与混合检索——向量 + 关键词的最佳组合
2026/6/12 3:18:51 网站建设 项目流程

一句话速读

  • FTS 索引:每次只能对单列建;PhraseQuery必须with_position=True
  • MatchQuery / PhraseQuery 的column参数是必需的
  • BoostQuery 签名BoostQuery(positive=..., negative=..., negative_boost=0.5)不是单一权重
  • 混合检索:Rust 核心execute_hybridtry_join!并行跑向量 + FTS
  • RRF 默认 K=60;归一化用 Min-Max(query/hybrid.rs
  • Python 层 10 种 Reranker:rrf / cross_encoder / cohere / jinaai / voyageai / openai / colbert / answerdotai / linear_combination / mrr

纯向量检索的尴尬

用户搜 “Python 异步编程”。

  • • 纯向量:可能返回 “JavaScript Promise” / “Go goroutine”——语义相近但关键词不对
  • • 纯关键词:可能返回 “Python 3.12 发布说明”——关键词命中但和"异步"无关

向量擅长语义,关键词擅长精确命中。两者合并就是混合检索(Hybrid Search)。


全文检索(Native FTS)

建索引(每次一列 + 位置信息)

import lancedbdb = lancedb.connect("/tmp/lancedb_fts_demo")# data 略,见测试脚本table = db.create_table("articles", data=data, mode="overwrite")# ⚠️ Native FTS 每次只能对一列建索引(多列要逐列建)# ⚠️ PhraseQuery 需要 with_position=True 才可用table.create_fts_index("content", with_position=True)table.create_fts_index("title", with_position=True)

旧的create_fts_index(["a","b"])多列语法在当前 Native FTS 下会直接报错。旧的基于tantivy-py的实现已被 Lance 原生 InvertedIndex 取代;传use_tantivy=True会直接告诉你改用 Native FTS。

基础检索

results = ( table.search("async", query_type="fts") .limit(10) .to_pandas())# 返回列含 _score(BM25 分数,越高越相关)

注意:FTS 返回_score(越高越相关),向量返回_distance(越小越相似),方向相反。

FTS 四种查询类型

from lancedb.query import MatchQuery, PhraseQuery, BooleanQuery, BoostQuery# 1) Match:单词匹配(BM25)—— column 必需mq = MatchQuery("async", column="content")results = table.search(mq, query_type="fts").limit(10).to_pandas()# 2) Phrase:短语精确匹配(词序一致)—— column 必需 + 索引 with_position=Truepq = PhraseQuery("Python async", column="content", slop=0)results = table.search(pq, query_type="fts").limit(10).to_pandas()# 3) Boolean:AND / OR 组合(& / |)boolean_q = MatchQuery("Python", "content") & MatchQuery("coroutine", "content")results = table.search(boolean_q, query_type="fts").limit(10).to_pandas()# 4) Boost:正向 + 负向查询(正例强化,负例压制)# ⚠️ BoostQuery 需要两个 FullTextQuery(positive / negative),不是 boost 权重bq = BoostQuery( positive=MatchQuery("Python", "content"), negative=MatchQuery("JavaScript", "content"), negative_boost=0.3, # 默认 0.5;越低,负例命中文档排名越靠后)results = table.search(bq, query_type="fts").limit(10).to_pandas()

MatchQuery还支持fuzziness(编辑距离)、operator(AND/OR)、prefix_lengthboost等参数;MultiMatchQuery在多列上并行匹配。

FTS 的两种实现(历史)

  • • 旧:Python 层tantivy-py(已弃用;create_fts_index(..., use_tantivy=True)现会报错)
  • :Rust 层 LanceInvertedIndex(默认,性能更好,无额外 Python 依赖)
// rust/lancedb/src/index/scalar.rs(实测)pub use lance_index::scalar::inverted::query::*;pub use lance_index::scalar::FullTextSearchQuery;

混合检索:核心机制

显式双路用法(无 Embedding Function 的表)

from lancedb.rerankers import RRFRerankerresults = ( table.search(query_type="hybrid") .vector(query_vec) # 显式向量分支 .text("async") # 显式 FTS 分支 .rerank(RRFReranker(K=60)) .limit(10) .to_pandas())# 返回列含 _relevance_score

自动双路用法(表绑定 Embedding Function 时)

# 前提:表用 LanceModel 定义了 SourceField + VectorFieldresults = ( table.search("Python 异步编程", query_type="hybrid") .rerank(RRFReranker(K=60)) .limit(10) .to_pandas())

Rust 核心执行流程

// rust/lancedb/src/query.rs 中 execute_hybrid 实测存在pub async fn execute_hybrid() { // 1. 两个子查询都保留 _rowid 用于后续合并 let fts_query = ...with_row_id(); let vector_query = ...with_row_id(); // 2. ⚡ 并行执行(try_join! 宏;延迟 ≈ max(两者),不是两者之和) let (fts_results, vec_results) = try_join!( fts_query.execute_with_options(opts.clone()), vector_query.inner_execute_with_options(opts) )?; // 3. 分数归一化(query/hybrid.rs 中 normalize_scores) vec_results = normalize_scores(vec_results, DIST_COL, None)?; // 含反转 fts_results = normalize_scores(fts_results, SCORE_COL, None)?; // 4. Reranker 合并(默认 RRFReranker) reranker.rerank_hybrid(query, vec_results, fts_results).await?; // 5. 应用 limit + 清理内部列}

关键点:并行执行 + 分数归一化 + Reranker

分数归一化:Min-Max

normalized = (score - min) / (max - min)# 对 _distance(越小越好)需反转:normalized = 1 - normalized# 当 max - min < 1e-5 时用 max 作分母防零除

归一化后,两路分数都在[0, 1],且统一为"越大越好"。

列名规范

来源语义
_distance向量分支距离(越小越好)
_scoreFTS 分支BM25 分数(越大越好)
_relevance_scoreReranker 输出最终排序分数(越大越好)

调试时保留所有分数:

RRFReranker(K=60, return_score="all")

实测返回列包含:_distance_score_relevance_score_rowid以及所有原始列。


RRF 重排详解

RRF(Reciprocal Rank Fusion)公式:

rrf_score(doc) = Σ_branch 1 / (rank_branch(doc) + K)

文档同时出现在向量 top-K 和 FTS top-K → 两个分数累加

// rust/lancedb/src/rerankers/rrf.rs (实测默认 K=60)let score = 1.0 / (rank as f32 + self.k);rrf_score_map.entry(result_id) .and_modify(|e| *e += score) .or_insert(score);

K=60 的由来:Cormack et al., SIGIR 2009。大多数场景无需调整。

为什么 RRF 有效?(K=1 简化示例)

文档向量排名FTS 排名向量 RRFFTS RRF总分最终排名
bar211/31/20.8331
foo11/200.5002
bean421/51/30.5333

bar同时在两路都靠前,累加后居首;foo虽然向量第一,但 FTS 没命中,总分不及。


10 种 Reranker(实测位于python/python/lancedb/rerankers/

Reranker外部 API延迟精度场景
RRFReranker(默认)~0首选,零成本
CrossEncoderReranker本地精排(sentence-transformers)
ColbertRerankerColBERT 多向量
AnswerdotaiRerankersanswerdotai/rerankers
LinearCombinationReranker~0向量分 × α + FTS 分 × (1-α)
MRRReranker~0Maximal Relevance Ranker
CohereReranker很高Cohere Rerank API
OpenaiReranker很高OpenAI
JinaReranker很高Jina
VoyageAIReranker很高VoyageAI

推荐升级路径:RRF → CrossEncoder / Colbert → 云 API。

Rust 内核目前仅实现 RRF(rust/lancedb/src/rerankers/rrf.rs),其他 9 种都在 Python 层。


预过滤 vs 后过滤

# prefilter=True(默认):先过滤再检索r1 = t.search(q).where("category = 'python'", prefilter=True).limit(10).to_pandas()# prefilter=False:先检索再过滤r2 = t.search(q).where("category = 'python'", prefilter=False).limit(10).to_pandas()
模式行为
prefilter(默认)先用标量索引 / 全表扫筛选,再在候选集做向量检索结果一定满足过滤条件候选集过小时检索效果差
postfilter先向量检索 top-K,再过滤检索范围大、召回高返回行数可能 < limit

规则:过滤选择性 > 10% → prefilter;过滤极度严格(<1%)→ postfilter。


完整混合检索实战

import lancedbimport numpy as npfrom lancedb.rerankers import RRFRerankerdb = lancedb.connect("/tmp/lancedb_hybrid_demo")rng = np.random.default_rng(42)articles = [ {"id": 1, "title": "Python asyncio", "content": "asyncio is Python async I/O framework", "category": "python", "vector": rng.random(128).tolist()}, {"id": 2, "title": "JavaScript async", "content": "Promise and async await for JavaScript", "category": "javascript", "vector": rng.random(128).tolist()}, {"id": 3, "title": "Python coroutine", "content": "Python coroutine from generator to async await", "category": "python", "vector": rng.random(128).tolist()}, {"id": 4, "title": "Go goroutine", "content": "goroutine is Go lightweight thread", "category": "go", "vector": rng.random(128).tolist()},]t = db.create_table("hybrid_articles", data=articles, mode="overwrite")# FTS 索引(每列一次,with_position 启用短语)t.create_fts_index("content", with_position=True)t.create_fts_index("title", with_position=True)# 向量索引(同步 SDK:扁平参数,见第 6 篇)t.create_index(metric="l2", num_partitions=2, num_sub_vectors=8, vector_column_name="vector", index_type="IVF_PQ")# 混合检索 + 前置过滤 + RRF 重排q_vec = rng.random(128).astype("float32")results = ( t.search(query_type="hybrid") .vector(q_vec) .text("async") .where("category = 'python'", prefilter=True) .rerank(RRFReranker(K=60)) .limit(5) .to_pandas())print(results[["id", "title", "_relevance_score"]])

学AI大模型的正确顺序,千万不要搞错了

🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!

有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!

就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇

学习路线:

✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经

以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!

我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询