Chroma 教程

metadata:能放什么,不能放什么

🎉摘要:本文详细解析Chroma向量数据库中metadata的用法,包括与document、embedding的区别,支持的数据类型(str、int、float、bool及数组),限制条件(不支持嵌套、数组元素同类型等),以及如何通过where条件实现精准标签过滤,提升语义搜索效率。通过FAQ模拟数据示例,展示无过滤与分类过滤的召回效果对比,帮助读者快速掌握metadata在知识库分区、权限控制等场景中的实战技巧。

在前面 Record 长什么样子章节,给出了一个 Record,样子如下:

{
 'ids': [['q2']], 
 'embeddings': [array([[ 4.05045711e-02,  1.02685414e-01, -8.69980920e-03,
        ...隐藏...
        -9.42492625e-04,  1.17516676e-02,  4.61317971e-02,
         8.79870206e-02,  5.30446088e-03,  4.84681455e-04]])], 
 'documents': [['如何申请退款?在订单详情页点击申请退款']], 
 'uris': None, 
 'included': ['distances', 'documents', 'metadatas', 'embeddings'], 
 'data': None, 
 'metadatas': [[{'category': '售后'}]], 
 'distances': [[0.5679782629013062]]
}

其中,metadata 是绑在每一条向量 / 文本块上的标签,键值对字典,不属于正文 document,也不是向量 embedding。

和 document、embedding 比较:

  • document = 正文内容(一段话)

  • embedding = 这段话转换成的向量,用来做语义相似度检索

  • metadata = 给这段话贴的标签,比如分类、来源、页码、权限、时间

为什么要给记录贴标签呢?

主要用途是在进行语义搜索时,圈定一个范围,仅对范围内的记录做语义搜索。

向量本身很难区分“这是售后文档还是物流文档”,语义容易混淆,标签过滤是硬条件,精准可靠。

拿你之前 FAQ 例子:  

metadatas=[
    {"category":"物流"},
    {"category":"售后"},
]

查询时添加如下条件:

where={"category":"售后"}

此时,只在“category = 售后”的数据里,去做相似度搜索,物流的数据直接跳过,不会被召回。

Chroma 还支持:等于、大于小于、数组包含、多条件组合。

同一个 collection 里放多种业务数据,用 metadata 做逻辑分区,不用拆很多 collection。

metadata 支持的类型

metadata 是一个扁平字典,支持的类型如下表:

类型例子
str{"author": "李白"}
int{"year": 2024}
float{"score": 0.87}
bool{"published": True}
str 数组{"tags": ["a", "b"]}
int 数组{"scores": [1, 2, 3]}
float 数组{"ratings": [4.5, 3.8]}
bool 数组{"flags": [True, False]}

同时,metadata 也存在如下限制:

  • 不支持嵌套:不能 {"a": {"b": 1}},也不支持数组的数组。要表达层级关系就拍平成 {"author_name": "x", "author_id": 3}。

  • 数组元素必须同类型,空数组也不允许。

  • 值不能是 None。想表达“没有”,要么不放这个 key,要么放个空字符串/0。

我在项目里的习惯是把过滤要用的字段单独拍平放进去,把完整的业务对象留在主库:

col.add(
    ids=[str(doc.id)],
    documents=[chunk.text],
    metadatas=[{
        "doc_id": doc.id,          # 回主库用
        "kb_id": doc.kb_id,        # 多知识库隔离
        "category": doc.category,  # 过滤用
        "year": doc.published_at.year,
        "tags": doc.tags,          # 数组,用 $contains 过滤
        "is_public": True,
    }],
)

演示示例

下面通过一个简单示例演示 metadatas 的过滤用法:

import chromadb

# FAQ模拟数据
mock_datas = [
    { "id":"q1", "text":"订单付款后多久发货?一般 48 小时内出库", "kb_id":"e-commerce",
      "category":"物流", "published_at":{ "year":2025, "month":9, "day":17 }, "tags":["发货","订单","出库"], "is_public":True },
    { "id":"q2", "text":"如何申请退款?在订单详情页点击申请退款", "kb_id":"e-commerce",
      "category":"售后", "published_at":{ "year":2025, "month":9, "day":17 }, "tags":["退款", "订单"], "is_public":False },
    { "id":"q3", "text":"发票怎么开?在个人中心-发票管理里申请电子发票", "kb_id":"e-commerce",
      "category":"财务", "published_at":{ "year":2025, "month":12, "day":21 }, "tags":["发票"], "is_public":False },
    { "id":"q4", "text":"快递丢了怎么办?联系客服补发或全额退款", "kb_id":"e-commerce",
      "category":"售后", "published_at":{ "year":2025, "month":12, "day":24 }, "tags":["物流", "退款"], "is_public":False },
]

# 获取内存模式客户端
client = chromadb.EphemeralClient()
# 获取集合,如果集合不存在,则创建集合
col = client.get_or_create_collection("demo_col", embedding_function=None)
# 写入数据
for doc in mock_datas:
    col.add(
        ids=[str(doc["id"])],
        documents=[doc["text"]],
        metadatas=[{
            "doc_id": doc["id"],                    # 回主库用
            "kb_id": doc["kb_id"],                  # 多知识库隔离
            "category": doc["category"],            # 过滤用
            "year": doc["published_at"]["year"],    # 只能是扁平的标量,不能塞整个 dict
            "tags": doc["tags"],                    # 数组,用 $contains 过滤
            "is_public": doc["is_public"],
        }],
    )

# 查询验证
results = col.query(query_texts=["没有收到快递"])
print(f"ids={results["ids"]}\ndistances={results["distances"]}\ndocuments={results["documents"]}")

print("-" * 50)

results = col.query(query_texts=["没有收到快递"], where={"category": "物流"})
print(f"ids={results["ids"]}\ndistances={results["distances"]}\ndocuments={results["documents"]}")

运行上面示例,输出如下:

ids=[['q2', 'q4', 'q1', 'q3']]
distances=[[0.7911956310272217, 0.8020352125167847, 0.896552562713623, 1.0556845664978027]]
documents=[['如何申请退款?在订单详情页点击申请退款', '快递丢了怎么办?联系客服补发或全额退款', '订单付款后多久发货?一般 48 小时内出库', '发票怎么开?在个人中心-发票管理里申请电子发票']]
--------------------------------------------------
ids=[['q1']]
distances=[[0.896552562713623]]
documents=[['订单付款后多久发货?一般 48 小时内出库']]

可以看见,第一次没有使用 where 条件召回了 4 条数据。第二次查询,使用 where 条件将召回范围限制在分类为“物流”的数据中,仅召回一条数据。

到这里,应该明白 metadatas 的用法了吧,更多 where 和 metadatas 的用法后续介绍。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号