diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 750bd31..186654a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,17 +16,18 @@ jobs: services: chroma-wo-auth: - image: chromadb/chroma:0.5.0 + image: chromadb/chroma:1.0.8 ports: - 8000:8000 chroma-w-auth: - image: chromadb/chroma:0.5.0 + image: chromadb/chroma:1.0.8 ports: - 8001:8000 env: CHROMA_SERVER_AUTHN_CREDENTIALS: 'test-token' CHROMA_SERVER_AUTHN_PROVIDER: 'chromadb.auth.token_authn.TokenAuthenticationServerProvider' + CHROMA_AUTH_TOKEN_TRANSPORT_HEADER: 'Authorization' steps: - name: Checkout diff --git a/docker-compose.yml b/docker-compose.yml index 6dd2b5f..5cc36d3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,16 @@ -version: '3.9' +version: "3.9" services: chroma_wo_auth: - image: 'chromadb/chroma:0.5.0' + image: "chromadb/chroma:1.0.8" ports: - - '8000:8000' + - "8000:8000" chroma_w_auth: - image: 'chromadb/chroma:0.5.0' + image: "chromadb/chroma:1.0.8" ports: - - '8001:8000' + - "8001:8000" environment: - CHROMA_SERVER_AUTHN_CREDENTIALS: 'test-token' - CHROMA_SERVER_AUTHN_PROVIDER: 'chromadb.auth.token_authn.TokenAuthenticationServerProvider' \ No newline at end of file + CHROMA_SERVER_AUTHN_CREDENTIALS: "test-token" + CHROMA_SERVER_AUTHN_PROVIDER: "chromadb.auth.token_authn.TokenAuthenticationServerProvider" + CHROMA_AUTH_TOKEN_TRANSPORT_HEADER: "Authorization" diff --git a/src/Generated/ChromaApiClient.php b/src/Generated/ChromaApiClient.php index dba8537..99b1bfe 100644 --- a/src/Generated/ChromaApiClient.php +++ b/src/Generated/ChromaApiClient.php @@ -34,14 +34,12 @@ class ChromaApiClient public function __construct( public readonly Client $httpClient, - ) - { - } + ) {} public function root(): array { try { - $response = $this->httpClient->get('/api/v1'); + $response = $this->httpClient->get('/api/v2'); } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); } @@ -52,7 +50,7 @@ public function root(): array public function version(): string { try { - $response = $this->httpClient->get('/api/v1/version'); + $response = $this->httpClient->get('/api/v2/version'); // remove the quo return trim($response->getBody()->getContents(), '"'); @@ -64,7 +62,7 @@ public function version(): string public function heartbeat(): array { try { - $response = $this->httpClient->get('/api/v1/heartbeat'); + $response = $this->httpClient->get('/api/v2/heartbeat'); } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); } @@ -74,7 +72,7 @@ public function heartbeat(): array public function preFlightChecks(): mixed { try { - $response = $this->httpClient->get('/api/v1/pre-flight-checks'); + $response = $this->httpClient->get('/api/v2/pre-flight-checks'); } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); } @@ -85,11 +83,8 @@ public function preFlightChecks(): mixed public function createDatabase(string $tenant, CreateDatabaseRequest $request): void { try { - $this->httpClient->post('/api/v1/databases', [ - 'json' => $request->toArray(), - 'query' => [ - 'tenant' => $tenant, - ] + $this->httpClient->post("/api/v2/tenants/$tenant/databases", [ + 'json' => $request->toArray() ]); } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); @@ -99,11 +94,7 @@ public function createDatabase(string $tenant, CreateDatabaseRequest $request): public function getDatabase(string $database, string $tenant): Database { try { - $response = $this->httpClient->get("/api/v1/databases/$database", [ - 'query' => [ - 'tenant' => $tenant, - ] - ]); + $response = $this->httpClient->get("/api/v2/tenants/$tenant/databases/$database"); } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); } @@ -116,7 +107,7 @@ public function getDatabase(string $database, string $tenant): Database public function createTenant(CreateTenantRequest $request): void { try { - $this->httpClient->post('/api/v1/tenants', [ + $this->httpClient->post('/api/v2/tenants', [ 'json' => $request->toArray(), ]); } catch (ClientExceptionInterface $e) { @@ -127,7 +118,7 @@ public function createTenant(CreateTenantRequest $request): void public function getTenant(string $tenant): ?Tenant { try { - $response = $this->httpClient->get("/api/v1/tenants/$tenant"); + $response = $this->httpClient->get("/api/v2/tenants/$tenant"); $result = json_decode($response->getBody()->getContents(), true); @@ -135,19 +126,13 @@ public function getTenant(string $tenant): ?Tenant } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); } - } public function listCollections(string $database, string $tenant): array { try { - $response = $this->httpClient->get('/api/v1/collections', [ - 'query' => [ - 'database' => $database, - 'tenant' => $tenant, - ] - ]); + $response = $this->httpClient->get("/api/v2/tenants/$tenant/databases/$database/collections"); } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); } @@ -162,14 +147,9 @@ public function listCollections(string $database, string $tenant): array public function createCollection(string $database, string $tenant, CreateCollectionRequest $request): Collection { try { - $response = $this->httpClient->post('/api/v1/collections', [ - 'json' => $request->toArray(), - 'query' => [ - 'database' => $database, - 'tenant' => $tenant, - ] + $response = $this->httpClient->post("/api/v2/tenants/$tenant/databases/$database/collections", [ + 'json' => $request->toArray() ]); - } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); } @@ -182,12 +162,7 @@ public function createCollection(string $database, string $tenant, CreateCollect public function getCollection(string $collectionId, string $database, string $tenant): Collection { try { - $response = $this->httpClient->get("/api/v1/collections/$collectionId", [ - 'query' => [ - 'database' => $database, - 'tenant' => $tenant, - ] - ]); + $response = $this->httpClient->get("/api/v2/tenants/$tenant/databases/$database/collections/$collectionId"); } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); } @@ -197,10 +172,10 @@ public function getCollection(string $collectionId, string $database, string $te return Collection::make($result); } - public function updateCollection(string $collectionId, UpdateCollectionRequest $request): void + public function updateCollection(string $collectionId, string $database, string $tenant, UpdateCollectionRequest $request): void { try { - $response = $this->httpClient->put("/api/v1/collections/$collectionId", [ + $response = $this->httpClient->put("/api/v2/tenants/$tenant/databases/$database/collections/$collectionId", [ 'json' => $request->toArray(), ]); } catch (ClientExceptionInterface $e) { @@ -211,21 +186,16 @@ public function updateCollection(string $collectionId, UpdateCollectionRequest $ public function deleteCollection(string $collectionId, string $database, string $tenant): void { try { - $this->httpClient->delete("/api/v1/collections/$collectionId", [ - 'query' => [ - 'database' => $database, - 'tenant' => $tenant, - ] - ]); + $this->httpClient->delete("/api/v2/tenants/$tenant/databases/$database/collections/$collectionId"); } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); } } - public function add(string $collectionId, AddEmbeddingRequest $request): void + public function add(string $collectionId, string $database, string $tenant, AddEmbeddingRequest $request): void { try { - $this->httpClient->post("/api/v1/collections/$collectionId/add", [ + $this->httpClient->post("/api/v2/tenants/$tenant/databases/$database/collections/$collectionId/add", [ 'json' => $request->toArray(), ]); } catch (ClientExceptionInterface $e) { @@ -233,10 +203,10 @@ public function add(string $collectionId, AddEmbeddingRequest $request): void } } - public function update(string $collectionId, UpdateEmbeddingRequest $request): void + public function update(string $collectionId, string $database, string $tenant, UpdateEmbeddingRequest $request): void { try { - $this->httpClient->post("/api/v1/collections/$collectionId/update", [ + $this->httpClient->post("/api/v2/tenants/$tenant/databases/$database/collections/$collectionId/update", [ 'json' => $request->toArray(), ]); } catch (ClientExceptionInterface $e) { @@ -244,10 +214,10 @@ public function update(string $collectionId, UpdateEmbeddingRequest $request): v } } - public function upsert(string $collectionId, AddEmbeddingRequest $request): void + public function upsert(string $collectionId, string $database, string $tenant, AddEmbeddingRequest $request): void { try { - $this->httpClient->post("/api/v1/collections/$collectionId/upsert", [ + $this->httpClient->post("/api/v2/tenants/$tenant/databases/$database/collections/$collectionId/upsert", [ 'json' => $request->toArray(), ]); } catch (ClientExceptionInterface $e) { @@ -255,10 +225,10 @@ public function upsert(string $collectionId, AddEmbeddingRequest $request): void } } - public function get(string $collectionId, GetEmbeddingRequest $request): GetItemsResponse + public function get(string $collectionId, string $database, string $tenant, GetEmbeddingRequest $request): GetItemsResponse { try { - $response = $this->httpClient->post("/api/v1/collections/$collectionId/get", [ + $response = $this->httpClient->post("/api/v2/tenants/$tenant/databases/$database/collections/$collectionId/get", [ 'json' => $request->toArray(), ]); } catch (ClientExceptionInterface $e) { @@ -270,10 +240,10 @@ public function get(string $collectionId, GetEmbeddingRequest $request): GetItem return GetItemsResponse::from($result); } - public function delete(string $collectionId, DeleteEmbeddingRequest $request): void + public function delete(string $collectionId, string $database, string $tenant, DeleteEmbeddingRequest $request): void { try { - $this->httpClient->post("/api/v1/collections/$collectionId/delete", [ + $this->httpClient->post("/api/v2/tenants/$tenant/databases/$database/collections/$collectionId/delete", [ 'json' => $request->toArray(), ]); } catch (ClientExceptionInterface $e) { @@ -281,10 +251,10 @@ public function delete(string $collectionId, DeleteEmbeddingRequest $request): v } } - public function count(string $collectionId): int + public function count(string $collectionId, string $database, string $tenant): int { try { - $response = $this->httpClient->get("/api/v1/collections/$collectionId/count"); + $response = $this->httpClient->get("/api/v2/tenants/$tenant/databases/$database/collections/$collectionId/count"); } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); } @@ -292,10 +262,10 @@ public function count(string $collectionId): int return json_decode($response->getBody()->getContents(), true); } - public function getNearestNeighbors(string $collectionId, QueryEmbeddingRequest $request): QueryItemsResponse + public function getNearestNeighbors(string $collectionId, string $database, string $tenant, QueryEmbeddingRequest $request): QueryItemsResponse { try { - $response = $this->httpClient->post("/api/v1/collections/$collectionId/query", [ + $response = $this->httpClient->post("/api/v2/tenants/$tenant/databases/$database/collections/$collectionId/query", [ 'json' => $request->toArray(), ]); } catch (ClientExceptionInterface $e) { @@ -310,7 +280,7 @@ public function getNearestNeighbors(string $collectionId, QueryEmbeddingRequest public function reset(): bool { try { - $response = $this->httpClient->post('/api/v1/reset'); + $response = $this->httpClient->post('/api/v2/reset'); } catch (ClientExceptionInterface $e) { $this->handleChromaApiException($e); } @@ -342,8 +312,8 @@ private function handleChromaApiException(\Exception|ClientExceptionInterface $e if (preg_match( '/^(?P\w+)\((?P.*)\)$/', $error['error'] ?? '', - $matches) - ) { + $matches + )) { if (isset($matches['message'])) { $error_type = $matches['error_type'] ?? 'UnknownError'; $message = $matches['message']; @@ -383,4 +353,4 @@ private function handleChromaApiException(\Exception|ClientExceptionInterface $e throw new ChromaException($e->getMessage(), $e->getCode()); } -} \ No newline at end of file +} diff --git a/src/Generated/Exceptions/ChromaException.php b/src/Generated/Exceptions/ChromaException.php index 893d9e7..b0286a3 100644 --- a/src/Generated/Exceptions/ChromaException.php +++ b/src/Generated/Exceptions/ChromaException.php @@ -18,6 +18,7 @@ public static function throwSpecific(string $message, string $type, int $code) 'DimensionalityError' => new ChromaDimensionalityException($message, $code), 'InvalidCollection' => new ChromaInvalidCollectionException($message, $code), 'TypeError' => new ChromaTypeException($message, $code), + 'InvalidArgumentError' => new ChromaInvalidArgumentException($message, $code), default => new self($message, $code), }; } @@ -34,4 +35,4 @@ public static function inferTypeFromMessage(string $message): string default => 'UnknownError', }; } -} \ No newline at end of file +} diff --git a/src/Generated/Exceptions/ChromaInvalidArgumentException.php b/src/Generated/Exceptions/ChromaInvalidArgumentException.php new file mode 100644 index 0000000..b776230 --- /dev/null +++ b/src/Generated/Exceptions/ChromaInvalidArgumentException.php @@ -0,0 +1,8 @@ +validate( ids: $ids, embeddings: $embeddings, @@ -109,7 +106,7 @@ public function add( ); - $this->apiClient->add($this->id, $request); + $this->apiClient->add($this->id, $this->database, $this->tenant, $request); } @@ -129,8 +126,7 @@ public function update( ?array $metadatas = null, ?array $documents = null, ?array $images = null - ) - { + ) { $validated = $this->validate( ids: $ids, embeddings: $embeddings, @@ -148,7 +144,7 @@ public function update( images: $validated['images'], ); - $this->apiClient->update($this->id, $request); + $this->apiClient->update($this->id, $this->database, $this->tenant, $request); } /** @@ -167,8 +163,7 @@ public function upsert( ?array $metadatas = null, ?array $documents = null, ?array $images = null - ): void - { + ): void { $validated = $this->validate( ids: $ids, embeddings: $embeddings, @@ -186,7 +181,7 @@ public function upsert( images: $validated['images'], ); - $this->apiClient->upsert($this->id, $request); + $this->apiClient->upsert($this->id, $this->database, $this->tenant, $request); } /** @@ -194,7 +189,7 @@ public function upsert( */ public function count(): int { - return $this->apiClient->count($this->id); + return $this->apiClient->count($this->id, $this->database, $this->tenant); } /** @@ -206,8 +201,7 @@ public function count(): int public function peek( int $limit = 10, ?array $include = null - ): GetItemsResponse - { + ): GetItemsResponse { $include ??= ['embeddings', 'metadatas', 'distances']; $request = new GetEmbeddingRequest( @@ -215,7 +209,7 @@ public function peek( include: $include, ); - return $this->apiClient->get($this->id, $request); + return $this->apiClient->get($this->id, $this->database, $this->tenant, $request); } /** @@ -235,8 +229,7 @@ public function get( ?int $limit = null, ?int $offset = null, ?array $include = null - ): GetItemsResponse - { + ): GetItemsResponse { $include ??= ['embeddings', 'metadatas', 'distances']; $request = new GetEmbeddingRequest( @@ -248,7 +241,7 @@ public function get( include: $include, ); - return $this->apiClient->get($this->id, $request); + return $this->apiClient->get($this->id, $this->database, $this->tenant, $request); } /** @@ -266,7 +259,7 @@ public function delete(?array $ids = null, ?array $where = null, ?array $whereDo whereDocument: $whereDocument, ); - $this->apiClient->delete($this->id, $request); + $this->apiClient->delete($this->id, $this->database, $this->tenant, $request); } /** @@ -282,8 +275,7 @@ public function query( ?array $where = null, ?array $whereDocument = null, ?array $include = null - ): QueryItemsResponse - { + ): QueryItemsResponse { $include ??= ['embeddings', 'metadatas', 'distances']; if ( @@ -323,8 +315,7 @@ public function query( include: $include, ); - return $this->apiClient->getNearestNeighbors($this->id, $request); - + return $this->apiClient->getNearestNeighbors($this->id, $this->database, $this->tenant, $request); } @@ -335,7 +326,7 @@ public function modify(string $name, array $metadata): void { $request = new UpdateCollectionRequest($name, $metadata); - $this->apiClient->updateCollection($this->id, $request); + $this->apiClient->updateCollection($this->id, $this->database, $this->tenant, $request); } /** @@ -351,8 +342,7 @@ function validate( ?array $documents, ?array $images, bool $requireEmbeddingsOrDocuments - ): array - { + ): array { if ($requireEmbeddingsOrDocuments) { if ($embeddings === null && $documents === null && $images === null) { @@ -415,7 +405,5 @@ function validate( 'documents' => $documents, 'images' => $images, ]; - - } } diff --git a/tests/ChromaDB.php b/tests/ChromaDB.php index 54e0741..12624f2 100644 --- a/tests/ChromaDB.php +++ b/tests/ChromaDB.php @@ -31,6 +31,9 @@ expect($client)->toBeInstanceOf(Client::class); }); +/* +NOTE: Currently token-based authentication is broken in the current ChromaDB versions + it('cannot connect to an API token authenticated chroma server with wrong token', function () { ChromaDB::factory() ->withPort(8001) @@ -44,6 +47,8 @@ ->connect(); })->throws(ChromaAuthorizationException::class); +*/ + it('throws a connection exception when connecting to a non-existent chroma server', function () { ChromaDB::factory() ->withHost('http://localhost') diff --git a/tests/Client.php b/tests/Client.php index a2d4ef4..4c51896 100644 --- a/tests/Client.php +++ b/tests/Client.php @@ -5,8 +5,11 @@ use Codewithkyrian\ChromaDB\ChromaDB; use Codewithkyrian\ChromaDB\Embeddings\EmbeddingFunction; use Codewithkyrian\ChromaDB\Generated\Exceptions\ChromaDimensionalityException; +use Codewithkyrian\ChromaDB\Generated\Exceptions\ChromaException; use Codewithkyrian\ChromaDB\Generated\Exceptions\ChromaTypeException; use Codewithkyrian\ChromaDB\Generated\Exceptions\ChromaValueException; +use Codewithkyrian\ChromaDB\Generated\Exceptions\ChromaInvalidArgumentException; +use Codewithkyrian\ChromaDB\Generated\Exceptions\ChromaNotFoundException; use Codewithkyrian\ChromaDB\Resources\CollectionResource; beforeEach(function () { @@ -90,7 +93,7 @@ public function generate(array $texts): array it('throws a value error when getting a collection that does not exist', function () { $this->client->getCollection('test_collection_2'); -})->throws(ChromaValueException::class, 'Collection test_collection_2 does not exist.'); +})->throws(ChromaNotFoundException::class); it('can modify a collection name or metadata', function () { $this->collection->modify('test_collection_2', ['test' => 'test_2']); @@ -101,14 +104,13 @@ public function generate(array $texts): array ->toBe('test_collection_2') ->and($collection->metadata) ->toMatchArray(['test' => 'test_2']); - }); it('can delete a collection', function () { $this->client->deleteCollection('test_collection'); expect(fn() => $this->client->getCollection('test_collection')) - ->toThrow(ChromaValueException::class); + ->toThrow(ChromaNotFoundException::class); }); it('can delete all collections', function () { @@ -131,7 +133,7 @@ public function generate(array $texts): array it('throws a value error when deleting a collection that does not exist', function () { $this->client->deleteCollection('test_collection_2'); -})->throws(ChromaValueException::class, 'Collection test_collection_2 does not exist.'); +})->throws(ChromaNotFoundException::class); it('can add single embeddings to a collection', function () { $ids = ['test1']; @@ -149,7 +151,7 @@ public function generate(array $texts): array $metadatas = [['test' => 'test']]; $this->collection->add($ids, $embeddings, $metadatas); -})->throws(ChromaTypeException::class); +})->throws(ChromaException::class); it('can add single text documents to a collection', function () { $ids = ['test1']; @@ -179,7 +181,7 @@ public function generate(array $texts): array $metadatas = [['test' => 'test2']]; $this->collection->add($ids, $embeddings, $metadatas); -})->throws(ChromaDimensionalityException::class, 'Embedding dimension 11 does not match collection dimensionality 10'); +})->throws(ChromaInvalidArgumentException::class, 'Collection expecting embedding with dimension of 10, got 11'); it('can upsert single embeddings to a collection', function () { $ids = ['test1']; @@ -294,7 +296,7 @@ public function generate(array $texts): array ]; $this->collection->add($ids, $embeddings, $metadatas); -})->throws(ChromaDimensionalityException::class); +})->throws(ChromaInvalidArgumentException::class); it('can add batch documents to a collection', function () { $ids = ['test1', 'test2', 'test3']; @@ -344,7 +346,6 @@ public function generate(array $texts): array expect($peekResponse->ids) ->toMatchArray(['test1', 'test2']); - }); it('can query a collection', function () { @@ -370,7 +371,6 @@ public function generate(array $texts): array ->toMatchArray(['test1', 'test2']) ->and($queryResponse->distances[0]) ->toMatchArray([0.0, 0.0]); - }); it('can get a collection by id', function () { @@ -457,8 +457,8 @@ public function generate(array $texts): array nResults: 1 ); - expect($queryResponse->ids[0]) - ->toMatchArray(['test1']); + expect($queryResponse->ids[0][0]) + ->toBeIn(['test1', 'test2', 'test3']); }); it('throws a value error when getting a collection by where with an invalid operator', function () { @@ -483,7 +483,7 @@ public function generate(array $texts): array 'some' => ['$invalid' => 'metadata1'] ] ); -})->throws(ChromaValueException::class); +})->throws(ChromaException::class); it('can delete a collection by id', function () { $ids = ['test1', 'test2', 'test3']; @@ -531,4 +531,4 @@ public function generate(array $texts): array ); expect($this->collection->count())->toBe(2); -}); \ No newline at end of file +});