一句话核心概念Chroma 的where过滤 六种相似度算法等于给语义搜索装了导航仪——不仅要搜得像还要搜得准。元数据就是你的精准制导系统。 关键实操1. 元数据过滤让语义搜索指哪打哪# 05_metadata_filter.pyfromchromadbimportClient clientClient()collectionclient.get_or_create_collection(nameadvanced_search,metadata{hnsw:space:cosine},)# 塞一批带丰富元数据的文档——元数据就是你的过滤标签collection.add(documents[Python 3.12 引入了更友好的错误提示信息。,Python 3.12 的 type 语句简化了泛型写法。,Python 3.11 提升了 10-60% 的运行速度。,Rust 的所有权系统让内存安全无需 GC。,Rust 的 async/await 在 1.39 版本稳定。,Go 的 goroutine 让并发编程变得简单。,],metadatas[{lang:Python,version:3.12,category:语法},{lang:Python,version:3.12,category:类型系统},{lang:Python,version:3.11,category:性能},{lang:Rust,version:1.0,category:内存管理},{lang:Rust,version:1.39,category:异步},{lang:Go,version:1.0,category:并发},],ids[py_error,py_type,py_speed,rust_owner,rust_async,go_goroutine],)# 过滤运算符全家福 # $eq (等于), $ne (不等于), $gt (大于), $gte (), $lt, $lte# $in (在列表中), $nin (不在列表中)# $and, $or (组合条件)# 场景1只要 Python 3.12 的文档resultscollection.query(query_texts[新特性],n_results3,where{$and:[{lang:{$eq:Python}},{version:{$eq:3.12}},]},)print( Python 3.12 文档)fordoc,metainzip(results[documents][0],results[metadatas][0]):print(f [{meta[category]}]{doc})# 场景2要 Rust 或 Go 的内容多语言检索resultscollection.query(query_texts[并发编程],n_results4,where{lang:{$in:[Rust,Go]}},)print(\n Rust 或 Go 的并发)fordoc,metainzip(results[documents][0],results[metadatas][0]):print(f [{meta[lang]}]{doc})# 场景3排除性能类文档resultscollection.query(query_texts[编程语言特性],n_results5,where{category:{$ne:性能}},)print(f\n 排除性能类找到{len(results[ids][0])}条)2.where_document全文关键词过滤# where_document直接在原始文本上做关键词匹配 # 注意这是简单的字符串包含不是语义搜索# Chroma 内部用 sqlite 的 LIKE 实现resultscollection.query(query_texts[编程],n_results3,where_document{$contains:并发},# 文档中必须包含并发这个词)print(\n 文档包含并发的语义搜索结果)fordocinresults[documents][0]:print(f →{doc})3. 距离算法深度对比选错算法 搜索结果报废# 05_distance_metrics.py# Chroma 支持六种距离算法创建 Collection 时通过 metadata 指定# 默认是 l2欧几里得但 99% 的 NLP 场景你该用 cosinefromchromadbimportClientimportnumpyasnp clientClient()# 六种算法的通俗解释 algorithms{l2:欧几里得空间中两点直线距离。范围[0,∞)越小越像。没归一化时对长文本不友好,cosine:余弦相似度只看方向不看长度。范围[0,2]越小越像。NLP 首选管你文本多长,ip:内积向量投影乘积。范围(-∞,∞)越大越像。Chroma 内部转成距离dist -ip,}forname,descinalgorithms.items():print(f{name:8s}→{desc})# 实操同一个查询不同算法结果差异有多大 # 用默认 embeddingall-MiniLM-L6-v2384维docs[我喜欢吃苹果,# 食物苹果公司发布了新 iPhone,# 科技这个苹果又大又甜,# 食物]# 分别用 l2 和 cosine 建两个集合col_l2client.create_collection(namedemo_l2,metadata{hnsw:space:l2},)col_cosclient.create_collection(namedemo_cosine,metadata{hnsw:space:cosine},)col_l2.add(documentsdocs,ids[d1,d2,d3])col_cos.add(documentsdocs,ids[d1,d2,d3])query水果r_l2col_l2.query(query_texts[query],n_results3)r_coscol_cos.query(query_texts[query],n_results3)print(f\n 查询水果l2 排序{r_l2[ids][0]}→{r_l2[distances][0]})print(f 查询水果cosine排序{r_cos[ids][0]}→{r_cos[distances][0]})# 注意两种算法的距离值不可直接比较每个算法的近有自己的尺度uv run python 05_metadata_filter.py uv run python 05_distance_metrics.py 六种距离算法速查表算法hnsw:space值越小越像最佳场景一句话欧几里得l2默认✅物理空间、图像像尺子量距离长文本吃亏余弦cosine✅NLP/文本首选只看方向不看长度文本之王内积ip❌越大越像推荐系统向量投影配合归一化用还有l2_square、linf、hamming但它们在实际文本场景基本用不到。除非你知道自己在干什么否则cosine 一把梭。 避坑指南坑现象解法where 类型不匹配where{version: 3.12}查不到但明明有这条数据元数据值类型必须和写入时一致3.12是 float3.12是 str。用collection.get()看一眼实际存的类型where_document 不是语义搜索搜goroutine找不到但$contains:并发能找where_document是原始字符串LIKE匹配不是向量搜索。它是先语义缩小范围再关键词精确过滤的辅助手段改 hnsw:space 不生效改完 metadata 搜索结果没变化Collection 创建后hnsw:space就锁死了想换算法只能重建集合重新写入所有数据。这就是为什么生产环境必须一开始就想好用什么算法 Chroma 面试题与通关答案Q1Chroma 的where过滤是先过滤再搜还是先搜再过滤对性能有什么影响考点拆解向量数据库的查询优化器设计考察对混合查询底层执行计划的理解。通关答案Chroma 0.5.x 的执行策略是先过滤再搜索Pre-filtering。执行流程 1. sqlite3 解析 where 条件 → 过滤出符合条件的文档 ID 集合 2. HNSW 图索引中只在这些 ID 里做 ANN 搜索 3. 返回 top-k为什么是 Pre-filteringChroma 定位是小规模数据集几十万级全量扫元数据的开销可控Pre-filtering 保证结果数量精确先过滤后结果集不会因为 ANN 近似而漏数据反例Post-filtering先搜再过滤可能出现搜了 10 条过滤后只剩 2 条的情况n_results不可控性能影响无过滤 query → HNSW → top-k → 返回 最快 轻量过滤 query → sqlite → HNSW(子集) → top-k 略慢sqlite 开销小 复杂过滤 query → sqlite(大范围扫描) → HNSW → top-k 慢sqlite 成瓶颈最佳实践# ✅ 好where 字段建立索引Chroma 自动对元数据 key 建索引collection.query(query_texts[...],n_results10,where{lang:Python})# ❌ 坏用复杂嵌套条件缩小到 1-2 条文档相当于没搜collection.query(query_texts[...],n_results10,where{$and:[{lang:Python},{version:3.12},{category:语法}]})# 如果只有1条符合搜了个寂寞# ✅ 好过滤适度宽松让向量搜索有发挥空间collection.query(query_texts[...],n_results10,where{lang:Python})一句话总结Chroma 是 Pre-filtering 策略——先缩小候选集再语义精排。过滤太狠等于自废武功。Q2为什么 NLP 场景默认选 cosine 而不是 l2从数学角度解释。考点拆解向量空间模型的数学直觉区分距离和方向在语义中的含义。通关答案核心原因文本向量的长度和语义没有半毛钱关系。l2 距离 sqrt(Σ(ai - bi)²) ← 受向量长度影响大 cosine 距离 1 - (A·B)/(|A|×|B|) ← 只看夹角和长度无关具体例子两句话 A 苹果好吃 → Embedding 向量 va B 苹果苹果苹果苹果 → Embedding 向量 vb ≈ 2 × va重复内容向量更长 l2(A, B) ≈ 很大 → 判为不相似 ❌ cosine(A, B) ≈ 0 → 判为极相似 ✅为什么 Embedding 向量的长度会变化文本越长向量各维度的绝对值通常越大信息量累积效应不同主题的文本激活不同数量的维度导致向量的范数差异所以同一个 Embedding 模型下短查询和长文档的 l2 距离天然很大但它们的语义可能非常接近源码视角# Chroma 内部计算 cosine 距离简化版defcosine_distance(a,b):dot_productsum(ai*biforai,biinzip(a,b))norm_asum(ai**2foraiina)**0.5norm_bsum(bi**2forbiinb)**0.5return1.0-(dot_product/(norm_a*norm_b))# 完全同方向 → 0.0完全相反 → 2.0一句话总结l2 在意你说了多少cosine 在意你说的意思。语义搜索要的是后者——否则长文档永远排在短文档后面。Q3元数据metadata和文档内容documents在 Chroma 中的存储路径有什么本质区别这对查询性能意味着什么考点拆解Chroma 内部存储架构向量索引 vs 结构化存储的职责分离。通关答案documents文本内容 ↓ EmbeddingFunction ↓ 向量存在 HNSW 图索引中← 用于 query() 的语义搜索 metadatas元数据 ↓ 直接存 ↓ sqlite3 表 ← 用于 get() 的 where 过滤、精确查询、排序本质区别维度documents→ 向量metadatas存储位置HNSW 图索引内存 原始文本sqlite3sqlite3 表查询方式ANN 近似搜索有损B-Tree 精确查询无损索引结构图结构节点向量边近邻关系B-Tree按 key 有序速度O(log n) 图遍历O(log n) B-Tree 查找查询性能的启发# 场景1元数据过滤 语义搜索推荐collection.query(query_texts[如何优化性能],where{lang:Python},# sqlite 快速过滤 → HNSW 精排)# 执行sqlite O(log n) → HNSW O(log n)# 场景2纯元数据查询别用 Chromacollection.get(where{lang:Python,version:3.12})# 这本质上就是把 Chroma 当 SQLite 用暴殄天物# 这种场景直接用 sqlite3 或 PostgreSQL 更合适一句话总结向量HNSW管像不像元数据sqlite3管是不是。前者模糊但聪明后者精确但死板——混合查询才是 Chroma 的正确打开方式。