未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本文适合用于理解 Weaviate 的 Class、Property、Object、跨对象引用、命名向量与租户之间的建模关系,不是可按章节顺序直接运行的完整教程。跨对象引用示例仍将 ReferenceProperty 放入 properties、把 beacon 引用作为普通属性插入,并使用 data.reference_to 与 wvc.query.weaviate_uuid,不能作为可靠的 Python v4 引用操作流程;命名规则表也存在将 article 同时视为非法名称和不同 Class 的内部矛盾。SPLADE 稀疏向量与 RRF 双通道场景、租户容量及部分跨产品能力对比缺少版本条件或支持依据,不宜直接用于生产选型。运行其他示例需准备相应 OpenAI 向量化模块与凭据、自备 alpha_documents,并分别处理重复创建集合和示意向量。本次仅作全文结构审阅,未执行代码或进行外部事实核验;恢复耗时已明确需实测,独立租户分片也已明确不等于独占硬件。安装文档名称仅供查找,不提供可点击入口。 已取消无法确定目标的可点击引用「04-安装与部署」,保留原提及;Ambiguous link target: 04-安装与部署

Weaviate 核心概念详解

tip 读前导引

这篇文档的定位是 Weaviate 的概念操作手册。

如果你读完 01-Weaviate概述与核心定位 觉得"Weaviate 的思路挺有意思",又在 02-Weaviate架构深度解析 里理解了它的存储机制,那么现在到了一个关键节点——你需要搞清楚 Weaviate 的数据模型是怎么组织的,才能开始写出正确的代码。

Weaviate 的数据模型和 Milvus、Qdrant 有几个显著差异:Class 命名强制首字母大写、跨对象引用是一等公民、多租户是原生设计而非插件。这些不是细节,是影响你数据建模决策的核心机制。

目录


数据模型体系全览

在 Weaviate 里,数据组织从大到小的层级是:

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
graph TD
    Schema["Schema(全局 DDL)"]
    Class["Class(相当于 SQL Table)"]
    Property["Property(字段定义)"]
    Object["Object(一条数据记录)"]
    Scalar["标量数据(Properties 的值)"]
    Vector["Vector(一个或多个,通过 Named Vectors 区分)"]

    Schema --> Class
    Class --> Property
    Class --> Object
    Object --> Scalar
    Object --> Vector

    style Schema fill:#4A90D9,color:#fff
    style Class fill:#7B68EE,color:#fff
    style Object fill:#50C878,color:#fff
    style Vector fill:#FF8C00,color:#fff

还有两个贯穿全局的机制:

  • Cross-references:Object 之间的引用关系,类似外键,但可以跨 Class 穿透查询
  • Tenant:数据隔离单元,每个 Tenant 有独立的物理存储
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
graph TD
    Schema["Schema(全局 DDL)"]
    C1["Class: Article"]
    C2["Class: Author"]
    P1["Properties<br/>title: text<br/>content: text<br/>publishedDate: date"]
    P2["Properties<br/>name: text<br/>institution: text"]
    PR["Cross-reference<br/>Article.author → Author"]
    O1["Object<br/>uuid: abc-123<br/>title: '量子计算入门'<br/>vector: [0.1, 0.4, ...]"]
    O2["Object<br/>uuid: xyz-456<br/>name: '张伟'<br/>vector: [0.3, 0.2, ...]"]
    T1["Tenant: company_A"]
    T2["Tenant: company_B"]

    Schema --> C1
    Schema --> C2
    C1 --> P1
    C2 --> P2
    C1 -.->|"Cross-ref"| C2
    P1 --> PR
    C1 --> O1
    C2 --> O2
    C1 --> T1
    C1 --> T2

    style Schema fill:#4A90D9,color:#fff,stroke:#2c6fad
    style C1 fill:#7B68EE,color:#fff,stroke:#5a4ec9
    style C2 fill:#7B68EE,color:#fff,stroke:#5a4ec9
    style PR fill:#E67E22,color:#fff,stroke:#d35400
    style T1 fill:#2ECC71,color:#fff,stroke:#27ae60
    style T2 fill:#2ECC71,color:#fff,stroke:#27ae60
    style O1 fill:#95A5A6,color:#fff
    style O2 fill:#95A5A6,color:#fff

Class:基本容器

Class 是 Weaviate 中最基本的数据容器。如果你熟悉关系数据库,可以把它理解为一张 Table;如果你用过 Milvus,它对应 Collection;在 Qdrant 里,它是 Collection。

命名规范与约束

warning Class 名称必须首字母大写

这是 Weaviate 的强制约束,不是建议。article 会报错,Article 才是合法的。

背后的原因:Weaviate 用大写首字母区分 Class 名称和 Property 名称,GraphQL Schema 的生成逻辑依赖这个约定。

命名规则:

规则 正确示例 错误示例
首字母必须大写 Article、UserDocument article、userDocument
使用 PascalCase BlogPost、ProductItem blog_post、product-item
纯字母和数字,无连字符 Article2024 Article-2024、Article_2024
区分大小写 Article 和 article 是不同的 Class —

实践建议:Class 命名对应业务实体,用名词单数形式。Article 而不是 Articles,UserDocument 而不是 UserDocuments。

一个 Class 对应一个向量空间

这个特性很重要,直接影响数据建模的决策。

Weaviate 中,每个 Class 独立配置向量索引,实际索引组织还取决于分片、命名向量和所选索引类型。不同 Class 的 Object 在向量空间上是完全隔离的——Named Vectors 的 multi-target 搜索针对同一 Class 内的多个目标向量,并不是跨 Class 搜索;跨 Class 的统一检索需要分别查询并在应用层合并结果。

这意味着什么?

  • 如果你有"文章"和"商品"两种数据,应该建两个 Class(Article 和 Product),而不是用一个 Class 加一个"type"字段来区分
  • 如果你希望对"文章"和"用户提问"统一做语义搜索,可在向量表示兼容的前提下把它们放在同一个 Class 里;Named Vectors 多目标搜索仍限于同一个 Class,不能替代跨 Class 查询。

note 对比 Milvus 的 Collection

Milvus 的 Collection 和 Weaviate 的 Class 在语义上非常类似,都是"一个向量空间 + 一组字段"的组合。主要差异在于 Class 支持跨对象的 Cross-reference,而 Milvus Collection 没有原生的"外键"机制。


Property:字段定义

Property 是 Class 内的字段定义,相当于表的列。Weaviate 的 Property 有严格的数据类型系统。

数据类型速查表

类型 说明 示例值
text 字符串,支持全文搜索,可配置 tokenizer "量子计算入门"
text[] 字符串数组 ["AI", "物理", "量子"]
int 64 位整数 42、1704067200
int[] 整数数组 [1, 2, 3]
number 64 位浮点数 3.14、99.9
number[] 浮点数数组 [1.1, 2.2, 3.3]
boolean 布尔值 true、false
boolean[] 布尔数组 [true, false, true]
date RFC3339 格式的时间戳 "2024-03-11T10:00:00Z"
date[] 日期数组 —
uuid UUID 格式字符串 "550e8400-e29b-41d4-a716-446655440000"
uuid[] UUID 数组 —
blob Base64 编码的二进制数据 图片、文件等
geoCoordinates 地理坐标(经纬度) {"latitude": 39.9, "longitude": 116.4}
phoneNumber 国际电话号码(自动规范化) {"input": "+8613800138000"}
object 嵌套对象(v1.22+) {"key": "value"}
object[] 嵌套对象数组 —
* (Cross-reference) 跨 Class 的对象引用 见 Cross-references:跨对象引用

分词器(Tokenizer)配置

这是影响搜索行为最重要的 Property 配置,只有 text 和 text[] 类型才有 tokenizer。

Tokenizer 直接决定 BM25 关键词搜索的行为:Weaviate 在做混合搜索(Hybrid Search)时,BM25 部分会用你配置的 tokenizer 来分词和匹配。

可用的 tokenizer:

Tokenizer 分词方式 适用场景
word(默认) 按空白符和标点分词,统一转小写 英文内容,通用场景
whitespace 只按空白符分词,保留大小写 需要大小写精确匹配的场景(如代码标识符)
lowercase 按空白符分词,统一转小写 类似 word,但不去除标点
trigram 生成 3-gram 字符片段,支持模糊匹配 需要支持前缀/中缀搜索,部分错别字容忍
gse GSE 分词器,支持中文分词 中文关键词检索候选方案,需要相应 tokenizer 启动配置
kagome_kr 韩文分词 韩文内容(v1.27+)
kagome_ja 日文分词 日文内容(v1.28+)

配置示例:

import weaviate.classes as wvc

# 定义 Property 时指定 tokenizer
wvc.config.Property(
    name="title",
    data_type=wvc.config.DataType.TEXT,
    tokenization=wvc.config.Tokenization.WORD  # 英文默认选项
)

# 中文关键词检索可选用 GSE,需先启用相应 tokenizer 配置
wvc.config.Property(
    name="content_zh",
    data_type=wvc.config.DataType.TEXT,
    tokenization=wvc.config.Tokenization.GSE  # 中文分词
)

warning 中文关键词检索需选择适合的 Tokenizer

如果你的 text 字段存储中文内容,但 tokenizer 用默认的 word,BM25 关键词搜索几乎不可用——因为中文没有空格分隔词,word tokenizer 会把整句话当成一个词处理。

启用 GSE tokenizer 需要相应的 tokenizer 启动配置,不依赖 text-spellcheck 或 qna-transformers 模块;字段还需设置 tokenization 为 gse。

特殊数据类型详解

geoCoordinates(地理坐标)

专为地理搜索设计,支持 geoWithin 过滤——"找出距离某个坐标 X 公里以内的 Object"。

# Property 定义
wvc.config.Property(
    name="location",
    data_type=wvc.config.DataType.GEO_COORDINATES
)

# 写入时传入经纬度
{"location": {"latitude": 39.9042, "longitude": 116.4074}}

# 查询时做地理范围过滤
from weaviate.classes.query import Filter

results = collection.query.fetch_objects(
    filters=Filter.by_property("location").within_geo_range(
        coordinate=wvc.query.GeoCoordinate(latitude=39.9042, longitude=116.4074),
        distance=10_000  # 10 公里以内,单位:米
    )
)

phoneNumber(国际电话号码)

phoneNumber 类型会自动做号码规范化,把各种格式的电话号码统一成 E.164 格式(如 +8613800138000)。存入的值包含原始输入和规范化后的号码,方便统一查询。

blob(二进制数据)

blob 以 Base64 编码存储,通常用来存图片或其他二进制文件。注意:blob 不用于 BM25 文本检索,但可作为支持图片输入的向量化模块的数据来源。使用 multi2vec-clip 时,可将 Base64 图片存入 blob 类型字段,并在模块配置中将该字段指定为图片来源;生成的对象向量用于向量搜索。

date(时间类型)

日期必须是 RFC3339 格式(ISO 8601 的子集),带时区信息。

# 正确格式
"2024-03-11T10:30:00Z"           # UTC 时间
"2024-03-11T18:30:00+08:00"      # 北京时间

# 错误格式(会报错)
"2024-03-11"                      # 缺少时间和时区
"2024/03/11 10:30:00"            # 格式不对

支持时间范围过滤:

from weaviate.classes.query import Filter
import datetime

results = collection.query.fetch_objects(
    filters=(
        Filter.by_property("publishedDate").greater_than(
            datetime.datetime(2024, 1, 1, tzinfo=datetime.timezone.utc)
        )
        & Filter.by_property("publishedDate").less_than(
            datetime.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc)
        )
    )
)

Object:单条数据记录

Object 是 Weaviate 中的一条数据记录,相当于 Milvus 的 Entity 或 Qdrant 的 Point。

每个 Object 由三部分构成:

部分 说明 可否自定义
UUID 对象标识符,使用 UUID 格式,不限于 v4 可以自定义,默认自动生成
Properties 字段值,对应 Class 中定义的 Property 按 Schema 写入
Vector 向量(一个或多个) 由 Vectorizer 模块自动生成,或手动提供

还有一个系统字段 _additional,包含 Weaviate 自动维护的元信息:

# _additional 包含的信息
{
    "_additional": {
        "id": "550e8400-e29b-41d4-a716-446655440000",    # UUID
        "vector": [0.1, 0.4, ...],                        # 向量(需显式请求)
        "distance": 0.12,                                 # 与查询向量的距离(搜索时)
        "certainty": 0.94,                                # 相似度置信度(1 - distance/2)
        "score": 0.87,                                    # 混合搜索综合评分
        "creationTimeUnix": 1704067200000,                # 创建时间(ms 时间戳)
        "lastUpdateTimeUnix": 1704067200000,              # 最后更新时间
        "generate": {"singleResult": "生成的文本..."}     # Generative 模块的输出
    }
}

Vector:向量存储

每个 Object 默认有一个与之关联的向量。向量的生成方式有两种:

方式一:Vectorizer 模块自动生成

在 Class 配置中声明 Vectorizer,写入 Object 时 Weaviate 自动调用对应的外部服务生成向量。这是 Weaviate AI-Native 的核心体现。

方式二:手动提供向量

如果你已经有自己的向量(比如用了一个 Weaviate 不支持的特殊模型),可以在写入时直接传入向量值,跳过 Vectorizer。

# 手动提供向量
collection.data.insert(
    properties={
        "title": "量子计算入门",
        "content": "量子比特利用叠加态..."
    },
    vector=[0.1, 0.4, 0.2, ...]  # 直接提供向量,不通过 Vectorizer
)

note 设置为 `none` 的 Vectorizer

如果 Class 的 vectorizer 设置为 none,Weaviate 不会自动向量化,写入时必须手动提供向量,否则该 Object 没有向量,无法做向量搜索(只能做关键词搜索和标量过滤)。

关于 Named Vectors(一个 Object 多个向量)的详细说明,见后文 Named Vectors:多向量 章节。


Schema:整体的 DDL

Schema 是整个知识库中所有 Class 和 Property 定义的总和,相当于关系数据库的 DDL(Data Definition Language)。

你可以把 Schema 理解为一份"数据蓝图"——在往里面存数据之前,先告诉 Weaviate 数据长什么样。

Weaviate 支持自动 Schema 推断(autoSchema),在写入数据时如果 Class 或 Property 不存在,Weaviate 会自动根据数据类型创建。开发阶段很方便,但生产环境建议显式定义 Schema,避免类型推断出错。

import weaviate
import weaviate.classes as wvc

client = weaviate.connect_to_local()

# 显式创建 Schema(推荐生产环境)
collection = client.collections.create(
    name="Article",
    description="知识库文章集合",
    vectorizer_config=wvc.config.Configure.Vectorizer.text2vec_openai(),
    properties=[
        wvc.config.Property(
            name="title",
            data_type=wvc.config.DataType.TEXT,
            description="文章标题",
            tokenization=wvc.config.Tokenization.WORD
        ),
        wvc.config.Property(
            name="content",
            data_type=wvc.config.DataType.TEXT,
            description="文章正文",
            tokenization=wvc.config.Tokenization.WORD
        ),
        wvc.config.Property(
            name="publishedDate",
            data_type=wvc.config.DataType.DATE,
            description="发布日期"
        ),
        wvc.config.Property(
            name="viewCount",
            data_type=wvc.config.DataType.INT,
            description="阅读次数"
        ),
    ]
)

tip Schema 的修改限制

Weaviate 的 Schema 修改有一个重要约束:Property 的数据类型一旦创建,无法修改。你可以增加新的 Property,但无法修改已有 Property 的类型(比如把 int 改成 text)。

生产环境的 Schema 变更需要谨慎规划,类型搞错了只能删 Class 重建。


Cross-references:跨对象引用

这是 Weaviate 最独特的数据模型特性。理解它,你才能真正领会为什么 Weaviate 选择 GraphQL 而不是 REST API。

什么是 Cross-reference

Cross-reference 是 Weaviate 内置的"对象间引用"机制。一个 Class 的 Object 可以引用另一个 Class 的 Object,就像关系数据库的外键——但它更灵活,可以跨 Class 穿透查询。

举个实际例子:

  • Article Class 有 author 字段,类型是 cross-reference,指向 Author Class
  • 查询某篇文章时,可以直接要求 Weaviate "把这篇文章的作者信息也一起返回"
  • 这一步在 GraphQL 里是一次请求,不需要两次

note 为什么这很重要

Milvus 和 Qdrant 没有内置的 cross-reference 机制。如果你需要"查文章同时返回作者信息",你要:

  1. 先用向量搜索找到文章,拿到 author_id
  2. 再用 author_id 去查询作者数据(第二次请求)
  3. 在应用层拼装结果

Weaviate 的 Cross-reference 把这两步合并成一次查询,在数据建模阶段就声明好关联关系。

定义 Cross-reference Property

import weaviate
import weaviate.classes as wvc

client = weaviate.connect_to_local()

# 先创建 Author Class
client.collections.create(
    name="Author",
    properties=[
        wvc.config.Property(name="name", data_type=wvc.config.DataType.TEXT),
        wvc.config.Property(name="institution", data_type=wvc.config.DataType.TEXT),
        wvc.config.Property(name="email", data_type=wvc.config.DataType.TEXT),
    ]
)

# 再创建 Article Class,包含指向 Author 的 cross-reference
client.collections.create(
    name="Article",
    vectorizer_config=wvc.config.Configure.Vectorizer.text2vec_openai(),
    properties=[
        wvc.config.Property(name="title", data_type=wvc.config.DataType.TEXT),
        wvc.config.Property(name="content", data_type=wvc.config.DataType.TEXT),
        wvc.config.Property(name="publishedDate", data_type=wvc.config.DataType.DATE),
        # Cross-reference Property:指向 Author Class
        wvc.config.ReferenceProperty(
            name="author",
            target_collection="Author",   # 引用的目标 Class
            description="文章的作者"
        )
    ]
)

warning Cross-reference 的顺序依赖

定义 cross-reference 时,目标 Class(Author)必须在源 Class(Article)之前创建。否则 Weaviate 无法验证引用目标是否存在。

插入时设置引用

from weaviate.util import generate_uuid5

# 第一步:插入 Author,使用确定性 UUID(后文详解)
author_uuid = generate_uuid5("author_zhang_wei")  # 从内容生成可重复 UUID

author_collection = client.collections.get("Author")
author_collection.data.insert(
    properties={
        "name": "张伟",
        "institution": "清华大学计算机系",
        "email": "zhangwei@tsinghua.edu.cn"
    },
    uuid=author_uuid   # 指定 UUID,方便后续引用
)

# 第二步:插入 Article,设置 cross-reference 指向 Author
article_collection = client.collections.get("Article")
article_uuid = generate_uuid5("article_quantum_computing_intro")

article_collection.data.insert(
    properties={
        "title": "量子计算入门:从比特到量子比特",
        "content": "量子计算利用量子力学的叠加态和纠缠特性...",
        "publishedDate": "2024-03-11T10:00:00Z",
        # cross-reference 通过 UUID 建立关联
        "author": [
            {"beacon": f"weaviate://localhost/Author/{author_uuid}"}
        ]
    },
    uuid=article_uuid
)

tip Beacon 格式

Cross-reference 使用 beacon 格式标识目标对象:weaviate://localhost/{ClassName}/{uuid}。

weaviate://localhost 是本地实例的固定前缀,云服务实例的格式是 weaviate://your-cluster-id。v4 Python 客户端对这个格式做了封装,可以直接传 UUID:

"author": author_collection.data.reference_to(author_uuid)

也可以在对象创建后再添加引用:

# 事后添加引用(适合批量建立关联的场景)
article_collection.data.reference_add(
    from_uuid=article_uuid,
    from_property="author",
    to=wvc.query.weaviate_uuid(author_uuid)
)

查询时穿透关联

这是 Cross-reference 真正体现价值的地方:

from weaviate.classes.query import QueryReference

article_collection = client.collections.get("Article")

# 向量搜索,同时返回关联的 Author 信息
results = article_collection.query.near_text(
    query="量子计算的最新进展",
    limit=5,
    return_references=[
        # 穿透 cross-reference,取到 Author 的字段
        QueryReference(
            link_on="author",
            return_properties=["name", "institution"]
        )
    ]
)

for article in results.objects:
    print(f"文章:{article.properties['title']}")
    # 遍历关联的 Author 对象
    if article.references and "author" in article.references:
        for author_ref in article.references["author"].objects:
            print(f"  作者:{author_ref.properties['name']}")
            print(f"  机构:{author_ref.properties['institution']}")

对应的原生 GraphQL 查询(方便理解底层逻辑):

{
  Get {
    Article(
      nearText: { concepts: ["量子计算的最新进展"] }
      limit: 5
    ) {
      title
      content
      publishedDate
      author {
        ... on Author {
          name
          institution
        }
      }
    }
  }
}

与 Milvus / Qdrant 的对比

能力 Weaviate Cross-reference Milvus Qdrant
内置外键机制 原生支持 不支持,需应用层处理 不支持,需应用层处理
单次查询穿透关联 支持(GraphQL 嵌套查询) 不支持,需多次请求 不支持,需多次请求
多对多关联 支持(一个 Object 可引用多个目标) 不支持 不支持
双向引用 需两边都定义 Property — —
引用对象的向量搜索 不支持(不能"通过 Author 搜 Article") — —

note Cross-reference 的局限

Cross-reference 是单向穿透的。你可以从 Article 查到它引用的 Author,但不能反过来"根据 Author 的属性做向量搜索并返回文章"。如果需要双向,需要在 Author 里也定义一个指向 Article 的 cross-reference(手动维护双向关联)。


Named Vectors:多向量

什么是 Named Vectors

Named Vectors 是 Weaviate v1.24+ 引入的特性,允许一个 Object 同时拥有多个命名向量。每个向量有自己的名字,可以独立配置 Vectorizer,查询时可以指定针对哪个向量做搜索。

在这之前,每个 Object 只有一个向量。这个限制在某些场景下很尴尬:

  • 一篇文章,标题很短但关键信息密集,正文很长但信息密度不一——用同一个 Vectorizer 生成同一个向量,会产生信息损失
  • 一个产品,希望用不同的 Embedding 模型分别生成"语义理解向量"和"关键词匹配向量"
  • 多语言场景:同一篇文档有中文版和英文版,希望分别生成向量,做跨语言检索

Named Vectors 把这些场景都覆盖了。

使用场景

场景一:标题向量 + 正文向量分开存

搜索标题时用 title_vector,搜索正文时用 content_vector,避免长正文稀释了标题的语义。

场景二:多模型向量

一个字段同时用 text-embedding-3-small(速度快,成本低)和 text-embedding-3-large(精度高,成本高)分别生成向量,A/B 测试哪个模型效果更好,不需要存两份数据。

场景三:混合检索双通道

一个向量用 Dense Embedding(语义搜索),另一个用 SPLADE 稀疏向量(关键词匹配),查询时对两路结果做 RRF 融合排序。

配置方式与查询

import weaviate
import weaviate.classes as wvc

client = weaviate.connect_to_local()

# 定义带 Named Vectors 的 Schema
collection = client.collections.create(
    name="BlogPost",
    # 不在这里设置全局 vectorizer,改为在每个 Named Vector 里单独配置
    vector_index_config=None,
    properties=[
        wvc.config.Property(name="title", data_type=wvc.config.DataType.TEXT),
        wvc.config.Property(name="body", data_type=wvc.config.DataType.TEXT),
        wvc.config.Property(name="category", data_type=wvc.config.DataType.TEXT),
    ],
    # Named Vectors 配置:每个向量独立命名,独立配置 Vectorizer
    vectorizer_config=[
        wvc.config.Configure.NamedVectors.text2vec_openai(
            name="title_vector",
            source_properties=["title"],           # 只用 title 字段生成向量
            model="text-embedding-3-small"
        ),
        wvc.config.Configure.NamedVectors.text2vec_openai(
            name="body_vector",
            source_properties=["body"],            # 只用 body 字段生成向量
            model="text-embedding-3-large"         # 正文用精度更高的模型
        ),
        wvc.config.Configure.NamedVectors.text2vec_openai(
            name="combined_vector",
            source_properties=["title", "body"],   # 标题+正文联合向量
            model="text-embedding-3-small"
        ),
    ]
)

# 写入数据(Weaviate 自动生成三个向量)
collection.data.insert(
    properties={
        "title": "从零开始学向量数据库",
        "body": "向量数据库是专门为 AI 应用设计的...",
        "category": "技术教程"
    }
    # 不需要手动传向量,Weaviate 根据 Named Vectors 配置自动生成
)

查询时指定目标向量:

# 针对标题向量做搜索(查"和这个标题相关的文章")
results = collection.query.near_text(
    query="入门级向量数据库教程",
    target_vector="title_vector",   # 指定搜索哪个向量
    limit=5
)

# 针对正文向量做搜索(查"正文内容相关的文章")
results = collection.query.near_text(
    query="HNSW 算法的内部实现原理",
    target_vector="body_vector",
    limit=5
)

# 多目标向量搜索:同时搜索 title 和 body,融合排序(v1.26+)
from weaviate.classes.query import TargetVectors

results = collection.query.near_text(
    query="向量数据库性能优化",
    target_vector=TargetVectors.manual_weights({
        "title_vector": 0.3,    # 标题匹配权重 30%
        "body_vector": 0.7      # 正文匹配权重 70%
    }),
    limit=5
)

tip Named Vectors 和性能

每个 Named Vector 可独立配置向量索引。增加命名向量会增加向量与索引开销,但总存储和内存还取决于维度、索引类型、压缩及共享的对象数据;只有在各向量配置相近时,向量相关开销才可粗略按数量估算,不能把总内存直接视为 3 倍。不要滥用,按实际业务需要配置。


Tenant:多租户

原生多租户的设计哲学

多租户(Multi-tenancy)是 SaaS 产品的核心需求:不同客户的数据要完全隔离,A 客户的查询不能访问 B 客户的数据,也不能影响 B 客户的查询性能。

Weaviate 的多租户是原生设计,从 v1.20 开始 GA。启用多租户的 Class,每个 Tenant 的数据存储在独立的 shard(分片)里,物理隔离。

相比之下:

  • Milvus 的多租户推荐方案是每个客户一个 Collection(完全隔离,但管理成本高)或者用 Partition 隔离(同一个索引,逻辑隔离,互相影响)
  • Qdrant 推荐每个客户一个 Collection,没有内置的"单 Collection 多租户"机制

Weaviate 的方案:一个 Class,N 个 Tenant,每个 Tenant 独立 shard。这样做有三个好处:

  1. Schema 统一管理:所有 Tenant 共用同一个 Schema,字段改动一次生效,不需要对每个 Collection 单独执行
  2. 分片隔离:不同 Tenant 使用独立 shard,但部署在共享节点时仍可能竞争 CPU、内存和 I/O;不能据此保证租户间性能互不影响。
  3. 成本灵活:每个 Tenant 可以独立设置活跃/休眠状态,不活跃的 Tenant 不占用内存

Tenant 状态管理

Weaviate v1.21+ 引入了 Tenant 状态管理,v1.26+ 新增 FROZEN 状态:

状态 说明 内存占用 是否可查询
HOT 活跃状态,索引在内存中 高 是
COLD 休眠状态,索引卸载到磁盘 极低 需要先激活
FROZEN 归档状态,数据移至对象存储(v1.26+) 几乎为零 需要先解冻(耗时较长)

状态转换路径:

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
stateDiagram-v2
    [*] --> HOT : 创建 Tenant(默认 HOT)
    HOT --> COLD : 手动设置 / 自动策略
    COLD --> HOT : 手动激活
    COLD --> FROZEN : 手动归档(v1.26+)
    FROZEN --> COLD : 手动解冻
    HOT --> [*] : 删除 Tenant

    note right of HOT
        索引加载在内存
        可直接读写
    end note

    note right of COLD
        索引在磁盘
        激活加载耗时取决于数据与部署条件
    end note

    note right of FROZEN
        数据在对象存储(S3 等)
        解冻耗时取决于数据规模与存储条件
        存储成本取决于对象存储与恢复策略
    end note

示例 SaaS 成本优化策略:可假设将 30 天以上未活跃的用户 Tenant 设为 COLD,将 180 天以上未活跃的设为 FROZEN,并由应用或运维策略实现状态管理。用户再次访问时先检测并恢复状态,可在 UI 上显示"正在加载您的数据...";恢复耗时需按数据规模、存储和部署条件测量,不能固定承诺为 10-30 秒。

与 Milvus Partition 的对比

维度 Weaviate Tenant Milvus Partition Milvus (每客户一 Collection)
隔离级别 物理隔离(独立 shard) 逻辑隔离(共享索引) 物理隔离
Schema 管理 统一(所有 Tenant 共用) 统一(同 Collection) 分散(每个 Collection 单独管理)
性能互相影响 独立 shard,但共享节点仍可能竞争资源 共享部署资源时可能互相影响 共享部署资源时仍可能互相影响
冷热管理 原生支持(HOT/COLD/FROZEN) 不支持 不支持
租户数量扩展 良好(数万 Tenant 可行) 有限(分区上限 1024) 有限(Collection 数量大了管理复杂)
Schema 变更 一次变更所有 Tenant 生效 一次变更即可 需要对每个 Collection 分别执行

代码示例:完整多租户流程

import weaviate
import weaviate.classes as wvc

client = weaviate.connect_to_local()

# Step 1:创建支持多租户的 Class
collection = client.collections.create(
    name="UserDocument",
    vectorizer_config=wvc.config.Configure.Vectorizer.text2vec_openai(),
    multi_tenancy_config=wvc.config.Configure.multi_tenancy(
        enabled=True,            # 开启多租户
        auto_tenant_creation=False  # 不自动创建 Tenant(推荐显式管理)
    ),
    properties=[
        wvc.config.Property(name="title", data_type=wvc.config.DataType.TEXT),
        wvc.config.Property(name="content", data_type=wvc.config.DataType.TEXT),
        wvc.config.Property(name="fileType", data_type=wvc.config.DataType.TEXT),
    ]
)

# Step 2:创建 Tenant(可以批量)
collection = client.collections.get("UserDocument")
collection.tenants.create(
    tenants=[
        wvc.tenants.Tenant(name="company_alpha"),    # 企业客户 A
        wvc.tenants.Tenant(name="company_beta"),     # 企业客户 B
        wvc.tenants.Tenant(name="company_gamma"),    # 企业客户 C
    ]
)

# Step 3:向指定 Tenant 写入数据
# 关键:获取带 Tenant 上下文的 collection 对象
alpha_collection = collection.with_tenant("company_alpha")

alpha_collection.data.insert(
    properties={
        "title": "Alpha 公司季度财报",
        "content": "本季度营收增长 30%...",
        "fileType": "report"
    }
)

# 批量写入
with alpha_collection.batch.dynamic() as batch:
    for doc in alpha_documents:
        batch.add_object(properties=doc)

# Step 4:查询时指定 Tenant(数据完全隔离)
alpha_results = alpha_collection.query.near_text(
    query="财务表现",
    limit=5
)

# beta 的查询完全独立,不会返回 alpha 的数据
beta_collection = collection.with_tenant("company_beta")
beta_results = beta_collection.query.near_text(
    query="技术文档",
    limit=5
)

# Step 5:Tenant 冷热管理
# 将不活跃的 Tenant 设为 COLD 状态(释放内存)
collection.tenants.update(
    tenants=[
        wvc.tenants.Tenant(
            name="company_gamma",
            activity_status=wvc.tenants.TenantActivityStatus.COLD
        )
    ]
)

# 需要访问时再激活
collection.tenants.update(
    tenants=[
        wvc.tenants.Tenant(
            name="company_gamma",
            activity_status=wvc.tenants.TenantActivityStatus.HOT
        )
    ]
)

client.close()

UUID 管理

每个 Weaviate Object 都有一个 UUID(Universally Unique Identifier),例如 550e8400-e29b-41d4-a716-446655440000。默认自动生成的 UUID 与通过 generate_uuid5() 生成的确定性 UUID 使用不同算法,不应将所有对象 UUID 限定为 v4。

自动生成 vs 自定义

方式 场景 代码
自动生成(默认) 数据没有自然 ID,或者 ID 无所谓 不传 uuid 参数,Weaviate 随机生成
自定义 UUID 数据有外部系统的 ID,需要保持关联 传入 uuid 参数
确定性 UUID 需要幂等写入(重复写入不产生重复数据) 用 generate_uuid5() 从内容生成

确定性 UUID:幂等写入的关键

这是实际工程中非常常见的需求,值得单独说清楚。

场景描述:你的数据同步管道每天从业务数据库把文章向量化写入 Weaviate。如果管道失败重跑,同一篇文章会被写入两次,产生重复 Object,搜索结果里出现重复内容。

解决方案:用 generate_uuid5() 从文章的唯一标识(比如 article_id)生成一个确定性 UUID。同一个输入,永远生成同一个 UUID。确定性 UUID 为重复写入提供一致的对象标识,但不使所有写入接口自动成为 upsert。单对象 insert 遇到已有 UUID 时不能当作自动更新;更新已有对象应使用适当的 replace 或 update 操作,并按所用接口处理不存在对象和重复写入。

from weaviate.util import generate_uuid5

# 从文章 ID 生成确定性 UUID
article_id = "article_20240311_001"  # 业务系统里的原始 ID
weaviate_uuid = generate_uuid5(article_id)
# 结果是固定的,比如:a3bf4c8e-5d72-5f3a-b128-e3e8f9a2c001
# 无论调用多少次,同一个 article_id 永远得到同一个 UUID

# 写入时使用这个 UUID
collection.data.insert(
    uuid=weaviate_uuid,
    properties={
        "title": "量子计算入门",
        "content": "...",
        "sourceArticleId": article_id  # 也可以存原始 ID 用于回查
    }
)

# 如果对象已存在,可显式调用 replace 完全替换;重复 insert 不等于自动更新
collection.data.replace(    # replace 是完全替换,update 是部分更新
    uuid=weaviate_uuid,
    properties={
        "title": "量子计算入门(更新版)",
        "content": "(更新后的正文)...",
        "sourceArticleId": article_id
    }
)

tip generate_uuid5 的工作原理

generate_uuid5() 是 UUID v5 算法:用 SHA-1 哈希算法对输入字符串进行哈希,输出 16 字节,格式化为 UUID 格式。同一个输入 → 同一个哈希 → 同一个 UUID。

这和 UUID v4(纯随机)本质不同。v4 每次调用都不一样,v5 是确定性的。

批量写入的幂等实现:

from weaviate.util import generate_uuid5

# 数据同步管道示例:用确定性 UUID 保持对象标识一致,仍需处理写入结果与重试
articles_to_sync = [
    {"id": "art_001", "title": "文章A", "content": "..."},
    {"id": "art_002", "title": "文章B", "content": "..."},
    # ...
]

collection = client.collections.get("Article")
with collection.batch.dynamic() as batch:
    for article in articles_to_sync:
        batch.add_object(
            uuid=generate_uuid5(article["id"]),  # 确定性 UUID;写入幂等性还取决于接口行为与错误处理
            properties={
                "title": article["title"],
                "content": article["content"],
                "sourceId": article["id"]
            }
        )
# 相同业务 ID 生成相同 UUID;重复批量写入的结果及失败处理应按所用接口确认

数据模型横向对比

了解 Weaviate 的概念之后,放到更大的视野里比较,有助于加深理解:

概念维度 Weaviate Milvus Qdrant
顶层容器 Class Collection Collection
命名约束 首字母必须大写 无特殊约束 无特殊约束
字段定义 Property(强类型) Field(强类型) Payload(弱类型,动态 key-value)
数据记录 Object Entity Point
主键机制 UUID(自动/手动/确定性) INT64 或 VARCHAR(primary field) UUID 或 unsigned int
向量存储 每 Object 一个向量(或多个 Named Vectors) 每 Entity 一个或多个向量 Field 每 Point 一个或多个 Named Vectors
跨对象引用 Cross-reference(原生支持,GraphQL 可穿透) 不支持 不支持
多租户 Tenant(独立 shard,物理隔离,HOT/COLD/FROZEN) Partition(逻辑隔离)或多 Collection(物理隔离) 推荐多 Collection
元数据过滤 Property 过滤(通过 Filter 对象) 标量字段 + 表达式 Payload 过滤(功能最强)
Schema 灵活性 可自动推断,但类型不可修改 强制预定义 Payload 无 Schema,随时添加字段
向量化 内置 Modules(Weaviate 的核心差异) 需要外部服务 需要外部服务

总结

Weaviate 的数据模型,本质上是一套面向 AI 应用的对象存储系统,在向量数据库的基础上加了三个独特的能力:

Cross-reference 让 Weaviate 具备了有限的"关联数据库"能力——对象之间可以建立引用关系,查询时可以跨 Class 穿透返回关联数据。这不是全功能的 SQL JOIN,但对于 AI 应用中常见的"文章+作者"、"产品+品牌"这类关联关系,已经够用了。

Named Vectors 让一个 Object 可以有多个视角的向量表示——标题有标题的向量,正文有正文的向量,不同语言版本有各自的向量。这在 RAG、多模态搜索场景里非常实用,不需要为同一份数据建多个 Collection。

原生 Tenant 把多租户从"架构设计问题"变成了"配置问题"。启用多租户后,可在同一集合内用独立租户分片组织数据并管理冷热状态,减少按客户创建集合的管理负担;生产环境仍需配置身份授权、租户路由和资源管理,独立分片不等于独占硬件。

理解这三个特性,你对 Weaviate 的核心设计哲学就基本摸透了。下一步是动手:

  • 04-安装与部署 — 把 Weaviate 跑起来,概念必须和代码结合才算真正理解
  • 02-Weaviate架构深度解析 — 如果想深入了解 Tenant shard 的物理存储机制、HNSW 索引的内存管理,回头看架构篇
  • 07B-多租户与数据隔离 — 多租户的生产实践,包括 Tenant 状态自动化管理、查询路由策略

#向量数据库 #Weaviate #核心概念 #Schema设计 #Cross-reference #Named-Vectors #多租户 #UUID