From 8a7fae6e902e770bdc9a6d246c1367250b79c5d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Cing=C3=B6z?= Date: Sun, 19 Oct 2025 20:07:46 +0200 Subject: [PATCH] feat: Add withChromaApiKey() method for Chroma Cloud authentication Adds support for authenticating with Chroma Cloud by setting the X-CHROMA-TOKEN header via the new withChromaApiKey() method. --- README.md | 29 ++++++++++++--- src/Factory.php | 20 ++++++++++ tests/Factory.php | 94 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 tests/Factory.php diff --git a/README.md b/README.md index 836e255..3d968ff 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,6 @@ echo $queryResponse->ids[0][1]; // test2 In order to use this library, you need to have ChromaDB running somewhere. You can either run it locally or in the cloud. -(Chroma doesn't support cloud yet, but it will soon.) For now, ChromaDB can only run in-memory in Python. You can however run it in client/server mode by either running the python @@ -164,10 +163,14 @@ If the tenant or database doesn't exist, the package will automatically create t ### Authentication -ChromaDB supports static token-based authentication. To use it, you need to start the Chroma server passing the required -environment variables as stated in the documentation. If you're using the docker image, you can pass in the environment -variables using the `--env` flag or by using a `.env` file and for the docker-compose file, you can use the `env_file` -option, or pass in the environment variables directly like so: +ChromaDB supports multiple authentication methods. + +#### Self-Hosted (Static Token) + +For self-hosted instances, you can use static token-based authentication. You need to start the Chroma server passing +the required environment variables as stated in the documentation. If you're using the docker image, you can pass in the +environment variables using the `--env` flag or by using a `.env` file and for the docker-compose file, you can use the +env_file option, or pass in the environment variables directly like so: ```yaml version: '3.9' @@ -194,6 +197,22 @@ $chroma = ChromaDB::factory() ->connect(); ``` +#### Chroma Cloud + +To authenticate with [Chroma Cloud](https://www.trychroma.com/cloud), you must provide your API key using the `withChromaApiKey()` method. This will +correctly send the key in the `X-CHROMA-TOKEN` header. + +You also need to set the host to your Chroma Cloud instance URL and the port to 443 (if you want to use TLS). + +```php +use Codewithkyrian\ChromaDB\ChromaDB; + +$chroma = ChromaDB::factory() + ->withHost('https://api.trychroma.com') + ->withPort(443) + ->withChromaApiKey('your-chroma-cloud-api-key') + ->connect(); +``` ### Getting the version ```php diff --git a/src/Factory.php b/src/Factory.php index 4a2a658..6334987 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -38,6 +38,12 @@ class Factory */ protected string $authToken; + /** + * The API key used to authenticate requests to Chroma Cloud. + * This will be sent as the X-CHROMA-TOKEN header. + */ + protected ?string $chromaApiKey = null; + /** * The http client to use for the requests. */ @@ -93,6 +99,16 @@ public function withAuthToken(string $authToken): self return $this; } + /** + * The API key used to authenticate requests to Chroma Cloud. + * This will be sent as the X-CHROMA-TOKEN header. + */ + public function withChromaApiKey(string $apiKey): self + { + $this->chromaApiKey = $apiKey; + return $this; + } + /** * The http client to use for the requests. */ @@ -122,6 +138,10 @@ public function createApiClient() : ChromaApiClient $headers['Authorization'] = 'Bearer ' . $this->authToken; } + if (!empty($this->chromaApiKey)) { + $headers['X-CHROMA-TOKEN'] = $this->chromaApiKey; + } + $this->httpClient ??= new \GuzzleHttp\Client([ 'base_uri' => $this->baseUrl, 'headers' => $headers, diff --git a/tests/Factory.php b/tests/Factory.php new file mode 100644 index 0000000..705dfe6 --- /dev/null +++ b/tests/Factory.php @@ -0,0 +1,94 @@ +withHost('https://api.trychroma.com') + ->withPort(443) + ->withChromaApiKey('test-api-key-12345'); + + $reflection = new ReflectionClass($factory); + $property = $reflection->getProperty('chromaApiKey'); + $property->setAccessible(true); + + expect($property->getValue($factory))->toBe('test-api-key-12345'); + }); + + it('can set both auth token and chroma api key', function () { + $factory = ChromaDB::factory() + ->withAuthToken('bearer-token-123') + ->withChromaApiKey('chroma-key-456'); + + $reflection = new ReflectionClass($factory); + + $authTokenProperty = $reflection->getProperty('authToken'); + $authTokenProperty->setAccessible(true); + + $chromaKeyProperty = $reflection->getProperty('chromaApiKey'); + $chromaKeyProperty->setAccessible(true); + + expect($authTokenProperty->getValue($factory)) + ->toBe('bearer-token-123') + ->and($chromaKeyProperty->getValue($factory)) + ->toBe('chroma-key-456'); + }); + + it('includes X-CHROMA-TOKEN header when api key is set', function () { + $factory = ChromaDB::factory() + ->withChromaApiKey('test-key-789'); + + $apiClient = $factory->createApiClient(); + + $reflection = new ReflectionClass($apiClient); + $clientProperty = $reflection->getProperty('httpClient'); + $clientProperty->setAccessible(true); + $httpClient = $clientProperty->getValue($apiClient); + + $config = $httpClient->getConfig(); + + expect($config['headers']['X-CHROMA-TOKEN'] ?? null) + ->toBe('test-key-789'); + }); + + it('includes both Authorization and X-CHROMA-TOKEN headers when both are set', function () { + $factory = ChromaDB::factory() + ->withAuthToken('my-bearer-token') + ->withChromaApiKey('my-chroma-key'); + + $apiClient = $factory->createApiClient(); + + $reflection = new ReflectionClass($apiClient); + $clientProperty = $reflection->getProperty('httpClient'); + $clientProperty->setAccessible(true); + $httpClient = $clientProperty->getValue($apiClient); + + $config = $httpClient->getConfig(); + + expect($config['headers']['Authorization'] ?? null) + ->toBe('Bearer my-bearer-token') + ->and($config['headers']['X-CHROMA-TOKEN'] ?? null) + ->toBe('my-chroma-key'); + }); + + it('does not include X-CHROMA-TOKEN header when api key is not set', function () { + $factory = ChromaDB::factory(); + + $apiClient = $factory->createApiClient(); + + $reflection = new ReflectionClass($apiClient); + $clientProperty = $reflection->getProperty('httpClient'); + $clientProperty->setAccessible(true); + $httpClient = $clientProperty->getValue($apiClient); + + $config = $httpClient->getConfig(); + + expect($config['headers']['X-CHROMA-TOKEN'] ?? null) + ->toBeNull(); + }); + +});