Skip to content

Commit e7c499e

Browse files
Update ChromaDB + improve docs and tests (#940)
1 parent 5732a4a commit e7c499e

File tree

5 files changed

+172
-57
lines changed

5 files changed

+172
-57
lines changed

docs/modules/chromadb.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,37 @@
88
npm install @testcontainers/chromadb --save-dev
99
```
1010

11-
## Example
11+
## Resources
12+
13+
* [GitHub](https://github.com/chroma-core/chroma)
14+
* [Node.js Client](https://www.npmjs.com/package/chromadb)
15+
* [Docs](https://docs.trychroma.com)
16+
* [Discord](https://discord.gg/MMeYNTmh3x)
17+
* [Cookbook](https://cookbook.chromadb.dev)
18+
19+
## Examples
20+
21+
<!--codeinclude-->
22+
[Connect to Chroma:](../../packages/modules/chromadb/src/chromadb-container.test.ts)
23+
inside_block:simpleConnect
24+
<!--/codeinclude-->
25+
26+
<!--codeinclude-->
27+
[Create Collection:](../../packages/modules/chromadb/src/chromadb-container.test.ts)
28+
inside_block:createCollection
29+
<!--/codeinclude-->
30+
31+
<!--codeinclude-->
32+
[Query Collection with Embedding Function:](../../packages/modules/chromadb/src/chromadb-container.test.ts)
33+
inside_block:queryCollectionWithEmbeddingFunction
34+
<!--/codeinclude-->
35+
36+
<!--codeinclude-->
37+
[Work with persistent directory:](../../packages/modules/chromadb/src/chromadb-container.test.ts)
38+
inside_block:persistentData
39+
<!--/codeinclude-->
1240

1341
<!--codeinclude-->
14-
[](../../packages/modules/chromadb/src/chromadb-container.test.ts) inside_block:docs
42+
[Work with authentication:](../../packages/modules/chromadb/src/chromadb-container.test.ts) inside_block:auth
1543
<!--/codeinclude-->
1644

package-lock.json

Lines changed: 25 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/modules/chromadb/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"build": "tsc --project tsconfig.build.json"
3030
},
3131
"devDependencies": {
32-
"chromadb": "^1.8.1"
32+
"chromadb": "^1.9.1",
33+
"ollama": "^0.5.14"
3334
},
3435
"dependencies": {
3536
"testcontainers": "^10.21.0"
Lines changed: 113 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,125 @@
1-
import { AdminClient, ChromaClient } from "chromadb";
2-
import { ChromaDBContainer } from "./chromadb-container";
1+
import { AdminClient, ChromaClient, OllamaEmbeddingFunction } from "chromadb";
2+
import fs from "node:fs";
3+
import os from "node:os";
4+
import path from "node:path";
5+
import { GenericContainer } from "testcontainers";
6+
import { ChromaDBContainer, StartedChromaDBContainer } from "./chromadb-container";
37

48
describe("ChromaDB", { timeout: 360_000 }, () => {
5-
// docs {
6-
it("should connect and return a query result", async () => {
9+
// startContainer {
10+
it("should connect", async () => {
711
const container = await new ChromaDBContainer().start();
12+
const client = await connectTo(container);
13+
expect(await client.heartbeat()).toBeDefined();
14+
// Do something with the client
15+
await container.stop();
16+
});
17+
// }
18+
19+
// simpleConnect {
20+
async function connectTo(container: StartedChromaDBContainer) {
21+
const client = new ChromaClient({
22+
path: container.getHttpUrl(),
23+
});
24+
const hb = await client.heartbeat();
25+
expect(hb).toBeDefined();
26+
return client;
27+
}
28+
// }
29+
30+
// createCollection {
31+
it("should create collection and get data", async () => {
32+
const container = await new ChromaDBContainer().start();
33+
const client = await connectTo(container);
34+
const collection = await client.createCollection({ name: "test", metadata: { "hnsw:space": "cosine" } });
35+
expect(collection.name).toBe("test");
36+
expect(collection.metadata).toBeDefined();
37+
expect(collection.metadata?.["hnsw:space"]).toBe("cosine");
38+
await collection.add({ ids: ["1"], embeddings: [[1, 2, 3]], documents: ["my doc"], metadatas: [{ key: "value" }] });
39+
const getResults = await collection.get({ ids: ["1"] });
40+
expect(getResults.ids[0]).toBe("1");
41+
expect(getResults.documents[0]).toStrictEqual("my doc");
42+
expect(getResults.metadatas).toBeDefined();
43+
expect(getResults.metadatas?.[0]?.key).toStrictEqual("value");
44+
await container.stop();
45+
});
46+
// }
47+
48+
// queryCollectionWithEmbeddingFunction {
49+
it("should create collection and query", async () => {
50+
const container = await new ChromaDBContainer().start();
51+
const ollama = await new GenericContainer("ollama/ollama").withExposedPorts(11434).start();
52+
await ollama.exec(["ollama", "pull", "nomic-embed-text"]);
53+
const client = await connectTo(container);
54+
const embedder = new OllamaEmbeddingFunction({
55+
url: `http://${ollama.getHost()}:${ollama.getMappedPort(11434)}/api/embeddings`,
56+
model: "nomic-embed-text",
57+
});
58+
const collection = await client.createCollection({
59+
name: "test",
60+
metadata: { "hnsw:space": "cosine" },
61+
embeddingFunction: embedder,
62+
});
63+
expect(collection.name).toBe("test");
64+
await collection.add({
65+
ids: ["1", "2"],
66+
documents: [
67+
"This is a document about dogs. Dogs are awesome.",
68+
"This is a document about cats. Cats are awesome.",
69+
],
70+
});
71+
const results = await collection.query({ queryTexts: ["Tell me about dogs"], nResults: 1 });
72+
expect(results).toBeDefined();
73+
expect(results.ids[0]).toEqual(["1"]);
74+
expect(results.ids[0][0]).toBe("1");
75+
await container.stop();
76+
});
77+
78+
// persistentData {
79+
it("should reconnect with volume and persistence data", async () => {
80+
const sourcePath = fs.mkdtempSync(path.join(os.tmpdir(), "chroma-temp"));
81+
const container = await new ChromaDBContainer()
82+
.withBindMounts([{ source: sourcePath, target: "/chroma/chroma" }])
83+
.start();
84+
const client = await connectTo(container);
85+
const collection = await client.createCollection({ name: "test", metadata: { "hnsw:space": "cosine" } });
86+
expect(collection.name).toBe("test");
87+
expect(collection.metadata).toBeDefined();
88+
expect(collection.metadata?.["hnsw:space"]).toBe("cosine");
89+
await collection.add({ ids: ["1"], embeddings: [[1, 2, 3]], documents: ["my doc"] });
90+
const getResults = await collection.get({ ids: ["1"] });
91+
expect(getResults.ids[0]).toBe("1");
92+
expect(getResults.documents[0]).toStrictEqual("my doc");
93+
await container.stop();
94+
expect(fs.existsSync(`${sourcePath}/chroma.sqlite3`)).toBe(true);
95+
try {
96+
fs.rmSync(sourcePath, { force: true, recursive: true });
97+
} catch (e) {
98+
// Ignore clean up, when have no access on fs.
99+
console.log(e);
100+
}
101+
});
102+
// }
103+
104+
// auth {
105+
it("should use auth", async () => {
8106
const tenant = "test-tenant";
9107
const key = "test-key";
10108
const database = "test-db";
109+
const container = await new ChromaDBContainer()
110+
.withEnvironment({
111+
CHROMA_SERVER_AUTHN_CREDENTIALS: key,
112+
CHROMA_SERVER_AUTHN_PROVIDER: "chromadb.auth.token_authn.TokenAuthenticationServerProvider",
113+
CHROMA_AUTH_TOKEN_TRANSPORT_HEADER: "X-Chroma-Token",
114+
})
115+
.start();
116+
11117
const adminClient = new AdminClient({
12118
tenant: tenant,
13119
auth: {
14120
provider: "token",
15121
credentials: key,
16-
providerOptions: {
17-
headerType: "X_CHROMA_TOKEN",
18-
},
122+
tokenHeaderType: "X_CHROMA_TOKEN",
19123
},
20124
path: container.getHttpUrl(),
21125
});
@@ -28,52 +132,14 @@ describe("ChromaDB", { timeout: 360_000 }, () => {
28132
auth: {
29133
provider: "token",
30134
credentials: key,
31-
providerOptions: {
32-
headerType: "X_CHROMA_TOKEN",
33-
},
135+
tokenHeaderType: "X_CHROMA_TOKEN",
34136
},
35137
path: container.getHttpUrl(),
36138
database,
37139
});
38140

39141
const collection = await dbClient.createCollection({ name: "test-collection" });
40-
41-
await collection.add({
42-
ids: ["1", "2", "3"],
43-
documents: ["apple", "oranges", "pineapple"],
44-
embeddings: [
45-
[1, 2, 3],
46-
[4, 5, 6],
47-
[7, 8, 9],
48-
],
49-
});
50-
51-
const result = await collection.get({ ids: ["1", "2", "3"] });
52-
53-
expect(result).toMatchInlineSnapshot(`
54-
{
55-
"data": null,
56-
"documents": [
57-
"apple",
58-
"oranges",
59-
"pineapple",
60-
],
61-
"embeddings": null,
62-
"ids": [
63-
"1",
64-
"2",
65-
"3",
66-
],
67-
"metadatas": [
68-
null,
69-
null,
70-
null,
71-
],
72-
"uris": null,
73-
}
74-
`);
75-
76-
await container.stop();
142+
expect(collection.name).toBe("test-collection");
77143
});
78144
// }
79145
});

packages/modules/chromadb/src/chromadb-container.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait
33
const CHROMADB_PORT = 8000;
44

55
export class ChromaDBContainer extends GenericContainer {
6-
constructor(image = "chromadb/chroma:0.4.24") {
6+
constructor(image = "chromadb/chroma:0.6.3") {
77
super(image);
88
this.withExposedPorts(CHROMADB_PORT)
9-
.withWaitStrategy(Wait.forHttp("/api/v1/heartbeat", CHROMADB_PORT))
9+
.withWaitStrategy(Wait.forHttp("/api/v2/heartbeat", CHROMADB_PORT))
1010
.withStartupTimeout(120_000);
1111
}
1212

0 commit comments

Comments
 (0)