第 6 章 检索四式:从语义到关键词


文档摘要

第 6 章 检索四式:从语义到关键词 索引建好了,怎么查?Zvec 的 是整个 RAG 的"主力武器",它统合了四种检索方式。这一章把语义检索、标量过滤、全文检索三种逐一拆解(第四种"混合检索"留到第 7 章专讲),并给你一套通用的查询参数心法。 6.1 query() 的统一心智模型 无论哪种检索,都通过 执行,核心是构造 对象。记住这张"查询路线图",所有检索方式都是它的变体: ⚠️ 铁律:单条 Query 路线里, 和 互斥。同一个 不能同时设 和 ——想结合语义和关键词?那是"多路线 + 重排序"的活儿,正是第 7 章混合检索的主题。 四种检索方式与本章/下章对应关系: 检索方式 | 用什么 | 解决 RAG 什么需求 | 在哪讲 单向量检索 | | 语义相似(主力) | 6.

第 6 章 检索四式:从语义到关键词

索引建好了,怎么查?Zvec 的 query() 是整个 RAG 的"主力武器",它统合了四种检索方式。这一章把语义检索、标量过滤、全文检索三种逐一拆解(第四种"混合检索"留到第 7 章专讲),并给你一套通用的查询参数心法。

6.1 query() 的统一心智模型

无论哪种检索,都通过 query() 执行,核心是构造 Query 对象。记住这张"查询路线图",所有检索方式都是它的变体:

collection.query( ... ) │ ┌───────────────────┼───────────────────┐ ▼ ▼ ▼ 向量检索路线 全文检索路线 (二者互斥) Query( Query( field_name=向量名, field_name=文本字段名, vector=查询向量, fts=Fts(match_string=...), [param=索引参数] [param=FtsQueryParam(...)] ) ) │ │ └─────────┬─────────┘ ▼ 再叠加可选的 filter(标量过滤)和 topk │ ▼ 返回 list[Doc]

⚠️ 铁律:单条 Query 路线里,vectorfts 互斥。同一个 Query 不能同时设 vectorfts——想结合语义和关键词?那是"多路线 + 重排序"的活儿,正是第 7 章混合检索的主题。

四种检索方式与本章/下章对应关系:

检索方式 用什么 解决 RAG 什么需求 在哪讲
单向量检索 Query(vector=...) 语义相似(主力) 6.2
标量过滤 filter="..." 按属性缩小范围 6.3
向量 + 过滤 两者叠加 语义检索 + 属性限定 6.4
全文检索 Query(fts=...) 关键词精确匹配 6.5
混合检索 多 Query + reranker 召回率上限 第 7 章

6.2 第一式:单向量语义检索

最基础也最常用——用一个查询向量找最相似的 Document。topk 控制返回几条,结果按相似度排序:

# 稠密向量:问题 ──embed──► 查询向量 ──检索 result = collection.query( queries=zvec.Query( field_name="dense", vector=q_vec, # 由 Embedding 模型生成 ), topk=5, include_vector=False, # 默认不返回向量本体,省内存 ) # 结果:list[Doc],每个含 id / score / fields for doc in result: print(doc.id, round(doc.score, 4), doc.fields["text"][:30])

还可以用已有 Document 的 id 当查询源——复用库里已存的向量,省一次 Embedding 调用。这在"找相似文档""相关推荐"场景特别有用:

# 用 book_1 的向量去找和它最相似的 100 篇 result = collection.query( queries=zvec.Query( field_name="dense", id="book_1", # 复用该 Document 已存的 dense 向量 ), topk=100, include_vector=True, # 这次把向量也带回来 output_fields=["publish_year"], # 只要这个标量字段,别的不返回 )

💡 output_fields 是 RAG 的省流利器。默认会返回所有标量字段,但 RAG 通常只需要 text 喂给 LLM。用 output_fields=["text"] 只取需要的,减少传输和内存。include_vector 默认 False 也是同理——绝大多数时候你不需要把向量本身带回来。

稀疏向量检索写法完全一样,只是 vector 换成稀疏字典:

# 稀疏向量:{维度索引: 权重},由稀疏 Embedding 模型生成 result = collection.query( queries=zvec.Query( field_name="sparse", vector={42: 1.25, 1337: 0.8, 1999: 0.64}, ), topk=5, )

6.3 第二式:标量过滤(类 SQL WHERE)

当检索要"按属性缩小范围"——只查某一年的、只查某类别的、只查有库存的——就用 filter。它是一段类 SQL 的布尔表达式

# 这些都返回 list[Doc](不带 score,因为纯过滤无相似度概念) collection.query(filter="publish_year = 2024", topk=10) collection.query(filter="publish_year < 1999", topk=50) collection.query(filter="in_stock = true", topk=100) collection.query(filter="category CONTAIN_ANY('rag', 'vector-db')", topk=20) collection.query(filter="category CONTAIN_ALL('science', 'philosophy')", topk=10) # 逻辑组合:用 AND / OR / 括号 collection.query( filter="publish_year >= 2024 AND in_stock = true AND category CONTAIN_ANY('rag')", topk=30, output_fields=["summary"], )

过滤表达式速查

类别 运算符 示例
比较 < <= = != >= > price <= 29.99name = 'Michael'
空值 is null / is not null email is null
成员 in / not in status in ('active', 'pending')
数组成员 contain_any / contain_all / array_length tags contain_all ('bug','urgent')
字符串模式 like% 通配) name like 'Smart%'
逻辑 and / or(括号分组) a = 1 and (b = 2 or c = 3)

⚠️ 性能关键:过滤字段必须建倒排索引。已索引字段被高效搜索;未索引字段也能过滤,但会显著变慢(退化为逐条扫描)。这就是第 4 章反复强调"给常过滤字段建倒排索引"的原因。范围查询还要开 enable_range_optimization=True 才能真正快。

完整运算符细节见附录 B

6.4 第三式:向量 + 过滤(RAG 最常用组合)

把 6.2 和 6.3 叠起来,就是 RAG 里最常用的检索形态:先按语义找相似,同时用 filter 限定范围。这正是"在 2024 年的产品手册里,找和用户问题最相关的段落"的标准写法:

result = collection.query( queries=zvec.Query(field_name="dense", vector=q_vec), filter="publish_year >= 2024 AND source = 'manual_v2.md'", # 语义 + 范围 + 精确 topk=5, )
查询执行顺序(向量 + 过滤): 倒排索引按 filter 圈出候选 DocID 集合 │ ▼ 向量索引在该候选集合内做相似度检索(只对满足条件的向量算距离) │ ▼ 返回 top-k(既满足过滤条件、又最相似的 Doc)

💡 这个组合是"可控 RAG"的关键。比如多租户场景用 filter="tenant_id = 'xxx'" 做权限隔离;时效性场景用 filter="publish_year >= 2024" 只查最新;多产品场景用 filter="product = 'X-Pro2'" 限定产品线。没有 filter 的纯语义检索,在企业场景几乎不可用

6.5 第四式:全文检索(BM25 + 中文分词)

当用户搜的是精确关键词——产品型号、报错码、人名、专有术语——稠密向量常常力不从心,这时全文检索(FTS)登场。它对文本字段分词建倒排,用 BM25 评分排序。

定义 FTS 字段

要让一个文本字段支持全文检索,Schema 里给它配 FtsIndexParam,关键是选分词器

zvec.FieldSchema( name="content", data_type=zvec.DataType.STRING, index_param=zvec.FtsIndexParam( tokenizer_name="jieba", # 中文/中英混合必用 jieba;英文用 standard ), )
分词器 适用 说明
standard 英文/拉丁语系(默认) 按非字母数字拆分
whitespace 需保留 Token 内标点 仅按空白拆
jieba 中文/中英混合 基于 cppjieba,Python SDK 内置字典,开箱即用

⚠️ 中文千万别用 standard。standard 会把"机器学习"按非字母数字边界乱切,检索效果极差。中文场景一律 jieba。需要自定义词典时通过 extra_paramsjieba_dict_dir 或设环境变量 ZVEC_JIEBA_DICT_DIR

两种查询模式

FTS 通过 Query 里的 Fts 对象查询,有两种模式:

Match String(自然语言)——输入纯文本,自动分词,词项间默认 OR

from zvec.model.param.query import Fts, Query result = collection.query( queries=Query( field_name="content", fts=Fts(match_string="机器学习"), # 返回含"机器"或"学习"的文档,BM25 排序 ), topk=5, )

Query String(高级表达式)——支持布尔运算符、必需/排除词、精确短语:

result = collection.query( queries=Query( field_name="content", fts=Fts(query_string='+学习 -神经网络 "向量搜索"'), # 必须含"学习"、排除"神经网络"、匹配精确短语"向量搜索" ), topk=5, )
语法 含义 示例
term 匹配词项 vector
"phrase" 精确短语(词序+相邻) "machine learning"
+term / -term 必须含 / 必须不含 +vector -slow
AND / OR / NOT 布尔 vector AND search
(expr) 分组 (a OR b) AND c

💡 默认运算符:相邻裸词项默认用 OR 组合。想要更严的召回(每个词都得命中),用 param=FtsQueryParam(default_operator="AND")

result = collection.query( queries=Query( field_name="content", fts=Fts(match_string="机器学习"), param=zvec.FtsQueryParam(default_operator="AND"), # "机器"和"学习"都得有 ), topk=5, )

BM25 + WAND:为什么 FTS 又准又快

FTS 的相关性排序靠 BM25:综合词频(TF,有衰减)、逆文档频率(IDF,越罕见权重越高)、文档长度(短文档同词频评分更高)。当查询含多词项时,用 WAND + Block-Max 算法预计算评分上界、跳过不可能进 top-k 的文档——大规模数据下也能高效返回。

💡 FTS 也能叠加 filter:和向量检索一样,全文检索可以加 filter="category = '技术'",先按属性圈范围再关键词检索。

6.6 通用查询参数:四种方式都适用

无论哪种检索,query() 都接受这几个通用参数:

参数 作用 RAG 建议
topk 返回最相似的 N 条 RAG 通常 5~20;太少漏召回,太多稀释信号
include_vector 是否返回向量本体 默认 False(省内存),需要时再开
output_fields 指定返回哪些标量字段 RAG 一般只要 text,用这个省流量
filter 标量过滤表达式 按属性缩小范围(企业 RAG 必备)

⚠️ reranker 参数只用于多向量检索,单路线查询别传(详见第 7 章)。

6.7 端到端片段:一次完整的 RAG 检索

把这一章的知识拼起来,下面是一次"语义 + 过滤"的完整 RAG 检索片段(Embedding 接第 3 章,LLM 生成接第 9 章):

# ① 问题向量化(用入库时同一个 embed 函数) q_vec = embed(["Zvec 怎么做多进程共享?"])[0] # ② 语义检索 + 过滤:只在近两年的、来源为官方文档的切片里找 hits = kb.query( queries=zvec.Query(field_name="dense", vector=q_vec, param=zvec.HnswQueryParam(ef=400)), # 调大 ef 提召回 filter="publish_year >= 2025 AND source = 'zvec-docs'", topk=5, output_fields=["text", "source"], # 只要这两个字段 ) # ③ 拼上下文,交给 LLM(第 9 章展开) context = "\n---\n".join(h.fields["text"] for h in hits) prompt = f"根据以下资料回答问题。\n资料:\n{context}\n\n问题: Zvec 怎么做多进程共享?" # answer = llm.chat(prompt) ← 第 9 章接 LLM

6.8 动手实验

  1. 过滤性能对比:建一个 source 字段,先不建索引,用 filter="source='x.md'" 测延迟;再用 create_index 补建倒排索引,对比速度差异(体会第 4 章索引的价值)。
  2. FTS 分词器对比:同一段中文语料,分别用 standardjieba 建全文索引,搜"机器学习",对比召回结果——直观感受中文分词器的重要性。
  3. 向量+过滤组合:实现一个"只在我自己上传的文档里做语义检索"的查询(用 filter="source = '我的文档.md'"),这是多用户 RAG 的权限隔离原型。

本章小结

  • query() 统合四种检索,核心是构造 Query 对象;单路线内 vectorfts 互斥
  • 四式:单向量(语义)/ 标量过滤(属性)/ 向量+过滤(RAG 最常用)/ 全文检索(关键词 BM25)
  • 过滤字段必须建倒排索引否则慢;中文全文检索必须用 jieba 分词器。
  • 通用参数 topk / output_fields / include_vector / filter 控制返回内容与范围。
  • 给了一次完整 RAG 检索片段,串起了第 3~6 章。

但单一检索方式都有盲区——语义检索漏关键词、关键词检索漏语义。下一章把它们融合起来,这是 RAG 召回率再上一个台阶的关键。详见第 7 章


发布者: 作者: 转发
评论区 (0)
U