在前面 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 是一个扁平字典,支持的类型如下表:
| 类型 | 例子 |
| 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 的用法后续介绍。