用 ChromaDB 做向量检索与 RAG:从嵌入到实战查询

2026-06-09 30 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

向量数据库正在从"概念热词"变成工程日常。ChromaDB 作为轻量级、Python-first 的向量存储方案,适合本地开发和小规模 RAG(检索增强生成)场景。但真正上手时,很多人会卡在:嵌入怎么选?余弦相似度到底在算什么?collection 查询的参数怎么配?这篇文章把这些问题拆开,配上可以直接跑的代码。

嵌入:文本变成数字的入口

嵌入(embedding)是把一段文本映射成高维浮点向量。维度通常在 384 到 1536 之间,取决于模型。两个向量越"近",语义越相似——这就是向量检索的基础。

ChromaDB 默认使用 all-MiniLM-L6-v2(Sentence Transformers 系列,384 维),无需额外配置即可开箱运行。你也可以换成 OpenAI 的 text-embedding-3-small 或其他模型,只需在创建 collection 时指定 embedding function。

关键认知:嵌入模型的选择直接决定检索质量。同一个句子,不同模型产出的向量不可混用——它们的空间结构完全不同。一旦切换模型,已有数据必须重新嵌入。

余弦相似度:ChromaDB 默认的距离度量

ChromaDB 默认使用余弦相似度(cosine similarity)作为距离度量。公式很简单:

cosine_similarity(A, B) = (A · B) / (||A|| × ||B||)

结果范围 [-1, 1],1 表示方向完全一致,0 表示无关,-1 表示相反。ChromaDB 内部存储时用的是余弦距离(1 - similarity),所以返回的 distance 越小越相似。

你可以在创建 collection 时切换度量方式:

import chromadb

client = chromadb.PersistentClient(path="./chroma_db")

# 默认 cosine,也可以选 l2(欧氏距离)或 ip(内积)
collection = client.create_collection(
    name="tech_docs",
    metadata={"hnsw:space": "cosine"}  # 可选 "l2" 或 "ip"
)

实际选择建议: - cosine:最通用,对向量长度不敏感,适合语义检索。 - l2:如果你关心绝对距离(比如地理位置向量),用欧氏距离。 - ip:内积,适合已经归一化的向量,计算更快。

Collection 查询:从添加到检索的完整流程

下面是一个可以直接跑的完整示例,覆盖添加文档、嵌入、查询三个步骤:

# pip install chromadb sentence-transformers

import chromadb

# 使用内存客户端,跑完即消失;换成 PersistentClient 可持久化
client = chromadb.Client()

collection = client.get_or_create_collection(
    name="python_tips",
    metadata={"hnsw:space": "cosine"}
)

# 添加文档——ChromaDB 会自动调用默认嵌入模型生成向量
docs = [
    "Python 的 list comprehension 比手动 for 循环更快更简洁",
    "用 functools.lru_cache 可以给纯函数加缓存,避免重复计算",
    "asyncio.gather 能并发运行多个协程,适合 IO 密集场景",
    "dataclass 替代手写 __init__,减少样板代码",
    "pathlib.Path 比 os.path 更面向对象,路径操作更直观",
]

ids = [f"tip_{i}" for i in range(len(docs))]

collection.add(
    documents=docs,
    ids=ids
)

# 查询:找和"缓存性能优化"最相关的两条文档
results = collection.query(
    query_texts=["如何优化函数的缓存和性能"],
    n_results=2
)

print("匹配文档:", results["documents"][0])
print("距离值:", results["distances"][0])
print("ID:", results["ids"][0])

运行后你会看到距离值(越小越相似)和对应的文档内容。query_texts 会被自动嵌入成向量,再和 collection 中已有向量做余弦距离计算。

几个实用参数:

参数 作用 典型值
n_results 返回结果数量 5-10
where 元数据过滤 {"category": "backend"}
where_document 文档内容过滤 {"$contains": "async"}
include 返回哪些字段 ["documents", "distances", "metadatas"]

元数据过滤是 ChromaDB 的一个亮点——它把向量检索和结构化过滤组合在一起,避免"只能靠向量猜"的局限:

# 先添加带元数据的文档
collection.add(
    documents=["FastAPI 的依赖注入系统非常灵活"],
    ids=["tip_5"],
    metadatas=[{"category": "web", "level": "advanced"}]
)

# 查询时同时过滤元数据
results = collection.query(
    query_texts=["Web 框架的高级用法"],
    n_results=3,
    where={"level": "advanced"}  # 只返回高级内容
)

RAG 实战:ChromaDB + LLM 的最小闭环

RAG 的核心流程:用户提问 → 从向量库检索相关片段 → 把片段塞进 prompt → LLM 生成回答。ChromaDB 负责中间的检索环节。

下面用 OpenAI API 拼一个最小 RAG:

# pip install openai chromadb

import chromadb
from openai import OpenAI

# 初始化
openai_client = OpenAI()  # 确保已设置 OPENAI_API_KEY 环境变量
chroma_client = chromadb.Client()
collection = chroma_client.get_or_create_collection("rag_demo")

# 知识库内容
knowledge = [
    "ChromaDB 默认使用 all-MiniLM-L6-v2 嵌入模型,输出 384 维向量",
    "ChromaDB 的 query 方法支持 where 和 where_document 两种过滤",
    "余弦距离 = 1 - 余弦相似度,值越小表示越相似",
    "PersistentClient 把数据存到本地磁盘,Client 只存内存",
]

collection.add(
    documents=knowledge,
    ids=[f"k_{i}" for i in range(len(knowledge))]
)

def rag_answer(question: str, n_contexts: int = 2) -> str:
    # 第一步:检索
    retrieved = collection.query(
        query_texts=[question],
        n_results=n_contexts
    )
    contexts = "\n".join(retrieved["documents"][0])

    # 第二步:构建 prompt
    prompt = f"""根据以下参考资料回答问题。如果资料中没有相关信息,明确说明。

参考资料:
{contexts}

问题:{question}
回答:"""

    # 第三步:调用 LLM
    response = openai_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.3
    )
    return response.choices[0].message.content

# 测试
answer = rag_answer("ChromaDB 用什么距离度量?")
print(answer)

这个闭环的关键决策点:

  1. n_contexts 选多少:2-5 条是常见范围。太少可能遗漏关键信息,太多会稀释 prompt、增加成本和延迟。
  2. temperature 设多少:RAG 场景建议 0.1-0.3,让模型紧贴检索到的内容,减少"自由发挥"。
  3. prompt 中的约束:加上"如果资料中没有相关信息,明确说明"能有效减少幻觉。

上手清单与边界提醒

在把 ChromaDB 用到真实项目前,确认这几项:

  • 嵌入一致性:同一个 collection 只用一种嵌入模型。切换模型 = 重新嵌入全部数据。
  • 数据规模:ChromaDB 适合万级到十万级文档。百万级以上考虑 Milvus 或 Pinecone。
  • 持久化选择:开发阶段用 Client()(内存),生产用 PersistentClient 或接入 Chroma 的 HTTP server 模式。
  • 元数据设计:提前规划 where 过滤要用到的字段,添加时就带上 metadatas,事后补很麻烦。
  • 距离度量:语义检索默认 cosine 就够用,不要无理由切换。

ChromaDB 的优势是轻和快——pip install 即用,Python API 直觉友好,适合从零到可演示的 RAG 原型。它的边界是规模和分布式能力。当你需要多节点部署、百万级向量、或严格的生产级 SLA时,就该评估更重的方案了。但在"先跑起来再说"的阶段,ChromaDB 是最短路径。


相关推荐