4. 基础 CRUD 操作

4.1 创建和获取 Collection

import { ChromaClient } from 'chromadb'

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

// 创建 Collection(已存在则报错)
const collection = await client.createCollection({
  name: 'my_docs',
  metadata: {
    'hnsw:space': 'cosine',   // 使用余弦距离(推荐)
    description: '产品文档知识库',  // 自定义描述,方便管理
  },
})

// 获取已有 Collection(不存在则报错)
const existing = await client.getCollection({ name: 'my_docs' })

// 获取或创建(最常用,不用判断是否存在)
const safe = await client.getOrCreateCollection({
  name: 'my_docs',
  metadata: { 'hnsw:space': 'cosine' },
})

// 列出所有 Collection
const allCollections = await client.listCollections()
console.log(allCollections)  // ['my_docs', 'user_feedback', ...]

// 删除 Collection(谨慎!数据会全部删除)
await client.deleteCollection({ name: 'old_collection' })

4.2 添加数据

安装向量函数库:

添加数据的时候我们一般需要指定向量(embeddings),如不指定,让Chroma通过向量函数(embeddingFunction)自动生成也是可以的。这个时候我们就要配置向量函数,比如我们可以使用:@chroma-core/default-embed 这个库:

npm install @chroma-core/default-embed  

这个库很好安装,但是国内用起来还是有点小麻烦的,这个库首次使用的时候会下载all-MiniLM-L6-v2模型,问题就出在这,国内的网络很可能下载不下来。这时候就需要使用代理,不过就算你的电脑上已经装了代理工具,但是代码运行时好像一直用不上代理,还是会出现网络问题。我尝试了多个方法,这种方法可以解决问题:

  • 安装 undici

    npm uninstall undici
    

    注意,要根据您的nodejs版本来选择undici库,比如:npm uninstall undici@5

  • 编写使用代理的代码并运行

    import { setGlobalDispatcher, ProxyAgent } from 'undici'
    setGlobalDispatcher(new ProxyAgent('http://127.0.0.1:7890'))
    import { ChromaClient } from 'chromadb'
    import { DefaultEmbeddingFunction } from '@chroma-core/default-embed'
    const embedder = new DefaultEmbeddingFunction();
    embedder.generate(['test'])  // 触发下载
    

    执行上面的代码,就会自动下载 all-MiniLM-L6-v2 模型,等下再完毕,这段代码就没啥用了,undici库也没什么用了,都可以移除。

添加数据的操作

import { ChromaClient } from 'chromadb'
import { DefaultEmbeddingFunction } from '@chroma-core/default-embed'
// 创建一个本地、免费、无需外部API的文本向量化函数
const embedder = new DefaultEmbeddingFunction();

// 生成密码
const auth = Buffer.from("admin:123456").toString("base64");
const client = new ChromaClient({
    ssl: false,
    host: '192.168.31.249',
    port: 7777,
    headers: { Authorization: `Basic ${auth}` },
});

async function main() {
    // 创建连接的时候,指定向量函数
    const collection = await client.getCollection({ name: 'my_docs', embeddingFunction: embedder });
    // 批量添加(推荐,减少网络请求次数)
    await collection.add({
        ids: ['doc_001', 'doc_002', 'doc_003'],
        documents: [
            '量子纠缠是两个粒子之间的神秘关联,无论相距多远都能瞬间影响彼此。',
            '薛定谔的猫是一个著名的思想实验,用来说明量子叠加态的概念。',
            '波粒二象性表明光既是波又是粒子,取决于观测方式。',
        ],
        metadatas: [
            { source: '量子力学.pdf', chapter: '第一章', page: 1 },
            { source: '量子力学.pdf', chapter: '第二章', page: 15 },
            { source: '量子力学.pdf', chapter: '第二章', page: 18 },
        ],
        // 如果不传 embeddings,Chroma 会用配置的 embedding 函数自动计算
    });
    console.log('添加成功,当前数据量:', await collection.count())
}
main()

如果我们插入的文档的id是相同的,那么会跳过插入。如果我们想插入时候遇到想通的id就更新,可以使用upsert()

 // 存在就更新,不存在就插入
await collection.upsert({
    ids: ['doc_003'],
    documents: ['中国人民万岁'],
    metadatas: [
        { source: '测试文档.pdf', chapter: '第一章', page: 1 },
    ],

});
console.log('添加成功,当前数据量:', await collection.count())
const existing = await collection.get({ ids: ['doc_003'] });
console.log(existing);

4.3 查询数据

按照 id 进行查询

// 按 ID 精确获取
const result = await collection.get({
  ids: ['doc_001', 'doc_002'],
  include: ['documents', 'metadatas', 'embeddings'],
  // include 控制返回哪些字段,embeddings 比较大,按需返回
})

console.log(result.ids)        // ['doc_001', 'doc_002']
console.log(result.documents)  // ['量子纠缠是...', '薛定谔的猫是...']
console.log(result.metadatas)  // [{source: '...', ...}, ...]

根据 metadata 条件过滤

const collection = await client.getCollection({ name: 'my_docs' });
const result = await collection.get({
    // 设置查询条件
    where: { chapter: '第二章' }
});
console.log(result);
const result = await collection.get({
    where: {
        page: { '$gt': 10 }  // page > 10
    }
});
// 多条件组合(and)
const result = await collection.get({
    where: {
        '$and': [
            { source: '量子力学.pdf' },
            { page: { '$gte': 15 } }
        ]
    }
});

支持的操作符:$eq$ne$gt$gte$lt$lte$in$nin,逻辑组合用 $and$or

根据文档内容过滤

const result = await collection.get({
    whereDocument: { '$contains': '量子' }
});
console.log(result);

语义相似度查询(最常用,向量检索的核心)

// 用文本查询,会自动转成向量再做相似度搜索
const result = await collection.query({
    queryTexts: ['什么是量子纠缠'],
    nResults: 2,  // 返回最相似的2条
})
console.log(result);

组合使用(最强大):相似度 + metadata 过滤

const result = await collection.query({
    queryTexts: ['量子物理相关内容'],
    nResults: 5,
    where: { source: '量子力学.pdf' },        // 先按条件过滤
    whereDocument: { '$contains': '粒子' }     // 再按文本过滤
});
console.log(result);

分页和偏移量

参数 get() query()
limit ✅ 支持 ✅ 支持(但叫 nResults
offset ✅ 支持 ❌ 不支持
await collection.get({ 
    limit: 100, 
    offset: 200  // 跳过前200条,取接下来100条
});
await collection.query({
    queryTexts: ['量子纠缠'],
    nResults: 10,   // 注意:这里不叫 limit,叫 nResults
});

4.4 更新数据

// 更新已有数据(ID 必须存在,否则报错)
await collection.update({
  ids: ['doc_001'],
  documents: ['更新后的量子纠缠说明...'],
  metadatas: [{ source: '量子力学第2版.pdf', page: 1 }],
})

// upsert:存在则更新,不存在则创建(最安全的写法)
await collection.upsert({
  ids: ['doc_001', 'doc_new'],
  documents: ['量子纠缠更新版', '全新的文档内容'],
  metadatas: [
    { source: '量子力学第2版.pdf' },
    { source: '新文件.pdf' },
  ],
})

chroma 只能支持根据id更新,对于不知道id的可以先查询出来数据,然后根据id进行更新。

4.5 删除数据

// 按 ID 删除
await collection.delete({
  ids: ['doc_001', 'doc_002'],
})

// 按元数据条件删除(删除某个来源的所有数据)
await collection.delete({
  where: { source: '旧文档.pdf' },
})

// 查看剩余数量
console.log('剩余:', await collection.count())

4.6 纯内存模式(测试用)

import { ChromaClient } from 'chromadb'

// 不传 path,使用内存模式,无需启动服务端
// 注意:重启后数据全部丢失,只适合单元测试
const memoryClient = new ChromaClient()

const testCollection = await memoryClient.createCollection({
  name: 'test',
})