数据库基础体系 · 第 64/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。
Weaviate 基础:Collection、Vectorizer、过滤、混合检索和多租户
Weaviate 是一个面向对象数据、全文检索和向量检索的数据库。它的核心使用方式不是先建立一张关系表,再为某一列额外创建向量索引,而是定义一个 Collection,为其中的对象配置属性、倒排索引、向量索引和向量化方式,然后通过相应的查询接口访问这些索引。
本文使用以下术语:
- Collection:对象的逻辑集合,类似关系数据库中的表,但同时承载属性、倒排索引和向量索引配置。
- Vectorizer:把文本或其他输入转换为向量的组件。
- 过滤:在检索过程中根据结构化条件缩小候选对象集合。
- 混合检索:把关键词检索和向量检索的结果融合。
- 多租户:在同一个 Collection 内按租户划分数据分片和访问上下文。
示例以 Weaviate Cloud 或启用了相应模块的 Weaviate 部署为边界,并使用 Python 客户端 v4 风格 API。具体的客户端小版本、向量化模块和云服务配置可能不同,正式项目应固定 weaviate-client 版本,并对照该版本的官方文档。
一、Collection 不只是“表名”
1. Collection 的职责
在 Weaviate 中,一个 Collection 通常包含以下部分:
Collection
├── 属性定义 properties
├── 属性索引 inverted index
├── 一个或多个向量索引 vector index
├── 向量化配置 vectorizer
├── 分片配置 sharding
├── 复制配置 replication
└── 可选的多租户配置 multi-tenancy
例如,可以定义一个 Article Collection:
Article
├── title: TEXT
├── body: TEXT
├── category: TEXT
├── published_at: DATE
└── word_count: INT
对于同一个对象,Weaviate 可以同时保存:
- 原始属性值,例如标题和正文;
- 属性的倒排索引,用于 BM25 和结构化过滤;
- 由正文生成的向量,用于近似近邻搜索;
- 其他元数据,例如 UUID、租户和时间戳。
因此,向量检索并不替代原始文本。RAG 系统最终需要引用原文、标题、来源和权限信息,这些内容仍应作为对象属性保存。
2. Collection 与旧版 Class 的关系
Weaviate 的早期 API 使用 Class 表示数据集合。较新的概念和客户端 API 使用 Collection。在理解现有资料或旧项目时,可以把:
旧术语:Class
新术语:Collection
视为同一层级的概念,但具体 API、配置字段和客户端写法可能不同。新项目应优先使用当前客户端文档中的 Collection API,而不是混用旧版 REST schema 写法。
3. 属性类型决定检索语义
常见属性类型包括:
TEXT:参与文本搜索和文本向量化;INT、NUMBER:支持数值比较过滤;DATE:支持时间比较过滤;BOOLEAN:支持布尔过滤;TEXT_ARRAY、数值数组等:支持数组相关查询;GEO_COORDINATES:用于地理位置相关能力;OBJECT、OBJECT_ARRAY:表示嵌套结构,但具体索引和查询能力要以部署版本为准。
文本属性还涉及 tokenization。tokenization 决定文本如何拆词,进而影响 BM25 和 Equal、Like 等过滤行为。比如,面向自然语言的文本字段通常使用适合分词的配置;邮箱、订单号和 SKU 这类标识符则往往需要避免被拆成不希望的词项。
这不是纯粹的展示配置。改变 tokenization 可能改变已有倒排索引的行为,通常需要重新导入或重建数据来验证效果。
二、Vectorizer:对象如何获得向量
1. Vectorizer 的输入和输出
Vectorizer 的作用可以抽象为:
其中:
- 是输入文本或多模态内容;
- 是向量维度;
- 是输入 的向量。
如果 Collection 的向量化配置指定了 title 和 body,插入对象时,Weaviate 会根据这些属性生成向量:
对象属性
title = "向量数据库简介"
body = "向量数据库用于近似近邻搜索..."
│
▼
Vectorizer
│
▼
向量 v ∈ R^d
│
▼
写入向量索引
查询时,near_text 也会执行类似过程:
查询文本 "如何进行近似近邻搜索"
│
▼
同一个 Vectorizer
│
▼
查询向量 q ∈ R^d
│
▼
在向量索引中查找接近 q 的对象
插入和查询必须使用语义兼容的向量化模型。若写入时使用模型 A、查询时使用模型 B,即使两个模型都输出相同维度,也不能假设向量空间相同。
2. 由 Weaviate 生成向量,还是由应用传入向量
常见有两种方式。
方式一:Weaviate 管理向量化
配置一个 Weaviate 支持的向量化模块,例如某个云端 embedding 服务或本地 embedding 服务:
import weaviate
from weaviate.classes.config import Configure, DataType, Property
client = weaviate.connect_to_local()
try:
articles = client.collections.create(
name="Article",
vector_config=Configure.Vectors.text2vec_openai(),
properties=[
Property(name="title", data_type=DataType.TEXT),
Property(name="body", data_type=DataType.TEXT),
Property(name="category", data_type=DataType.TEXT),
],
)
finally:
client.close()
这个示例的前提是:
- Weaviate 部署启用了对应的向量化集成;
- 运行环境提供该模块所需的凭据;
- 当前 Python 客户端支持
Configure.Vectors.text2vec_openai()这一配置形式。
如果模块未启用,Collection 创建或插入时会失败,常见表现是模块不可用、向量化配置无效或请求无法调用 embedding 服务。
方式二:应用自行生成向量
如果 embedding 由应用、专用模型服务或数据管道负责,可以把 Collection 配置为不自动向量化,并在写入和查询时显式提供向量。
这种方式的关键不是“把向量传进去”这么简单,而是应用必须保证:
- 所有对象使用相同的模型和预处理规则;
- 查询向量使用相同的模型;
- 向量维度一致;
- 模型升级时能够区分新旧向量,或重新生成全部向量。
如果模型从 切换到 ,则旧对象位于空间 ,新查询位于空间 。直接混用的结果通常不是一个明确的异常,而是检索质量悄然下降。
3. 多向量与命名向量
一个对象可能需要多个向量,例如:
- 标题向量;
- 正文向量;
- 图片向量;
- 不同语义目标对应的向量。
这类配置通常称为 named vectors。它解决的是“同一个对象存在多个向量空间”的问题,但也增加了查询时的明确性要求:查询必须指定使用哪个向量空间,不能把不同模型生成的向量当成一个空间进行比较。
生产环境中,命名向量适合以下场景:
用户查询 ──► title_vector
图片查询 ──► image_vector
长文语义查询 ──► body_vector
它不等于把多个向量简单拼接。拼接会改变维度和距离分布;命名向量则保留多个独立的索引和语义空间。
三、距离、向量索引和近似搜索
1. 距离与相似度
向量检索首先需要定义两个向量之间的距离或相似度。常见度量包括余弦距离、点积和欧氏距离。
以余弦相似度为例:
其中:
- 是查询向量;
- 是对象向量;
- 是点积;
- 和 是向量长度。
余弦相似度越大,方向越接近。若向量已经归一化,余弦相似度就等价于点积。
Weaviate 的查询结果通常可以返回距离或附加元数据。注意“距离越小越好”和“相似度越大越好”是两个相反方向的概念。调试混合检索时,必须确认客户端返回的是哪一种量。
2. 为什么需要近似近邻搜索
精确检索需要把查询向量与所有 个对象逐一比较。若每个向量维度为 ,单次查询的比较成本近似为:
对象数量增大后,这种方式会变慢。因此,向量数据库通常使用近似近邻索引。Weaviate 常见的向量索引实现是 HNSW。
HNSW 可以理解为多层图:
高层:少量节点,快速跨越远距离
中层:更多节点,逐步靠近目标区域
底层:完整或近完整的邻接图
一次查询的大致过程是:
- 从高层入口开始;
- 访问邻居;
- 如果邻居更接近查询向量,则移动到邻居;
- 在当前层无法继续改善时,下降到下一层;
- 在底层维护候选集;
- 返回近似最近邻。
ef、连接数等索引参数会影响召回率、内存和延迟。这里的“近似”意味着系统可能不返回真实的全局最近邻。提高搜索宽度通常有助于召回,但会增加查询成本。
3. 向量索引和倒排索引是两套索引
对于同一个 Collection:
正文属性 ──► 倒排索引 ──► BM25、文本过滤
对象向量 ──► 向量索引 ──► near_vector、near_text
这两类索引不能互相替代:
- 倒排索引擅长精确词项、词频和结构化条件;
- 向量索引擅长语义相近但词面不同的内容。
例如:
文档:如何降低数据库写放大
查询:减少存储系统的额外写入
词面可能差异很大,但语义相近,向量检索有优势。相反,查询:
错误码 0x80070005
通常更适合关键词或精确过滤,而不是依赖语义相似度。
四、过滤:先定义候选范围,再进行检索
1. 过滤表达式
过滤是对对象属性施加逻辑条件。例如:
category = "database"
AND word_count > 1000
可以写成:
from weaviate.classes.query import Filter
filters = (
Filter.by_property("category").equal("database")
& Filter.by_property("word_count").greater_than(1000)
)
常见操作包括:
equalnot_equalgreater_thangreater_or_equalless_thanless_or_equallikecontains_anycontains_allis_null
逻辑组合通常使用:
&表示 AND;|表示 OR;~或对应的否定 API 表示 NOT,具体写法应以客户端版本为准。
完整查询示例:
import weaviate
from weaviate.classes.query import Filter
client = weaviate.connect_to_local()
try:
articles = client.collections.get("Article")
filters = (
Filter.by_property("category").equal("database")
& Filter.by_property("word_count").greater_than(1000)
)
result = articles.query.near_text(
query="如何设计向量检索系统",
filters=filters,
limit=5,
return_metadata=["distance"],
)
for obj in result.objects:
print(obj.properties)
print(obj.metadata.distance)
finally:
client.close()
这个请求的语义是:
- 把查询文本向量化;
- 在满足
category = database且word_count > 1000的对象中进行向量检索; - 返回最多五个结果;
- 同时返回向量距离。
过滤条件不是返回后由 Python 再筛选。若把过滤放在应用层,可能发生:
数据库先返回 5 个语义结果
应用过滤后只剩 1 个
这会导致结果数量不足,也可能让系统错误地认为“数据库没有更多相关结果”。
2. Equal、Like 和“包含”不是同一回事
一个常见误解是把 Equal 当作自然语言搜索。
假设属性为:
title = "Weaviate 向量数据库基础"
那么:
Equal("Weaviate 向量数据库基础")表示属性值或其索引语义上的等值匹配;Like("*向量数据库*")表示通配模式匹配;- BM25 查询表示按词项相关性排序;
- 向量查询表示按向量空间中的距离排序。
它们的目标不同。字段的 tokenization 还会影响文本等值和通配行为,因此不能只看 Python 字符串是否相等来推断数据库中的过滤结果。
3. 过滤如何影响向量检索
理想语义是:
其中:
- 是过滤谓词;
- 只有满足 的对象才属于候选集合;
TopK再从候选集合中选择距离最近的对象。
实现上,数据库需要协调倒排索引与向量索引。不同版本和索引配置可能采用不同的预过滤、候选扩展或图遍历策略。工程上不应假设“过滤永远零成本”,尤其是以下情况:
- 过滤条件极其稀疏;
- 过滤条件选择性很高;
- 数据分布不均匀;
limit较大;- 分片数量较多。
如果过滤后只有很少对象,但向量索引的候选搜索仍然需要扩大范围,延迟和召回都可能发生变化。应使用真实数据评测,而不是只在无过滤数据集上测向量查询性能。
4. 过滤字段与权限边界
过滤可以表达:
tenant_id = "tenant-a"
AND document_status = "published"
AND user_group CONTAINS "finance"
但过滤本身不自动构成安全边界。若应用忘记添加权限过滤,数据库不会凭空知道当前用户只能访问哪些对象。
更安全的做法是:
- 租户隔离使用 Weaviate 的多租户上下文;
- 用户、组织或文档级授权由应用认证和授权层决定;
- 必须执行的权限条件在服务端统一注入;
- 不允许客户端直接提交任意过滤表达式来绕过权限逻辑。
五、混合检索:关键词和向量如何合并
1. 为什么需要混合检索
关键词检索和向量检索的失败模式不同。
关键词检索通常擅长:
- 专有名词;
- 产品型号;
- 错误码;
- 精确术语;
- 词频和字段权重。
向量检索通常擅长:
- 同义表达;
- 语义改写;
- 用户问题与文档表述不同;
- 不包含完全相同关键词的相关内容。
因此,对查询:
Kafka consumer rebalance 失败
关键词搜索可以准确抓住 Kafka、consumer 和 rebalance。对查询:
为什么消息消费组会重复分配分区
向量检索可能更容易找到关于 consumer group rebalance 的说明。
2. 混合检索的基本过程
混合检索不是把文本直接拼接成一个向量,而是分别执行两条检索路径:
查询文本
├──► BM25 / 倒排索引 ──► 关键词结果
└──► Vectorizer ───────► 向量结果
│
▼
分数融合
│
▼
最终排序
设某对象的 BM25 分数为 ,向量检索分数为 。由于两者的数值范围和含义不同,不能直接相加。需要先归一化:
然后使用权重 融合:
其中:
- 更偏向关键词;
- 更偏向向量;
- 表示两者权重相同。
Weaviate 的混合检索支持 BM25 与向量检索结果融合,并允许通过 alpha 调整倾向。具体分数融合方式和默认行为属于版本相关语义,应以当前版本文档为准;在较新的实现中,常见的是基于相对分数的融合,而不是简单使用原始 BM25 分数和距离。
3. 混合检索示例
import weaviate
from weaviate.classes.query import Filter
client = weaviate.connect_to_local()
try:
articles = client.collections.get("Article")
filters = Filter.by_property("category").equal("database")
result = articles.query.hybrid(
query="如何降低向量检索的误召回",
alpha=0.65,
filters=filters,
limit=5,
return_metadata=["score"],
)
for obj in result.objects:
print(obj.properties["title"])
print("hybrid score:", obj.metadata.score)
finally:
client.close()
每个参数的语义是:
query:同时作为 BM25 查询文本和向量化输入;alpha=0.65:整体上更偏向向量结果;filters:只在符合条件的对象中进行混合检索;limit=5:返回最终融合后的前五个对象;score:返回融合分数,不能直接解释为概率。
4. 一个完整的融合算例
假设 BM25 和向量检索分别返回以下对象:
| 对象 | BM25 原始分数 | 向量相似度 |
|---|---|---|
| A | 12 | 0.71 |
| B | 8 | 0.90 |
| C | 2 | 0.85 |
BM25 范围为 ,向量相似度范围为 。
归一化后:
| 对象 | BM25 归一化 | 向量归一化 |
|---|---|---|
| A | 1.00 | 0.00 |
| B | 0.60 | 1.00 |
| C | 0.00 | 0.74 |
取 :
最终排序为:
B > C > A
如果改为 :
排序变成:
A > B > C
这说明 alpha 不是“召回率开关”,而是改变两种检索证据在最终排序中的相对影响。
5. 混合检索的边界
混合检索不能解决以下问题:
- 文档根本没有被正确切分;
- embedding 模型不适合领域语料;
- BM25 字段配置错误;
- 权限过滤缺失;
- 文档内容过期;
- 最终结果缺少引用位置。
RAG 中常见的数据流是:
用户问题
└──► 混合检索
└──► 过滤、去重、重排
└──► 返回文档片段和来源
└──► 生成答案与引用
Weaviate 负责检索数据层;重排模型、上下文拼接、引用格式和答案生成通常属于应用或上层检索服务。混合检索结果的 score 不能直接当作事实置信度,也不能替代引用验证。
六、多租户:Collection 内的逻辑隔离和分片
1. 多租户解决什么问题
假设 SaaS 应用有多个客户:
tenant-a:客户 A 的文档
tenant-b:客户 B 的文档
tenant-c:客户 C 的文档
如果所有文档都放在一个普通 Collection 中,应用必须为每个查询附加:
tenant_id = 当前租户
这种方式容易因代码遗漏导致跨租户泄露。
启用 Weaviate 多租户后,租户成为 Collection 数据访问上下文的一部分:
Article / tenant-a
Article / tenant-b
Article / tenant-c
查询必须指定租户。数据库据此选择对应租户的数据分片,而不是在所有租户的数据上搜索后再依赖应用过滤。
多租户提供的是数据库层面的租户数据隔离和分片语义,不等于完整的身份认证与授权系统。应用仍需验证调用者是否有权访问指定租户。
2. 创建多租户 Collection
Python 客户端示例:
import weaviate
from weaviate.classes.config import (
Configure,
DataType,
Property,
)
client = weaviate.connect_to_local()
try:
documents = client.collections.create(
name="Document",
vector_config=Configure.Vectors.text2vec_openai(),
multi_tenancy_config=Configure.multi_tenancy(
enabled=True,
),
properties=[
Property(name="title", data_type=DataType.TEXT),
Property(name="body", data_type=DataType.TEXT),
Property(name="source_url", data_type=DataType.TEXT),
],
)
documents.tenants.create(
tenants=[
{"name": "tenant-a"},
{"name": "tenant-b"},
]
)
finally:
client.close()
这段代码包含两个不同的生命周期操作:
- 创建 Collection;
- 创建租户。
创建 Collection 不等于自动创建所有租户。租户通常需要显式管理。某些版本支持自动租户创建,但生产环境应谨慎使用,因为拼写错误或未经校验的租户名称可能导致产生意外分片。
3. 在租户上下文中写入和查询
import weaviate
client = weaviate.connect_to_local()
try:
tenant_a = client.collections.use(
"Document",
tenant="tenant-a",
)
tenant_a.data.insert(
properties={
"title": "租户 A 的向量检索说明",
"body": "这条文档只属于租户 A。",
"source_url": "https://example.com/a/vector-search",
}
)
result = tenant_a.query.hybrid(
query="向量检索",
alpha=0.5,
limit=3,
)
for obj in result.objects:
print(obj.properties)
finally:
client.close()
查询上下文已经绑定到 tenant-a,因此结果只来自这个租户。
如果使用不存在的租户,常见结果是请求失败,而不是自动返回空集合。错误可能发生在:
- 租户不存在;
- 租户处于不可读状态;
- Collection 未启用多租户,却传入了租户;
- Collection 启用了多租户,却没有传入租户。
这些错误应在应用层明确处理,不能把它们统一转换成“没有搜索结果”,否则会掩盖配置错误。
4. 多租户下的向量化流程
多租户不会改变向量化模型本身。数据流仍然是:
tenant-a 的对象
└──► 同一个 Vectorizer
└──► tenant-a 分片内的向量索引
tenant-b 的对象
└──► 同一个 Vectorizer
└──► tenant-b 分片内的向量索引
同一个 Collection 的租户通常共享 Collection 级别的 schema 和向量化配置。若不同租户需要完全不同的 embedding 模型、属性结构或距离度量,单纯多租户并不适合;应考虑不同 Collection,甚至不同数据库或部署。
5. 租户状态与数据生命周期
租户不只是一个字符串。实际部署中通常还需要管理租户状态,例如:
ACTIVE 可读写
INACTIVE 暂停访问
OFFLOADED 数据从在线资源中卸载,恢复前不可正常查询
具体状态名称和可用操作取决于 Weaviate 版本与部署形态。典型生命周期是:
创建租户
└──► 写入数据
└──► 正常查询
├──► 暂停
├──► 卸载以降低资源占用
└──► 恢复后继续查询
不能把“租户没有返回结果”和“租户被卸载”混为一谈。后者通常是状态错误或资源未恢复,应查看租户状态、服务日志和指标。
6. 多租户的分片取舍
多租户通常适合:
- 大量客户共享同一套 schema;
- 每个租户的数据需要逻辑隔离;
- 希望按租户扩缩容、迁移或管理生命周期;
- 查询天然带有租户边界。
但租户数量和数据规模会影响运维成本。若为每个极小客户都创建一个分片,可能带来:
- 分片元数据增多;
- 资源利用率下降;
- 租户状态管理复杂;
- 备份和恢复粒度变细。
反过来,如果把所有租户放在普通 Collection 中,仅靠 tenant_id 过滤,虽然分片更简单,但隔离依赖应用代码,错误代价更高。
七、写入、批处理和事务边界
1. 单对象写入不是跨对象事务
Weaviate 的对象写入接口不应被理解为关系数据库中的任意多对象事务。以下操作:
插入对象 A
插入对象 B
更新对象 C
删除对象 D
通常不是一个可以自动整体提交或整体回滚的 ACID 事务。中途网络断开时,可能出现:
A 已写入
B 已写入
C 尚未更新
D 尚未删除
因此,批量摄取必须具备:
- 幂等对象 ID;
- 可重试;
- 失败记录;
- 断点续传;
- 写入后校验;
- 必要时的补偿删除或更新。
批处理提高吞吐,但不等于事务。批请求部分失败时,应用必须检查每个对象的结果,而不能只判断 HTTP 请求是否成功。
2. 一个可靠的摄取状态机
可以把文档摄取抽象成:
SOURCE_READ
│
▼
CHUNKED
│
▼
EMBEDDED
│
▼
WRITTEN
│
▼
VERIFIED
失败路径包括:
embedding 超时 ──► 重试或 DEAD_LETTER
写入失败 ──► 按对象 ID 重试
校验失败 ──► 标记异常并重新生成向量
模型版本变化 ──► 建立新 Collection 或重建向量
如果使用 Weaviate 的远程 Vectorizer,写入时还依赖外部 embedding 服务的可用性。网络、配额、凭据和模型限流都可能成为写入路径的一部分。
3. Schema 变更和模型变更不是同一类操作
增加一个普通属性,通常是 schema 变更。改变以下内容则可能影响已有数据的检索语义:
- 向量化模型;
- 向量化的源属性;
- tokenization;
- 距离度量;
- 命名向量配置;
- 分片或复制策略。
尤其是 embedding 模型变更。一个安全的迁移流程通常是:
建立新 Collection
└──► 使用新模型重新摄取
└──► 离线评测
└──► 双读或切换流量
└──► 保留旧 Collection
直接覆盖旧向量会让新旧数据处于不同的向量空间,导致问题难以诊断。
八、生产环境中的故障表现与诊断路径
1. 查询无结果
可能原因并不只有“没有相关文档”:
- 过滤条件拼写错误;
- 文本属性的 tokenization 与预期不同;
- 查询使用了错误的租户;
- 租户处于不可查询状态;
- Collection 没有正确配置向量化;
- 使用了错误的命名向量;
- 写入成功但异步索引尚未完成;
- 过滤条件过于严格;
- 查询文本使用了与写入不同的模型。
诊断顺序应先验证边界,再验证召回:
1. 能否读取 Collection 配置?
2. 当前租户是否存在且可用?
3. 不加过滤能否 fetch 到对象?
4. 用精确对象 ID 能否读取?
5. 只做 BM25 能否返回?
6. 只做向量检索能否返回?
7. 最后再检查 hybrid 的 alpha 和过滤条件。
不要一开始就调整 alpha。如果对象根本没有写入,调整融合权重不会产生任何作用。
2. 向量检索结果看起来不相关
优先检查:
- 写入属性是否为空;
- Vectorizer 是否配置了预期的属性;
- 写入时是否发生了 embedding 调用失败;
- 查询和写入是否使用同一个模型;
- 文档切分是否过长或过短;
- 是否把标题、正文、代码和元数据混在了同一字段;
- 是否使用了错误的 named vector;
- 距离度量是否与模型预期一致。
一个常见反例是:向量化只配置了 title,但工程师以为正文也参与了向量生成。结果是正文内容对 near_text 几乎没有影响,而 BM25 可能仍然可以搜索正文,最终造成“关键词结果正常、向量结果异常”的表象。
3. 混合检索分数不能跨请求直接比较
相对分数融合通常依赖当前请求返回结果中的最小值和最大值。因此,某个请求中的 0.8 不一定比另一个请求中的 0.6 更“可信”。混合分数主要用于同一次查询内部排序,不应未经校准就当作跨查询阈值。
如果业务需要“低于某个相关性阈值就不回答”,应使用专门的评测数据校准阈值,并结合:
- BM25 分数;
- 向量距离;
- 混合排序名次;
- 重排分数;
- 是否存在足够的引用证据。
4. 多租户串数据的典型原因
多租户泄露通常不是 HNSW 本身把数据混在一起,而是应用访问上下文错误,例如:
# 错误风险:使用了未绑定租户的 Collection
documents = client.collections.get("Document")
documents.query.hybrid(query="...", limit=10)
在启用多租户的 Collection 中,必须显式选择租户:
documents = client.collections.use(
"Document",
tenant=current_tenant,
)
同时,current_tenant 不能直接信任请求体中的任意字符串。它应来自经过认证的会话、令牌或服务端授权结果。
九、如何选择 Collection、向量化和检索方式
1. 选择普通 Collection 还是多租户 Collection
可以按以下条件判断:
租户是否共享相同 schema 和模型?
├── 否:考虑不同 Collection
└── 是
│
每个租户是否需要数据库级访问上下文?
├── 是:启用多租户
└── 否:普通 Collection + 严格权限过滤
如果只是给文档增加一个 organization_id 字段,并不自动获得多租户隔离。它只是一个属性过滤条件。
2. 选择 Vectorizer 管理方式
| 场景 | 适合方式 |
|---|---|
| 希望快速构建系统 | 由 Weaviate 调用集成的 Vectorizer |
| 已有统一 embedding 服务 | 应用生成向量后写入 |
| 需要多个语义空间 | Named vectors |
| 模型经常变更 | 版本化 Collection 或显式记录模型版本 |
| 需要严格复现 | 固定模型、预处理、维度和距离度量 |
3. 选择检索方式
| 需求 | 主要方式 |
|---|---|
| 精确术语、错误码、型号 | BM25 |
| 同义表达和语义相似 | 向量检索 |
| 同时兼顾词面和语义 | Hybrid |
| 只搜索某类对象 | Filter |
| 按客户隔离数据 | Multi-tenancy |
| 对候选结果进一步精排 | Hybrid/向量召回后接重排 |
一个实际的 RAG 查询往往不是单次 near_text:
1. 根据租户选择 Collection 上下文
2. 注入权限过滤
3. 执行 hybrid 召回
4. 对结果去重、截断和重排
5. 返回正文、标题、来源 URL、文档 ID 和片段位置
6. 生成答案
7. 验证答案中的引用是否来自返回对象
Weaviate 负责其中的数据检索与索引部分;权限、重排、引用和生成仍需要应用层明确实现。
十、一个最小的端到端验证顺序
在正式接入 RAG 前,可以按下面顺序验证系统。假设已经创建 Article Collection,并配置好了可用的 Vectorizer。
import weaviate
from weaviate.classes.query import Filter
client = weaviate.connect_to_local()
try:
articles = client.collections.get("Article")
# 1. 写入一条最小对象
obj_id = articles.data.insert(
properties={
"title": "向量数据库中的混合检索",
"body": "混合检索同时使用关键词检索和向量检索。",
"category": "database",
"word_count": 1200,
}
)
print("inserted:", obj_id)
# 2. 按 ID 验证原始对象可读
fetched = articles.query.fetch_object_by_id(obj_id)
print("fetched:", fetched.properties if fetched else None)
# 3. 验证关键词路径
keyword_result = articles.query.bm25(
query="混合检索",
limit=3,
)
print("bm25 count:", len(keyword_result.objects))
# 4. 验证向量路径
vector_result = articles.query.near_text(
query="同时使用关键词和语义搜索",
limit=3,
return_metadata=["distance"],
)
print("vector count:", len(vector_result.objects))
# 5. 验证过滤
filtered_result = articles.query.hybrid(
query="检索",
alpha=0.5,
filters=(
Filter.by_property("category").equal("database")
& Filter.by_property("word_count").greater_than(1000)
),
limit=3,
return_metadata=["score"],
)
print("filtered hybrid count:", len(filtered_result.objects))
finally:
client.close()
每一步的目的不同:
- 插入:验证 schema、写入接口和向量化路径;
- 按 ID 读取:排除检索排序问题;
- BM25:验证倒排索引和文本属性;
- 向量检索:验证 embedding 和向量索引;
- 混合加过滤:验证多个组件之间的组合行为。
如果第 2 步失败,应先修复写入或连接问题;如果第 2 步成功而第 3 步失败,应检查文本属性和倒排索引;如果第 3 步成功而第 4 步失败,应检查 Vectorizer、模块凭据和向量配置;只有这些基础路径都正常后,才有意义评估 alpha、切分策略和重排模型。
Collection 定义了数据边界,Vectorizer 定义了语义空间,倒排索引和向量索引提供两种互补的召回路径,过滤负责缩小合法候选集,混合检索负责融合词面和语义证据,多租户则把租户上下文提升为数据库访问边界。理解这些组件之间的数据流和故障边界,才能把 Weaviate 从“能返回相似文本”的演示组件,正确地用作 RAG 的检索数据层。
系列导航与关联阅读
- 系列入口:数据库完整学习路线:从关系模型、事务索引到分布式与向量检索
- 上一篇:Qdrant 实战:Collection、Payload 过滤、HNSW、分片和快照
- 下一篇:Pinecone 托管向量库:Index、Namespace、Metadata 与容量成本
- 延伸:混合检索与 RAG 数据层:全文、向量、融合、重排和引用
- 延伸:向量数据库生产运维:摄取、版本、评测、备份、权限和成本
官方资料
本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论
0 条讨论