持久化与数据管理

数据存储位置

使用 Docker 启动时,数据存储在挂载的本地目录:

# 数据存在 ./chroma_data
docker run -d \
  -p 8000:8000 \
  -v $(pwd)/chroma_data:/chroma/chroma \
  chromadb/chroma

# 目录结构
chroma_data/
  └── chroma.sqlite3   # 索引和元数据
  └── xxxxxxxx-xxxx/   # 向量数据(每个 Collection 一个目录)

备份与恢复

# 备份:直接复制整个 chroma_data 目录
cp -r ./chroma_data ./chroma_data_backup_20250101

# 恢复:停止 Chroma,替换数据目录,重启
docker stop chromadb
cp -r ./chroma_data_backup_20250101 ./chroma_data
docker start chromadb

数据量统计

// 查看各 Collection 的数据量
const collections = await client.listCollections()

for (const name of collections) {
  const col = await client.getCollection({ name })
  const count = await col.count()
  console.log(`${name}: ${count} 条`)
}

增量更新策略

// 实际项目中,文档更新很常见,用 upsert 做增量更新
async function syncDocument(docId: string, content: string, metadata: object) {
  const embeddings = await getEmbeddings([content])

  // upsert:存在则更新,不存在则新增,不需要判断
  await collection.upsert({
    ids: [docId],
    documents: [content],
    embeddings: embeddings,
    metadatas: [metadata],
  })
}

// 删除已下架的文档
async function removeOutdatedDocs(source: string) {
  await collection.delete({
    where: { source },
  })
  console.log(`已删除来源为 ${source} 的所有文档`)
}

常见问题与踩坑

Q1:Connection refused 连接被拒绝

# Chroma 服务没启动,或端口不对
# 检查服务是否运行
docker ps | grep chroma

# 检查端口
curl http://localhost:8000/api/v2/heartbeat

# 如果换了端口,代码里也要对应修改
const client = new ChromaClient({ path: 'http://localhost:9000' })

Q2:检索结果和预期完全不符

最常见的原因是建库和查询用了不同的 Embedding 模型

// ❌ 错误:建库用 A 模型,查询用 B 模型
// 建库时
const embedFnA = new OpenAIEmbeddings({ model: 'text-embedding-3-small' })
await Chroma.fromDocuments(docs, embedFnA, { collectionName: 'kb' })

// 查询时(不小心换了模型)
const embedFnB = new OpenAIEmbeddings({ model: 'text-embedding-3-large' })
const store = await Chroma.fromExistingCollection(embedFnB, { collectionName: 'kb' })
// 结果完全乱,因为向量空间不一样

// ✅ 正确:始终用同一个模型,建议把模型名写成常量
const EMBEDDING_MODEL = 'text-embedding-3-small'
const embeddings = new OpenAIEmbeddings({ model: EMBEDDING_MODEL })

Q3:Collection already exists 报错

// 不要用 createCollection,改用 getOrCreateCollection
const collection = await client.getOrCreateCollection({ name: 'my_docs' })

Q4:大批量数据导入很慢

// 分批处理,每批 100 条,避免单次请求太大
async function batchAdd(docs: Document[], batchSize = 100) {
  for (let i = 0; i < docs.length; i += batchSize) {
    const batch = docs.slice(i, i + batchSize)

    // 批量计算 embedding
    const embeddings = await getEmbeddings(batch.map(d => d.pageContent))

    await collection.add({
      ids: batch.map((_, j) => `doc_${i + j}`),
      documents: batch.map(d => d.pageContent),
      embeddings,
      metadatas: batch.map(d => d.metadata),
    })

    console.log(`已导入 ${Math.min(i + batchSize, docs.length)} / ${docs.length}`)
  }
}

Q5:元数据过滤没有效果

// 元数据的值只支持 string、number、boolean
// ❌ 不支持数组或嵌套对象作为元数据值
metadata: {
  tags: ['ai', 'ml'],           // ❌ 数组不支持
  author: { name: '张三' },     // ❌ 嵌套对象不支持
}

// ✅ 正确:展平为基础类型
metadata: {
  tag_ai: true,       // 用布尔值模拟标签
  tag_ml: true,
  authorName: '张三', // 展平嵌套对象
}

Q6:距离分数怎么理解,多少算"相关"

// 余弦距离参考标准(不同数据集会有差异,以下是经验值)
// < 0.2  → 非常相关,几乎是同一个意思
// 0.2~0.4 → 比较相关,可以使用
// 0.4~0.6 → 有一定关联,谨慎使用
// > 0.6  → 基本不相关,建议过滤掉

// 在 RAG 中设置阈值过滤
const results = await collection.query({
  queryTexts: ['用户问题'],
  nResults: 10,  // 多取一些
  include: ['documents', 'distances'],
})

const DISTANCE_THRESHOLD = 0.5
const filteredDocs = results.documents[0].filter(
  (_, i) => results.distances![0][i] < DISTANCE_THRESHOLD
)

if (filteredDocs.length === 0) {
  console.log('没有找到足够相关的内容,建议直接告知用户')
}

总结:Chroma 核心 API 速查

// 客户端
const client = new ChromaClient({ path: 'http://localhost:8000' })

// Collection 管理
await client.createCollection({ name, metadata })
await client.getCollection({ name })
await client.getOrCreateCollection({ name, metadata })   // 最常用
await client.listCollections()
await client.deleteCollection({ name })

// 数据操作
await collection.add({ ids, documents, embeddings, metadatas })
await collection.upsert({ ids, documents, embeddings, metadatas })  // 推荐
await collection.update({ ids, documents, metadatas })
await collection.delete({ ids })
await collection.delete({ where })
await collection.get({ ids, where, include })
await collection.count()

// 检索
await collection.query({
  queryTexts,       // 文本查询(自动向量化)
  queryEmbeddings,  // 向量查询(自己传)
  nResults,
  where,            // 元数据过滤
  whereDocument,    // 文档内容过滤
  include,
})