第 6 章 检索四式:从语义到关键词 索引建好了,怎么查?Zvec 的 是整个 RAG 的"主力武器",它统合了四种检索方式。这一章把语义检索、标量过滤、全文检索三种逐一拆解(第四种"混合检索"留到第 7 章专讲),并给你一套通用的查询参数心法。 6.1 query() 的统一心智模型 无论哪种检索,都通过 执行,核心是构造 对象。记住这张"查询路线图",所有检索方式都是它的变体: ⚠️ 铁律:单条 Query 路线里, 和 互斥。同一个 不能同时设 和 ——想结合语义和关键词?那是"多路线 + 重排序"的活儿,正是第 7 章混合检索的主题。 四种检索方式与本章/下章对应关系: 检索方式 | 用什么 | 解决 RAG 什么需求 | 在哪讲 单向量检索 | | 语义相似(主力) | 6.
索引建好了,怎么查?Zvec 的
query()是整个 RAG 的"主力武器",它统合了四种检索方式。这一章把语义检索、标量过滤、全文检索三种逐一拆解(第四种"混合检索"留到第 7 章专讲),并给你一套通用的查询参数心法。
无论哪种检索,都通过 query() 执行,核心是构造 Query 对象。记住这张"查询路线图",所有检索方式都是它的变体:
collection.query( ... ) │ ┌───────────────────┼───────────────────┐ ▼ ▼ ▼ 向量检索路线 全文检索路线 (二者互斥) Query( Query( field_name=向量名, field_name=文本字段名, vector=查询向量, fts=Fts(match_string=...), [param=索引参数] [param=FtsQueryParam(...)] ) ) │ │ └─────────┬─────────┘ ▼ 再叠加可选的 filter(标量过滤)和 topk │ ▼ 返回 list[Doc]
⚠️ 铁律:单条 Query 路线里,
vector和fts互斥。同一个Query不能同时设vector和fts——想结合语义和关键词?那是"多路线 + 重排序"的活儿,正是第 7 章混合检索的主题。
四种检索方式与本章/下章对应关系:
| 检索方式 | 用什么 | 解决 RAG 什么需求 | 在哪讲 |
|---|---|---|---|
| 单向量检索 | Query(vector=...) |
语义相似(主力) | 6.2 |
| 标量过滤 | filter="..." |
按属性缩小范围 | 6.3 |
| 向量 + 过滤 | 两者叠加 | 语义检索 + 属性限定 | 6.4 |
| 全文检索 | Query(fts=...) |
关键词精确匹配 | 6.5 |
| 混合检索 | 多 Query + reranker | 召回率上限 | 第 7 章 |
最基础也最常用——用一个查询向量找最相似的 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, )
当检索要"按属性缩小范围"——只查某一年的、只查某类别的、只查有库存的——就用 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.99、name = '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.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 的纯语义检索,在企业场景几乎不可用。
当用户搜的是精确关键词——产品型号、报错码、人名、专有术语——稠密向量常常力不从心,这时全文检索(FTS)登场。它对文本字段分词建倒排,用 BM25 评分排序。
要让一个文本字段支持全文检索,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_params传jieba_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, )
FTS 的相关性排序靠 BM25:综合词频(TF,有衰减)、逆文档频率(IDF,越罕见权重越高)、文档长度(短文档同词频评分更高)。当查询含多词项时,用 WAND + Block-Max 算法预计算评分上界、跳过不可能进 top-k 的文档——大规模数据下也能高效返回。
💡 FTS 也能叠加 filter:和向量检索一样,全文检索可以加
filter="category = '技术'",先按属性圈范围再关键词检索。
无论哪种检索,query() 都接受这几个通用参数:
| 参数 | 作用 | RAG 建议 |
|---|---|---|
topk |
返回最相似的 N 条 | RAG 通常 5~20;太少漏召回,太多稀释信号 |
include_vector |
是否返回向量本体 | 默认 False(省内存),需要时再开 |
output_fields |
指定返回哪些标量字段 | RAG 一般只要 text,用这个省流量 |
filter |
标量过滤表达式 | 按属性缩小范围(企业 RAG 必备) |
⚠️
reranker参数只用于多向量检索,单路线查询别传(详见第 7 章)。
把这一章的知识拼起来,下面是一次"语义 + 过滤"的完整 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
source 字段,先不建索引,用 filter="source='x.md'" 测延迟;再用 create_index 补建倒排索引,对比速度差异(体会第 4 章索引的价值)。standard 和 jieba 建全文索引,搜"机器学习",对比召回结果——直观感受中文分词器的重要性。filter="source = '我的文档.md'"),这是多用户 RAG 的权限隔离原型。query() 统合四种检索,核心是构造 Query 对象;单路线内 vector 与 fts 互斥。topk / output_fields / include_vector / filter 控制返回内容与范围。但单一检索方式都有盲区——语义检索漏关键词、关键词检索漏语义。下一章把它们融合起来,这是 RAG 召回率再上一个台阶的关键。详见第 7 章。