├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── docker-compose.yml ├── examples ├── .gitignore ├── composer.json └── index.php ├── phpunit.xml ├── src ├── ChromaDB.php ├── Client.php ├── Embeddings │ ├── EmbeddingFunction.php │ ├── HuggingFaceEmbeddingServerFunction.php │ ├── JinaEmbeddingFunction.php │ ├── MistralAIEmbeddingFunction.php │ ├── OllamaEmbeddingFunction.php │ └── OpenAIEmbeddingFunction.php ├── Factory.php ├── Generated │ ├── ChromaApiClient.php │ ├── Exceptions │ │ ├── ChromaAuthorizationException.php │ │ ├── ChromaConnectionException.php │ │ ├── ChromaDimensionalityException.php │ │ ├── ChromaException.php │ │ ├── ChromaInvalidCollectionException.php │ │ ├── ChromaNotFoundException.php │ │ ├── ChromaTypeException.php │ │ ├── ChromaUniqueConstraintException.php │ │ ├── ChromaValueException.php │ │ └── ValidationException.php │ ├── Models │ │ ├── Collection.php │ │ ├── Database.php │ │ └── Tenant.php │ ├── Requests │ │ ├── AddEmbeddingRequest.php │ │ ├── CreateCollectionRequest.php │ │ ├── CreateDatabaseRequest.php │ │ ├── CreateTenantRequest.php │ │ ├── DeleteEmbeddingRequest.php │ │ ├── GetEmbeddingRequest.php │ │ ├── QueryEmbeddingRequest.php │ │ ├── UpdateCollectionRequest.php │ │ └── UpdateEmbeddingRequest.php │ └── Responses │ │ ├── GetItemsResponse.php │ │ └── QueryItemsResponse.php └── Resources │ └── CollectionResource.php └── tests ├── ChromaDB.php └── Client.php /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [ 'push', 'pull_request', 'workflow_dispatch' ] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | fail-fast: true 11 | matrix: 12 | php: [ 8.1, 8.2, 8.3, 8.4 ] 13 | dependency-version: [ prefer-lowest, prefer-stable ] 14 | 15 | name: Tests on PHP ${{ matrix.php }} - ${{ matrix.dependency-version }} 16 | 17 | services: 18 | chroma-wo-auth: 19 | image: chromadb/chroma:0.5.0 20 | ports: 21 | - 8000:8000 22 | 23 | chroma-w-auth: 24 | image: chromadb/chroma:0.5.0 25 | ports: 26 | - 8001:8000 27 | env: 28 | CHROMA_SERVER_AUTHN_CREDENTIALS: 'test-token' 29 | CHROMA_SERVER_AUTHN_PROVIDER: 'chromadb.auth.token_authn.TokenAuthenticationServerProvider' 30 | 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v3 34 | 35 | - name: Cache dependencies 36 | uses: actions/cache@v3 37 | with: 38 | path: ~/.composer/cache/files 39 | key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 40 | 41 | - name: Setup PHP 42 | uses: shivammathur/setup-php@v2 43 | with: 44 | php-version: ${{ matrix.php }} 45 | extensions: dom, mbstring, zip 46 | coverage: none 47 | 48 | - name: Install Composer dependencies 49 | run: composer update --${{ matrix.dependency-version }} --no-interaction --prefer-dist 50 | 51 | - name: Run tests 52 | run: composer test 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /.php-cs-fixer.cache 3 | /.php-cs-fixer.php 4 | /composer.lock 5 | /vendor/ 6 | *.swp 7 | *.swo 8 | playground/* 9 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Obikwelu Kyrian Sochima 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ChromaDB PHP 2 | 3 | **A PHP library for interacting with [Chroma](https://github.com/chroma-core/chroma) vector database seamlessly.** 4 | 5 | [![Total Downloads](https://img.shields.io/packagist/dt/codewithkyrian/chromadb-php.svg)](https://packagist.org/packages/codewithkyrian/chromadb-php) 6 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/codewithkyrian/chromadb-php.svg)](https://packagist.org/packages/codewithkyrian/chromadb-php) 7 | [![MIT Licensed](https://img.shields.io/badge/license-mit-blue.svg)](https://github.com/CodeWithKyrian/chromadb-php/blob/main/LICENSE) 8 | [![GitHub Tests Action Status](https://github.com/CodeWithKyrian/chromadb-php/actions/workflows/test.yml/badge.svg)](https://github.com/CodeWithKyrian/chromadb-php/actions/workflows/test.yml) 9 | 10 | > **Note:** This package is framework-agnostic, and can be used in any PHP project. If you're using Laravel however, you 11 | > might want to check out the Laravel-specific package [here](https://github.com/CodeWithKyrian/chromadb-laravel) which 12 | > provides a more Laravel-like experience, and includes a few extra features. 13 | 14 | ## Description 15 | 16 | [Chroma](https://www.trychroma.com/) is an open-source vector database that allows you to store, search, and analyze high-dimensional data at scale. 17 | It is designed to be fast, scalable, and reliable. It makes it easy to build LLM (Large Language Model) applications and 18 | services that require high-dimensional vector search. 19 | 20 | ChromaDB PHP provides a simple and intuitive interface for interacting with Chroma from PHP. It enables you to: 21 | 22 | - Create, read, update, and delete documents. 23 | - Execute queries and aggregations. 24 | - Manage collections and indexes. 25 | - Handle authentication and authorization. 26 | - Utilize other ChromaDB features seamlessly. 27 | - And more... 28 | 29 | ## Small Example 30 | 31 | ```php 32 | use Codewithkyrian\ChromaDB\ChromaDB; 33 | 34 | $chromaDB = ChromaDB::client(); 35 | 36 | // Check current ChromaDB version 37 | echo $chromaDB->version(); 38 | 39 | // Create a collection 40 | $collection = $chromaDB->createCollection('test-collection'); 41 | 42 | echo $collection->name; // test-collection 43 | echo $collection->id; // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx 44 | 45 | // Insert some documents into the collection 46 | $ids = ['test1', 'test2', 'test3']; 47 | $embeddings = [ 48 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0], 49 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0], 50 | [10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0], 51 | ]; 52 | $metadatas = [ 53 | ['url' => 'https://example.com/test1'], 54 | ['url' => 'https://example.com/test2'], 55 | ['url' => 'https://example.com/test3'], 56 | ]; 57 | 58 | $collection->add($ids, $embeddings, $metadatas); 59 | 60 | // Search for similar embeddings 61 | $queryResponse = $collection->query( 62 | queryEmbeddings: [ 63 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] 64 | ], 65 | nResults: 2 66 | ); 67 | 68 | // Print results 69 | echo $queryResponse->ids[0][0]; // test1 70 | echo $queryResponse->ids[0][1]; // test2 71 | 72 | 73 | ``` 74 | 75 | ## Requirements 76 | 77 | - PHP 8.1 or higher 78 | - ChromaDB 0.4.0 or higher running in client/server mode 79 | 80 | ## Running ChromaDB 81 | 82 | In order to use this library, you need to have ChromaDB running somewhere. You can either run it locally or in the 83 | cloud. 84 | (Chroma doesn't support cloud yet, but it will soon.) 85 | 86 | For now, ChromaDB can only run in-memory in Python. You can however run it in client/server mode by either running the 87 | python 88 | project or using the docker image (recommended). 89 | 90 | To run the docker image, you can use the following command: 91 | 92 | ```bash 93 | docker run -p 8000:8000 chromadb/chroma 94 | ``` 95 | 96 | You can also pass in some environment variables using a `.env` file: 97 | 98 | ```bash 99 | docker run -p 8000:8000 --env-file .env chromadb/chroma 100 | ``` 101 | 102 | Or if you prefer using a docker-compose file, you can use the following: 103 | 104 | ```yaml 105 | version: '3.9' 106 | 107 | services: 108 | chroma: 109 | image: 'chromadb/chroma' 110 | ports: 111 | - '8000:8000' 112 | volumes: 113 | - chroma-data:/chroma/chroma 114 | 115 | volumes: 116 | chroma-data: 117 | driver: local 118 | ``` 119 | 120 | And then run it using: 121 | 122 | ```bash 123 | docker-compose up -d 124 | ``` 125 | 126 | (Check out the [Chroma Documentation](https://docs.trychroma.com/deployment) for more information on how to run 127 | ChromaDB.) 128 | 129 | Either way, you can now access ChromaDB at `http://localhost:8000`. 130 | 131 | ## Installation 132 | 133 | ```bash 134 | composer require codewithkyrian/chromadb-php 135 | ``` 136 | 137 | ## Usage 138 | 139 | ### Connecting to ChromaDB 140 | 141 | ```php 142 | use Codewithkyrian\ChromaDB\ChromaDB; 143 | 144 | $chroma = ChromaDB::client(); 145 | 146 | ``` 147 | 148 | By default, ChromaDB will try to connect to `http://localhost:8000` using the default database name `default_database` 149 | and default tenant name `default_tenant`. You can however change these values by constructing the client using the 150 | factory method: 151 | 152 | ```php 153 | use Codewithkyrian\ChromaDB\ChromaDB; 154 | 155 | $chroma = ChromaDB::factory() 156 | ->withHost('http://localhost') 157 | ->withPort(8000) 158 | ->withDatabase('new_database') 159 | ->withTenant('new_tenant') 160 | ->connect(); 161 | ``` 162 | 163 | If the tenant or database doesn't exist, the package will automatically create them for you. 164 | 165 | ### Authentication 166 | 167 | ChromaDB supports static token-based authentication. To use it, you need to start the Chroma server passing the required 168 | environment variables as stated in the documentation. If you're using the docker image, you can pass in the environment 169 | variables using the `--env` flag or by using a `.env` file and for the docker-compose file, you can use the `env_file` 170 | option, or pass in the environment variables directly like so: 171 | 172 | ```yaml 173 | version: '3.9' 174 | 175 | services: 176 | chroma: 177 | image: 'chromadb/chroma' 178 | ports: 179 | - '8000:8000' 180 | environment: 181 | - CHROMA_SERVER_AUTHN_CREDENTIALS=test-token 182 | - CHROMA_SERVER_AUTHN_PROVIDER=chromadb.auth.token_authn.TokenAuthenticationServerProvider 183 | 184 | ... 185 | ``` 186 | 187 | You can then connect to ChromaDB using the factory method: 188 | 189 | ```php 190 | use Codewithkyrian\ChromaDB\ChromaDB; 191 | 192 | $chroma = ChromaDB::factory() 193 | ->withAuthToken('test-token') 194 | ->connect(); 195 | ``` 196 | 197 | ### Getting the version 198 | 199 | ```php 200 | 201 | echo $chroma->version(); // 0.4.0 202 | 203 | ``` 204 | 205 | ### Creating a Collection 206 | 207 | Creating a collection is as simple as calling the `createCollection` method on the client and passing in the name of 208 | the collection. 209 | 210 | ```php 211 | 212 | $collection = $chroma->createCollection('test-collection'); 213 | 214 | ``` 215 | 216 | If the collection already exists in the database, the package will throw an exception. 217 | 218 | ### Inserting Documents 219 | 220 | ```php 221 | $ids = ['test1', 'test2', 'test3']; 222 | $embeddings = [ 223 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0], 224 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0], 225 | [10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0], 226 | ]; 227 | $metadatas = [ 228 | ['url' => 'https://example.com/test1'], 229 | ['url' => 'https://example.com/test2'], 230 | ['url' => 'https://example.com/test3'], 231 | ]; 232 | 233 | $collection->add($ids, $embeddings, $metadatas); 234 | ``` 235 | 236 | To insert documents into a collection, you need to provide the following: 237 | 238 | - `ids`: An array of document ids. The ids must be unique and must be strings. 239 | - `embeddings`: An array of document embeddings. The embeddings must be a 1D array of floats with a consistent length. You 240 | can compute the embeddings using any embedding model of your choice (just make sure that's what you use when querying as 241 | well). 242 | - `metadatas`: An array of document metadatas. The metadatas must be an array of key-value pairs. 243 | 244 | If you don't have the embeddings, you can pass in the documents and provide an embedding function that will be used to 245 | compute the embeddings for you. 246 | 247 | ### Passing in Embedding Function 248 | 249 | To use an embedding function, you need to pass it in as an argument when creating the collection: 250 | 251 | ```php 252 | use CodeWithKyrian\ChromaDB\EmbeddingFunction\EmbeddingFunctionInterface; 253 | 254 | $embeddingFunction = new OpenAIEmbeddingFunction('api-key', 'org-id', 'model-name'); 255 | 256 | $collection = $chroma->createCollection('test-collection', embeddingFunction: $embeddingFunction); 257 | ``` 258 | 259 | The embedding function must be an instance of `EmbeddingFunctionInterface`. There are a few built-in embedding functions 260 | that you can use: 261 | 262 | - `OpenAIEmbeddingFunction`: This embedding function uses the OpenAI API to compute the embeddings. You can use it like 263 | this: 264 | ```php 265 | use CodeWithKyrian\Chroma\EmbeddingFunction\OpenAIEmbeddingFunction; 266 | 267 | $embeddingFunction = new OpenAIEmbeddingFunction('api-key', 'org-id', 'model-name'); 268 | 269 | $collection = $chromaDB->createCollection('test-collection', embeddingFunction: $embeddingFunction); 270 | ``` 271 | You can get your OpenAI API key and organization id from your [OpenAI dashboard](https://beta.openai.com/), and you 272 | can omit the organization id if your API key doesn't belong to an organization. The model name is optional as well and 273 | defaults to `text-embedding-ada-002` 274 | 275 | - `JinaEmbeddingFunction`: This is a wrapper for the Jina Embedding models. You can use by passing your Jina API key and 276 | the desired model. THis defaults to `jina-embeddings-v2-base-en` 277 | ```php 278 | use Codewithkyrian\ChromaDB\Embeddings\JinaEmbeddingFunction; 279 | 280 | $embeddingFunction = new JinaEmbeddingFunction('api-key'); 281 | 282 | $collection = $chromaDB->createCollection('test-collection', embeddingFunction: $embeddingFunction); 283 | ``` 284 | 285 | - `HuggingFaceEmbeddingServerFunction`: This embedding function is a wrapper around the HuggingFace Text Embedding 286 | Server. Before using it, you need to have 287 | the [HuggingFace Embedding Server](https://github.com/huggingface/text-embeddings-inference) running somewhere locally. Here's how you can use it: 288 | ```php 289 | use CodeWithKyrian\Chroma\EmbeddingFunction\HuggingFaceEmbeddingFunction; 290 | 291 | $embeddingFunction = new HuggingFaceEmbeddingFunction('api-key', 'model-name'); 292 | 293 | $collection = $chromaDB->createCollection('test-collection', embeddingFunction: $embeddingFunction); 294 | ``` 295 | 296 | Besides the built-in embedding functions, you can also create your own embedding function by implementing 297 | the `EmbeddingFunction` interface (including Anonymous Classes): 298 | 299 | ```php 300 | use CodeWithKyrian\ChromaDB\EmbeddingFunction\EmbeddingFunctionInterface; 301 | 302 | $embeddingFunction = new class implements EmbeddingFunctionInterface { 303 | public function generate(array $texts): array 304 | { 305 | // Compute the embeddings here and return them as an array of arrays 306 | } 307 | }; 308 | 309 | $collection = $chroma->createCollection('test-collection', embeddingFunction: $embeddingFunction); 310 | ``` 311 | 312 | > The embedding function will be called for each batch of documents that are inserted into the collection, and must be 313 | > provided either when creating the collection or when querying the collection. If you don't provide an embedding 314 | > function, and you don't provide the embeddings, the package will throw an exception. 315 | 316 | ### Inserting Documents into a Collection with an Embedding Function 317 | 318 | ```php 319 | $ids = ['test1', 'test2', 'test3']; 320 | $documents = [ 321 | 'This is a test document', 322 | 'This is another test document', 323 | 'This is yet another test document', 324 | ]; 325 | $metadatas = [ 326 | ['url' => 'https://example.com/test1'], 327 | ['url' => 'https://example.com/test2'], 328 | ['url' => 'https://example.com/test3'], 329 | ]; 330 | 331 | $collection->add( 332 | ids: $ids, 333 | documents: $documents, 334 | metadatas: $metadatas 335 | ); 336 | ``` 337 | 338 | ### Getting a Collection 339 | 340 | ```php 341 | $collection = $chromaDB->getCollection('test-collection'); 342 | ``` 343 | 344 | Or with an embedding function: 345 | 346 | ```php 347 | $collection = $chromaDB->getCollection('test-collection', embeddingFunction: $embeddingFunction); 348 | ``` 349 | 350 | > Make sure that the embedding function you provide is the same one that was used when creating the collection. 351 | 352 | ### Counting the items in a collection 353 | 354 | ```php 355 | $collection->count() // 2 356 | ``` 357 | 358 | ### Updating a collection 359 | 360 | ```php 361 | $collection->update( 362 | ids: ['test1', 'test2', 'test3'], 363 | embeddings: [ 364 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0], 365 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0], 366 | [10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0], 367 | ], 368 | metadatas: [ 369 | ['url' => 'https://example.com/test1'], 370 | ['url' => 'https://example.com/test2'], 371 | ['url' => 'https://example.com/test3'], 372 | ] 373 | ); 374 | ``` 375 | 376 | ### Deleting Documents 377 | 378 | ```php 379 | $collection->delete(['test1', 'test2', 'test3']); 380 | ``` 381 | 382 | ### Querying a Collection 383 | 384 | ```php 385 | $queryResponse = $collection->query( 386 | queryEmbeddings: [ 387 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] 388 | ], 389 | nResults: 2 390 | ); 391 | 392 | echo $queryResponse->ids[0][0]; // test1 393 | echo $queryResponse->ids[0][1]; // test2 394 | ``` 395 | 396 | To query a collection, you need to provide the following: 397 | 398 | - `queryEmbeddings` (optional): An array of query embeddings. The embeddings must be a 1D array of floats. You 399 | can compute the embeddings using any embedding model of your choice (just make sure that's what you use when inserting 400 | as 401 | well). 402 | - `nResults`: The number of results to return. Defaults to 10. 403 | - `queryTexts` (optional): An array of query texts. The texts must be strings. You can omit this if you provide the 404 | embeddings. Here's 405 | an example: 406 | ```php 407 | $queryResponse = $collection->query( 408 | queryTexts: [ 409 | 'This is a test document' 410 | ], 411 | nResults: 2 412 | ); 413 | 414 | echo $queryResponse->ids[0][0]; // test1 415 | echo $queryResponse->ids[0][1]; // test2 416 | ``` 417 | - `where` (optional): The where clause to use to filter items based on their metadata. Here's an example: 418 | ```php 419 | $queryResponse = $collection->query( 420 | queryEmbeddings: [ 421 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] 422 | ], 423 | nResults: 2, 424 | where: [ 425 | 'url' => 'https://example.com/test1' 426 | ] 427 | ); 428 | 429 | echo $queryResponse->ids[0][0]; // test1 430 | ``` 431 | The where clause must be an array of key-value pairs. The key must be a string, and the value can be a string or 432 | an array of valid filter values. Here are the valid filters (`$eq`, `$ne`, `$in`, `$nin`, `$gt`, `$gte`, `$lt`, 433 | `$lte`): 434 | - `$eq`: Equals 435 | - `$ne`: Not equals 436 | - `$gt`: Greater than 437 | - `$gte`: Greater than or equal to 438 | - `$lt`: Less than 439 | - `$lte`: Less than or equal to 440 | 441 | Here's an example: 442 | ```php 443 | $queryResponse = $collection->query( 444 | queryEmbeddings: [ 445 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] 446 | ], 447 | nResults: 2, 448 | where: [ 449 | 'url' => [ 450 | '$eq' => 'https://example.com/test1' 451 | ] 452 | ] 453 | ); 454 | ``` 455 | You can also use multiple filters: 456 | ```php 457 | $queryResponse = $collection->query( 458 | queryEmbeddings: [ 459 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] 460 | ], 461 | nResults: 2, 462 | where: [ 463 | 'url' => [ 464 | '$eq' => 'https://example.com/test1' 465 | ], 466 | 'title' => [ 467 | '$ne' => 'Test 1' 468 | ] 469 | ] 470 | ); 471 | ``` 472 | - `whereDocument` (optional): The where clause to use to filter items based on their document. Here's an example: 473 | ```php 474 | $queryResponse = $collection->query( 475 | queryEmbeddings: [ 476 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] 477 | ], 478 | nResults: 2, 479 | whereDocument: [ 480 | 'text' => 'This is a test document' 481 | ] 482 | ); 483 | 484 | echo $queryResponse->ids[0][0]; // test1 485 | ``` 486 | The where clause must be an array of key-value pairs. The key must be a string, and the value can be a string or 487 | an array of valid filter values. In this case, only two filtering keys are supported - `$contains` 488 | and `$not_contains`. 489 | 490 | Here's an example: 491 | ```php 492 | $queryResponse = $collection->query( 493 | queryEmbeddings: [ 494 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] 495 | ], 496 | nResults: 2, 497 | whereDocument: [ 498 | 'text' => [ 499 | '$contains' => 'test document' 500 | ] 501 | ] 502 | ); 503 | ``` 504 | - `include` (optional): An array of fields to include in the response. Possible values 505 | are `embeddings`, `documents`, `metadatas` and `distances`. It defaults to `embeddings` 506 | and `metadatas` (`documents` are not included by default because they can be large). 507 | ```php 508 | $queryResponse = $collection->query( 509 | queryEmbeddings: [ 510 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] 511 | ], 512 | nResults: 2, 513 | include: ['embeddings'] 514 | ); 515 | ``` 516 | `distances` is only valid for querying and not for getting. It returns the distances between the query embeddings 517 | and the embeddings of the results. 518 | 519 | Other relevant information about querying and retrieving a collection can be found in the [ChromaDB Documentation](https://docs.trychroma.com/usage-guide). 520 | 521 | ### Deleting items in a collection 522 | 523 | To delete the documents in a collection, pass in an array of the ids of the items: 524 | 525 | ```php 526 | $collection->delete(['test1', 'test2']); 527 | 528 | $collection->count() // 1 529 | ``` 530 | 531 | Passing the ids is optional. You can delete items from a collection using a where filter: 532 | 533 | ```php 534 | $collection->add( 535 | ['test1', 'test2', 'test3'], 536 | [ 537 | [1.0, 2.0, 3.0, 4.0, 5.0], 538 | [6.0, 7.0, 8.0, 9.0, 10.0], 539 | [11.0, 12.0, 13.0, 14.0, 15.0], 540 | ], 541 | [ 542 | ['some' => 'metadata1'], 543 | ['some' => 'metadata2'], 544 | ['some' => 'metadata3'], 545 | ] 546 | ); 547 | 548 | $collection->delete( 549 | where: [ 550 | 'some' => 'metadata1' 551 | ] 552 | ); 553 | 554 | $collection->count() // 2 555 | ``` 556 | 557 | ### Deleting a collection 558 | 559 | Deleting a collection is as simple as passing in the name of the collection to be deleted. 560 | 561 | ```php 562 | $chroma->deleteCollection('test_collection'); 563 | ``` 564 | 565 | ## Testing 566 | 567 | ``` 568 | // Run chroma by running the docker compose file in the repo 569 | docker compose up -d 570 | 571 | composer test 572 | ``` 573 | 574 | ## Contributors 575 | 576 | - [Kyrian Obikwelu](https://github.com/CodeWithKyrian) 577 | - Other contributors are welcome. 578 | 579 | ## License 580 | 581 | This project is licensed under the MIT License. See 582 | the [LICENSE](https://github.com/codewithkyrian/chromadb-php/blob/main/LICENSE) file for more information. 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codewithkyrian/chromadb-php", 3 | "description": "A PHP client for the Chroma Open Source Embedding Database", 4 | "keywords": ["chromadb", "php", "embedding", "database", "vectors", "semantic", "search", "chroma", "open-source"], 5 | "type": "library", 6 | "license": "MIT", 7 | "require": { 8 | "php": "^8.1", 9 | "guzzlehttp/guzzle": "^7.0" 10 | }, 11 | "require-dev": { 12 | "pestphp/pest": "^2.19", 13 | "symfony/var-dumper": "^6.3", 14 | "mockery/mockery": "^1.6" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "Codewithkyrian\\ChromaDB\\": "src/" 19 | } 20 | }, 21 | "authors": [ 22 | { 23 | "name": "Kyrian Obikwelu", 24 | "email": "kyrianobikwelu@gmail.com" 25 | } 26 | ], 27 | "config": { 28 | "allow-plugins": { 29 | "pestphp/pest-plugin": true 30 | } 31 | }, 32 | "scripts": { 33 | "test": "vendor/bin/pest", 34 | "test:coverage": "XDEBUG_MODE=coverage ./vendor/bin/pest --coverage" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | chroma_wo_auth: 5 | image: 'chromadb/chroma:0.5.0' 6 | ports: 7 | - '8000:8000' 8 | 9 | chroma_w_auth: 10 | image: 'chromadb/chroma:0.5.0' 11 | ports: 12 | - '8001:8000' 13 | environment: 14 | CHROMA_SERVER_AUTHN_CREDENTIALS: 'test-token' 15 | CHROMA_SERVER_AUTHN_PROVIDER: 'chromadb.auth.token_authn.TokenAuthenticationServerProvider' -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock -------------------------------------------------------------------------------- /examples/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kyrian/examples", 3 | "type": "project", 4 | "autoload": { 5 | "psr-4": { 6 | } 7 | }, 8 | "authors": [ 9 | { 10 | "name": "Kyrian Obikwelu", 11 | "email": "koshnawaza@gmail.com" 12 | } 13 | ], 14 | "repositories": [ 15 | { 16 | "type": "path", 17 | "url": "../", 18 | "options": { 19 | "symlink": true 20 | } 21 | } 22 | ], 23 | "require": { 24 | "codewithkyrian/chromadb-php": "@dev", 25 | "symfony/var-dumper": "^6.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/index.php: -------------------------------------------------------------------------------- 1 | withDatabase('test_database') 13 | ->withTenant('test_tenant') 14 | ->connect(); 15 | 16 | $chroma->deleteAllCollections(); 17 | 18 | $embeddingFunction = new OllamaEmbeddingFunction(); 19 | 20 | $collection = $chroma->createCollection( 21 | name: 'test_collection', 22 | embeddingFunction: $embeddingFunction 23 | ); 24 | 25 | 26 | $collection->add( 27 | ids: ['1', '2', '3'], 28 | documents: ['He seems very happy', 'He was very sad when we last talked', 'She made him angry'] 29 | ); 30 | 31 | $queryResponse = $collection->query( 32 | queryTexts: ['She annoyed him'], 33 | include: ['documents', 'distances'] 34 | ); 35 | 36 | dd($queryResponse->documents[0], $queryResponse->distances[0]); 37 | 38 | 39 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | ./src 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/ChromaDB.php: -------------------------------------------------------------------------------- 1 | connect(); 13 | } 14 | 15 | /** 16 | * Creates a new factory instance to configure a custom Alchemy Client 17 | */ 18 | public static function factory(): Factory 19 | { 20 | return new Factory(); 21 | } 22 | 23 | /** 24 | * Resets the database. This will delete all collections and entries and 25 | * return true if the database was reset successfully. 26 | */ 27 | public static function reset() : bool 28 | { 29 | return (new Factory())->createApiClient()->reset(); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | initDatabaseAndTenant(); 22 | } 23 | 24 | 25 | public function initDatabaseAndTenant(): void 26 | { 27 | try { 28 | $this->apiClient->getTenant($this->tenant); 29 | } catch (ChromaNotFoundException) { 30 | $createTenantRequest = new Generated\Requests\CreateTenantRequest($this->tenant); 31 | $this->apiClient->createTenant($createTenantRequest); 32 | } 33 | 34 | try { 35 | $this->apiClient->getDatabase($this->database, $this->tenant); 36 | } catch (ChromaNotFoundException) { 37 | $createDatabaseRequest = new Generated\Requests\CreateDatabaseRequest($this->database); 38 | $this->apiClient->createDatabase($this->tenant, $createDatabaseRequest); 39 | } 40 | } 41 | 42 | /** 43 | * Returns the version of the Chroma API. 44 | */ 45 | public function version(): string 46 | { 47 | return $this->apiClient->version(); 48 | } 49 | 50 | /** 51 | * Returns the current time in nanoseconds since epoch. This is useful for 52 | * checking if the server is alive. 53 | */ 54 | public function heartbeat(): int 55 | { 56 | $res = $this->apiClient->heartbeat(); 57 | 58 | return $res['nanosecond heartbeat'] ?? 0; 59 | } 60 | 61 | /** 62 | * Lists all collections. 63 | * 64 | * @return Collection[] 65 | */ 66 | public function listCollections(): array 67 | { 68 | return $this->apiClient->listCollections($this->database, $this->tenant); 69 | } 70 | 71 | 72 | /** 73 | * Creates a new collection with the specified properties. 74 | * 75 | * @param string $name The name of the collection. 76 | * @param ?array $metadata Optional metadata associated with the collection. 77 | * @param ?EmbeddingFunction $embeddingFunction Optional custom embedding function for the collection. 78 | * 79 | * @return CollectionResource 80 | */ 81 | public function createCollection(string $name, ?array $metadata = null, ?EmbeddingFunction $embeddingFunction = null): CollectionResource 82 | { 83 | $request = new Generated\Requests\CreateCollectionRequest($name, $metadata); 84 | 85 | $collection = $this->apiClient->createCollection($this->database, $this->tenant, $request); 86 | 87 | 88 | return CollectionResource::make( 89 | $collection, 90 | $this->database, 91 | $this->tenant, 92 | $embeddingFunction, 93 | $this->apiClient 94 | ); 95 | } 96 | 97 | /** 98 | * Gets or creates a collection with the specified properties. 99 | * 100 | * @param string $name The name of the collection. 101 | * @param ?array $metadata Optional metadata associated with the collection. 102 | * @param ?EmbeddingFunction $embeddingFunction Optional custom embedding function for the collection. 103 | * 104 | * @return CollectionResource 105 | */ 106 | public function getOrCreateCollection(string $name, ?array $metadata = null, ?EmbeddingFunction $embeddingFunction = null): CollectionResource 107 | { 108 | $request = new Generated\Requests\CreateCollectionRequest($name, $metadata, true); 109 | 110 | $collection = $this->apiClient->createCollection($this->database, $this->tenant, $request); 111 | 112 | return CollectionResource::make( 113 | $collection, 114 | $this->database, 115 | $this->tenant, 116 | $embeddingFunction, 117 | $this->apiClient 118 | ); 119 | } 120 | 121 | /** 122 | * Gets a collection with the specified name. Will raise an exception if the 123 | * collection does not exist. 124 | * 125 | * @param string $name The name of the collection. 126 | * @param ?EmbeddingFunction $embeddingFunction Optional custom embedding function for the collection. 127 | * 128 | * @return CollectionResource 129 | */ 130 | public function getCollection(string $name, ?EmbeddingFunction $embeddingFunction = null): CollectionResource 131 | { 132 | $collection = $this->apiClient->getCollection($name, $this->database, $this->tenant); 133 | 134 | return CollectionResource::make( 135 | $collection, 136 | $this->database, 137 | $this->tenant, 138 | $embeddingFunction, 139 | $this->apiClient 140 | ); 141 | } 142 | 143 | /** 144 | * Deletes a collection with the specified name. 145 | * 146 | * @param string $name The name of the collection. 147 | */ 148 | public function deleteCollection(string $name): void 149 | { 150 | $this->apiClient->deleteCollection($name, $this->database, $this->tenant); 151 | } 152 | 153 | /** 154 | * De 155 | */ 156 | public function deleteAllCollections(): void 157 | { 158 | $collections = $this->listCollections(); 159 | 160 | foreach ($collections as $collection) { 161 | $this->deleteCollection($collection->name); 162 | } 163 | } 164 | 165 | 166 | 167 | } -------------------------------------------------------------------------------- /src/Embeddings/EmbeddingFunction.php: -------------------------------------------------------------------------------- 1 | $this->baseUrl, 24 | 'headers' => [ 25 | 'Content-Type' => 'application/json', 26 | ] 27 | ]); 28 | 29 | try { 30 | $response = $client->post('embed', [ 31 | 'json' => [ 32 | 'inputs' => $texts, 33 | ] 34 | ]); 35 | } catch (GuzzleException $e) { 36 | throw new \RuntimeException('Failed to generate embeddings', 0, $e); 37 | } 38 | 39 | return json_decode($response->getBody()->getContents(), true); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Embeddings/JinaEmbeddingFunction.php: -------------------------------------------------------------------------------- 1 | client = new Client([ 23 | 'base_uri' => 'https://api.jina.ai/v1/', 24 | 'headers' => [ 25 | 'Authorization' => "Bearer $this->apiKey", 26 | 'Content-Type' => 'application/json', 27 | 'Accept-Encoding' => 'identity', 28 | ] 29 | ]); 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | public function generate(array $texts): array 36 | { 37 | try { 38 | $response = $this->client->post('embeddings', [ 39 | 'json' => [ 40 | 'model' => $this->model, 41 | 'input' => $texts, 42 | ] 43 | ]); 44 | 45 | $result = json_decode($response->getBody()->getContents(), true); 46 | $embeddings = $result['data']; 47 | usort($embeddings, fn($a, $b) => $a['index'] <=> $b['index']); 48 | 49 | return array_map(fn($embedding) => $embedding['embedding'], $embeddings); 50 | } catch (ClientExceptionInterface $e) { 51 | throw new \RuntimeException("Error calling Jina AI API: {$e->getMessage()}", 0, $e); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/Embeddings/MistralAIEmbeddingFunction.php: -------------------------------------------------------------------------------- 1 | "Bearer $this->apiKey", 23 | 'Content-Type' => 'application/json', 24 | ]; 25 | 26 | 27 | $this->client = new Client([ 28 | 'base_uri' => 'https://api.mistral.ai/v1/', 29 | 'headers' => $headers 30 | ]); 31 | } 32 | 33 | /** 34 | * @inheritDoc 35 | */ 36 | public function generate(array $texts): array 37 | { 38 | try { 39 | $response = $this->client->post('embeddings', [ 40 | 'json' => [ 41 | 'model' => $this->model, 42 | 'input' => $texts, 43 | ] 44 | ]); 45 | 46 | $result = json_decode($response->getBody()->getContents(), true); 47 | $embeddings = $result['data']; 48 | usort($embeddings, fn($a, $b) => $a['index'] <=> $b['index']); 49 | 50 | return array_map(fn($embedding) => $embedding['embedding'], $embeddings); 51 | } catch (ClientExceptionInterface $e) { 52 | throw new \RuntimeException("Error calling MistralAI API: {$e->getMessage()}", 0, $e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Embeddings/OllamaEmbeddingFunction.php: -------------------------------------------------------------------------------- 1 | client = new Client([ 20 | 'base_uri' => $this->baseUrl, 21 | 'headers' => [ 22 | 'Content-Type' => 'application/json', 23 | ] 24 | ]); 25 | } 26 | 27 | /** 28 | * @inheritDoc 29 | */ 30 | public function generate(array $texts): array 31 | { 32 | try { 33 | $embeddings = []; 34 | 35 | foreach ($texts as $text) { 36 | $response = $this->client->post('api/embeddings', [ 37 | 'json' => [ 38 | 'prompt' => $text, 39 | 'model' => $this->model, 40 | ] 41 | ]); 42 | 43 | $result = json_decode($response->getBody()->getContents(), true); 44 | 45 | $embeddings[] = $result['embedding']; 46 | } 47 | 48 | return $embeddings; 49 | } catch (\Exception $e) { 50 | throw new \RuntimeException('Failed to generate embeddings', 0, $e); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/Embeddings/OpenAIEmbeddingFunction.php: -------------------------------------------------------------------------------- 1 | "Bearer $this->apiKey", 23 | 'Content-Type' => 'application/json', 24 | ]; 25 | 26 | if (!empty($this->organization)) { 27 | $headers['OpenAI-Organization'] = $this->organization; 28 | } 29 | 30 | $this->client = new Client([ 31 | 'base_uri' => 'https://api.openai.com/v1/', 32 | 'headers' => $headers 33 | ]); 34 | } 35 | 36 | /** 37 | * @inheritDoc 38 | */ 39 | public function generate(array $texts): array 40 | { 41 | try { 42 | $response = $this->client->post('embeddings', [ 43 | 'json' => [ 44 | 'model' => $this->model, 45 | 'input' => $texts, 46 | ] 47 | ]); 48 | 49 | $result = json_decode($response->getBody()->getContents(), true); 50 | $embeddings = $result['data']; 51 | usort($embeddings, fn($a, $b) => $a['index'] <=> $b['index']); 52 | 53 | return array_map(fn($embedding) => $embedding['embedding'], $embeddings); 54 | } catch (ClientExceptionInterface $e) { 55 | throw new \RuntimeException("Error calling OpenAI API: {$e->getMessage()}", 0, $e); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | host = $host; 57 | return $this; 58 | } 59 | 60 | /** 61 | * The port of the client to use for the requests. 62 | */ 63 | public function withPort(int $port): self 64 | { 65 | $this->port = $port; 66 | return $this; 67 | } 68 | 69 | /** 70 | * The database to use for the instance. 71 | */ 72 | public function withDatabase(string $database): self 73 | { 74 | $this->database = $database; 75 | return $this; 76 | } 77 | 78 | /** 79 | * The tenant to use for the instance. 80 | */ 81 | public function withTenant(string $tenant): self 82 | { 83 | $this->tenant = $tenant; 84 | return $this; 85 | } 86 | 87 | /** 88 | * The bearer token used to authenticate requests. 89 | */ 90 | public function withAuthToken(string $authToken): self 91 | { 92 | $this->authToken = $authToken; 93 | return $this; 94 | } 95 | 96 | /** 97 | * The http client to use for the requests. 98 | */ 99 | public function withHttpClient(\GuzzleHttp\Client $httpClient): self 100 | { 101 | $this->httpClient = $httpClient; 102 | return $this; 103 | } 104 | 105 | public function connect(): Client 106 | { 107 | $this->apiClient = $this->createApiClient(); 108 | 109 | return new Client($this->apiClient, $this->database, $this->tenant); 110 | } 111 | 112 | public function createApiClient() : ChromaApiClient 113 | { 114 | $this->baseUrl = $this->host . ':' . $this->port; 115 | 116 | $headers = [ 117 | 'Content-Type' => 'application/json', 118 | 'Accept' => 'application/json', 119 | ]; 120 | 121 | if (!empty($this->authToken)) { 122 | $headers['Authorization'] = 'Bearer ' . $this->authToken; 123 | } 124 | 125 | $this->httpClient ??= new \GuzzleHttp\Client([ 126 | 'base_uri' => $this->baseUrl, 127 | 'headers' => $headers, 128 | ]); 129 | 130 | return new ChromaApiClient($this->httpClient); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Generated/ChromaApiClient.php: -------------------------------------------------------------------------------- 1 | httpClient->get('/api/v1'); 45 | } catch (ClientExceptionInterface $e) { 46 | $this->handleChromaApiException($e); 47 | } 48 | return json_decode($response->getBody()->getContents(), true); 49 | } 50 | 51 | 52 | public function version(): string 53 | { 54 | try { 55 | $response = $this->httpClient->get('/api/v1/version'); 56 | 57 | // remove the quo 58 | return trim($response->getBody()->getContents(), '"'); 59 | } catch (ClientExceptionInterface $e) { 60 | $this->handleChromaApiException($e); 61 | } 62 | } 63 | 64 | public function heartbeat(): array 65 | { 66 | try { 67 | $response = $this->httpClient->get('/api/v1/heartbeat'); 68 | } catch (ClientExceptionInterface $e) { 69 | $this->handleChromaApiException($e); 70 | } 71 | return json_decode($response->getBody()->getContents(), true); 72 | } 73 | 74 | public function preFlightChecks(): mixed 75 | { 76 | try { 77 | $response = $this->httpClient->get('/api/v1/pre-flight-checks'); 78 | } catch (ClientExceptionInterface $e) { 79 | $this->handleChromaApiException($e); 80 | } 81 | return json_decode($response->getBody()->getContents(), true); 82 | } 83 | 84 | 85 | public function createDatabase(string $tenant, CreateDatabaseRequest $request): void 86 | { 87 | try { 88 | $this->httpClient->post('/api/v1/databases', [ 89 | 'json' => $request->toArray(), 90 | 'query' => [ 91 | 'tenant' => $tenant, 92 | ] 93 | ]); 94 | } catch (ClientExceptionInterface $e) { 95 | $this->handleChromaApiException($e); 96 | } 97 | } 98 | 99 | public function getDatabase(string $database, string $tenant): Database 100 | { 101 | try { 102 | $response = $this->httpClient->get("/api/v1/databases/$database", [ 103 | 'query' => [ 104 | 'tenant' => $tenant, 105 | ] 106 | ]); 107 | } catch (ClientExceptionInterface $e) { 108 | $this->handleChromaApiException($e); 109 | } 110 | 111 | $result = json_decode($response->getBody()->getContents(), true); 112 | 113 | return Database::make($result); 114 | } 115 | 116 | public function createTenant(CreateTenantRequest $request): void 117 | { 118 | try { 119 | $this->httpClient->post('/api/v1/tenants', [ 120 | 'json' => $request->toArray(), 121 | ]); 122 | } catch (ClientExceptionInterface $e) { 123 | $this->handleChromaApiException($e); 124 | } 125 | } 126 | 127 | public function getTenant(string $tenant): ?Tenant 128 | { 129 | try { 130 | $response = $this->httpClient->get("/api/v1/tenants/$tenant"); 131 | 132 | $result = json_decode($response->getBody()->getContents(), true); 133 | 134 | return Tenant::make($result); 135 | } catch (ClientExceptionInterface $e) { 136 | $this->handleChromaApiException($e); 137 | } 138 | 139 | } 140 | 141 | 142 | public function listCollections(string $database, string $tenant): array 143 | { 144 | try { 145 | $response = $this->httpClient->get('/api/v1/collections', [ 146 | 'query' => [ 147 | 'database' => $database, 148 | 'tenant' => $tenant, 149 | ] 150 | ]); 151 | } catch (ClientExceptionInterface $e) { 152 | $this->handleChromaApiException($e); 153 | } 154 | 155 | $result = json_decode($response->getBody()->getContents(), true); 156 | 157 | return array_map(function (array $item) { 158 | return Collection::make($item); 159 | }, $result); 160 | } 161 | 162 | public function createCollection(string $database, string $tenant, CreateCollectionRequest $request): Collection 163 | { 164 | try { 165 | $response = $this->httpClient->post('/api/v1/collections', [ 166 | 'json' => $request->toArray(), 167 | 'query' => [ 168 | 'database' => $database, 169 | 'tenant' => $tenant, 170 | ] 171 | ]); 172 | 173 | } catch (ClientExceptionInterface $e) { 174 | $this->handleChromaApiException($e); 175 | } 176 | 177 | $result = json_decode($response->getBody()->getContents(), true); 178 | 179 | return Collection::make($result); 180 | } 181 | 182 | public function getCollection(string $collectionId, string $database, string $tenant): Collection 183 | { 184 | try { 185 | $response = $this->httpClient->get("/api/v1/collections/$collectionId", [ 186 | 'query' => [ 187 | 'database' => $database, 188 | 'tenant' => $tenant, 189 | ] 190 | ]); 191 | } catch (ClientExceptionInterface $e) { 192 | $this->handleChromaApiException($e); 193 | } 194 | 195 | $result = json_decode($response->getBody()->getContents(), true); 196 | 197 | return Collection::make($result); 198 | } 199 | 200 | public function updateCollection(string $collectionId, UpdateCollectionRequest $request): void 201 | { 202 | try { 203 | $response = $this->httpClient->put("/api/v1/collections/$collectionId", [ 204 | 'json' => $request->toArray(), 205 | ]); 206 | } catch (ClientExceptionInterface $e) { 207 | $this->handleChromaApiException($e); 208 | } 209 | } 210 | 211 | public function deleteCollection(string $collectionId, string $database, string $tenant): void 212 | { 213 | try { 214 | $this->httpClient->delete("/api/v1/collections/$collectionId", [ 215 | 'query' => [ 216 | 'database' => $database, 217 | 'tenant' => $tenant, 218 | ] 219 | ]); 220 | } catch (ClientExceptionInterface $e) { 221 | $this->handleChromaApiException($e); 222 | } 223 | } 224 | 225 | public function add(string $collectionId, AddEmbeddingRequest $request): void 226 | { 227 | try { 228 | $this->httpClient->post("/api/v1/collections/$collectionId/add", [ 229 | 'json' => $request->toArray(), 230 | ]); 231 | } catch (ClientExceptionInterface $e) { 232 | $this->handleChromaApiException($e); 233 | } 234 | } 235 | 236 | public function update(string $collectionId, UpdateEmbeddingRequest $request): void 237 | { 238 | try { 239 | $this->httpClient->post("/api/v1/collections/$collectionId/update", [ 240 | 'json' => $request->toArray(), 241 | ]); 242 | } catch (ClientExceptionInterface $e) { 243 | $this->handleChromaApiException($e); 244 | } 245 | } 246 | 247 | public function upsert(string $collectionId, AddEmbeddingRequest $request): void 248 | { 249 | try { 250 | $this->httpClient->post("/api/v1/collections/$collectionId/upsert", [ 251 | 'json' => $request->toArray(), 252 | ]); 253 | } catch (ClientExceptionInterface $e) { 254 | $this->handleChromaApiException($e); 255 | } 256 | } 257 | 258 | public function get(string $collectionId, GetEmbeddingRequest $request): GetItemsResponse 259 | { 260 | try { 261 | $response = $this->httpClient->post("/api/v1/collections/$collectionId/get", [ 262 | 'json' => $request->toArray(), 263 | ]); 264 | } catch (ClientExceptionInterface $e) { 265 | $this->handleChromaApiException($e); 266 | } 267 | 268 | $result = json_decode($response->getBody()->getContents(), true); 269 | 270 | return GetItemsResponse::from($result); 271 | } 272 | 273 | public function delete(string $collectionId, DeleteEmbeddingRequest $request): void 274 | { 275 | try { 276 | $this->httpClient->post("/api/v1/collections/$collectionId/delete", [ 277 | 'json' => $request->toArray(), 278 | ]); 279 | } catch (ClientExceptionInterface $e) { 280 | $this->handleChromaApiException($e); 281 | } 282 | } 283 | 284 | public function count(string $collectionId): int 285 | { 286 | try { 287 | $response = $this->httpClient->get("/api/v1/collections/$collectionId/count"); 288 | } catch (ClientExceptionInterface $e) { 289 | $this->handleChromaApiException($e); 290 | } 291 | 292 | return json_decode($response->getBody()->getContents(), true); 293 | } 294 | 295 | public function getNearestNeighbors(string $collectionId, QueryEmbeddingRequest $request): QueryItemsResponse 296 | { 297 | try { 298 | $response = $this->httpClient->post("/api/v1/collections/$collectionId/query", [ 299 | 'json' => $request->toArray(), 300 | ]); 301 | } catch (ClientExceptionInterface $e) { 302 | $this->handleChromaApiException($e); 303 | } 304 | 305 | $result = json_decode($response->getBody()->getContents(), true); 306 | 307 | return QueryItemsResponse::from($result); 308 | } 309 | 310 | public function reset(): bool 311 | { 312 | try { 313 | $response = $this->httpClient->post('/api/v1/reset'); 314 | } catch (ClientExceptionInterface $e) { 315 | $this->handleChromaApiException($e); 316 | } 317 | 318 | return json_decode($response->getBody()->getContents(), true); 319 | } 320 | 321 | private function handleChromaApiException(\Exception|ClientExceptionInterface $e): void 322 | { 323 | if ($e instanceof ConnectException) { 324 | $context = $e->getHandlerContext(); 325 | $message = $context['error'] ?? $e->getMessage(); 326 | $code = $context['errno'] ?? $e->getCode(); 327 | throw new ChromaConnectionException($message, $code); 328 | } 329 | 330 | if ($e instanceof RequestException) { 331 | $errorString = $e->getResponse()->getBody()->getContents(); 332 | 333 | if (preg_match('/(?<={"\"error\"\:\")([^"]*)/', $errorString, $matches)) { 334 | $errorString = $matches[1]; 335 | } 336 | 337 | $error = json_decode($errorString, true); 338 | 339 | if ($error !== null) { 340 | 341 | // If the structure is 'error' => 'NotFoundError("Collection not found")' 342 | if (preg_match( 343 | '/^(?P\w+)\((?P.*)\)$/', 344 | $error['error'] ?? '', 345 | $matches) 346 | ) { 347 | if (isset($matches['message'])) { 348 | $error_type = $matches['error_type'] ?? 'UnknownError'; 349 | $message = $matches['message']; 350 | 351 | // Remove trailing and leading quotes 352 | if (str_starts_with($message, "'") && str_ends_with($message, "'")) { 353 | $message = substr($message, 1, -1); 354 | } 355 | 356 | ChromaException::throwSpecific($message, $error_type, $e->getCode()); 357 | } 358 | } 359 | 360 | // If the structure is 'detail' => 'Collection not found' 361 | if (isset($error['detail'])) { 362 | $message = $error['detail']; 363 | $error_type = ChromaException::inferTypeFromMessage($message); 364 | 365 | 366 | ChromaException::throwSpecific($message, $error_type, $e->getCode()); 367 | } 368 | 369 | // If the structure is {'error': 'Error Type', 'message' : 'Error message'} 370 | if (isset($error['error']) && isset($error['message'])) { 371 | ChromaException::throwSpecific($error['message'], $error['error'], $e->getCode()); 372 | } 373 | 374 | // If the structure is 'error' => 'Collection not found' 375 | if (isset($error['error'])) { 376 | $message = $error['error']; 377 | $error_type = ChromaException::inferTypeFromMessage($message); 378 | 379 | ChromaException::throwSpecific($message, $error_type, $e->getCode()); 380 | } 381 | } 382 | } 383 | 384 | throw new ChromaException($e->getMessage(), $e->getCode()); 385 | } 386 | } -------------------------------------------------------------------------------- /src/Generated/Exceptions/ChromaAuthorizationException.php: -------------------------------------------------------------------------------- 1 | new ChromaNotFoundException($message, $code), 15 | 'AuthorizationError' => new ChromaAuthorizationException($message, $code), 16 | 'ValueError' => new ChromaValueException($message, $code), 17 | 'UniqueConstraintError' => new ChromaUniqueConstraintException($message, $code), 18 | 'DimensionalityError' => new ChromaDimensionalityException($message, $code), 19 | 'InvalidCollection' => new ChromaInvalidCollectionException($message, $code), 20 | 'TypeError' => new ChromaTypeException($message, $code), 21 | default => new self($message, $code), 22 | }; 23 | } 24 | 25 | public static function inferTypeFromMessage(string $message): string 26 | { 27 | return match (true) { 28 | str_contains($message, 'NotFoundError') => 'NotFoundError', 29 | str_contains($message, 'AuthorizationError') => 'AuthorizationError', 30 | str_contains($message, 'Forbidden') => 'AuthorizationError', 31 | str_contains($message, 'UniqueConstraintError') => 'UniqueConstraintError', 32 | str_contains($message, 'ValueError') => 'ValueError', 33 | str_contains($message, 'dimensionality') => 'DimensionalityError', 34 | default => 'UnknownError', 35 | }; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Generated/Exceptions/ChromaInvalidCollectionException.php: -------------------------------------------------------------------------------- 1 | message}"; 32 | } 33 | 34 | public function toArray(): array 35 | { 36 | return [ 37 | 'loc' => $this->loc, 38 | 'message' => $this->message, 39 | 'type' => $this->type, 40 | ]; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Generated/Models/Collection.php: -------------------------------------------------------------------------------- 1 | $this->name, 32 | 'id' => $this->id, 33 | 'metadata' => $this->metadata, 34 | ]; 35 | } 36 | } -------------------------------------------------------------------------------- /src/Generated/Models/Database.php: -------------------------------------------------------------------------------- 1 | $this->id, 42 | 'name' => $this->name, 43 | 'tenant' => $this->tenant, 44 | ]; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/Generated/Models/Tenant.php: -------------------------------------------------------------------------------- 1 | $this->name, 32 | ]; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Generated/Requests/AddEmbeddingRequest.php: -------------------------------------------------------------------------------- 1 | > 25 | */ 26 | public readonly ?array $metadatas, 27 | 28 | /** 29 | * IDs of the items to add. 30 | * 31 | * @var string[] 32 | */ 33 | public readonly array $ids, 34 | 35 | /** 36 | * Optional documents of the items to add. 37 | * 38 | * @var string[] 39 | */ 40 | public readonly ?array $documents, 41 | 42 | public readonly ?array $images, 43 | 44 | ) 45 | { 46 | } 47 | 48 | public static function create(array $data): self 49 | { 50 | return new self( 51 | embeddings: $data['embeddings'] ?? null, 52 | metadatas: $data['metadatas'] ?? null, 53 | ids: $data['ids'], 54 | documents: $data['documents'] ?? null, 55 | images: $data['images'] ?? null, 56 | ); 57 | } 58 | 59 | public function toArray(): array 60 | { 61 | return [ 62 | 'embeddings' => $this->embeddings, 63 | 'metadatas' => $this->metadatas, 64 | 'ids' => $this->ids, 65 | 'documents' => $this->documents, 66 | ]; 67 | } 68 | } -------------------------------------------------------------------------------- /src/Generated/Requests/CreateCollectionRequest.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | public readonly ?array $metadata, 25 | 26 | /** 27 | * If true, will return existing collection if it exists. 28 | */ 29 | public readonly bool $getOrCreate = false, 30 | ) 31 | { 32 | } 33 | 34 | public static function create(array $data): self 35 | { 36 | return new self( 37 | name: $data['name'], 38 | metadata: $data['metadata'] ?? null, 39 | getOrCreate: $data['get_or_create'] ?? false, 40 | ); 41 | } 42 | 43 | public function toArray(): array 44 | { 45 | return [ 46 | 'name' => $this->name, 47 | 'metadata' => $this->metadata, 48 | 'get_or_create' => $this->getOrCreate, 49 | ]; 50 | } 51 | } -------------------------------------------------------------------------------- /src/Generated/Requests/CreateDatabaseRequest.php: -------------------------------------------------------------------------------- 1 | $this->name, 27 | ]; 28 | } 29 | } -------------------------------------------------------------------------------- /src/Generated/Requests/CreateTenantRequest.php: -------------------------------------------------------------------------------- 1 | $this->name, 27 | ]; 28 | } 29 | } -------------------------------------------------------------------------------- /src/Generated/Requests/DeleteEmbeddingRequest.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public readonly ?array $where, 24 | 25 | /** 26 | * Optional query condition to filter items to delete based on document content. 27 | * 28 | * @var array 29 | */ 30 | public readonly ?array $whereDocument, 31 | ) 32 | { 33 | } 34 | 35 | public static function create(array $data): self 36 | { 37 | return new self( 38 | ids: $data['ids'] ?? null, 39 | where: $data['where'] ?? null, 40 | whereDocument: $data['where_document'] ?? null, 41 | ); 42 | } 43 | 44 | public function toArray(): array 45 | { 46 | return [ 47 | 'ids' => $this->ids, 48 | 'where' => $this->where, 49 | 'where_document' => $this->whereDocument, 50 | ]; 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/Generated/Requests/GetEmbeddingRequest.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | public readonly ?array $where= null, 27 | 28 | /** 29 | * Optional where clause to filter items by. 30 | * 31 | * @var array 32 | */ 33 | public readonly ?array $whereDocument= null, 34 | 35 | /** 36 | * Sort items. 37 | */ 38 | public readonly ?string $sort= null, 39 | 40 | /** 41 | * Optional limit on the number of items to get. 42 | */ 43 | public readonly ?int $limit= null, 44 | 45 | /** 46 | * Optional offset on the number of items to get. 47 | */ 48 | public readonly ?int $offset= null, 49 | 50 | /** 51 | * Optional list of items to include in the response. 52 | * 53 | * @var string[] 54 | */ 55 | public readonly ?array $include= null, 56 | ) 57 | { 58 | } 59 | 60 | public static function create(array $data): self 61 | { 62 | return new self( 63 | ids: $data['ids'] ?? null, 64 | where: $data['where'] ?? null, 65 | whereDocument: $data['where_document'] ?? null, 66 | sort: $data['sort'] ?? null, 67 | limit: $data['limit'] ?? null, 68 | offset: $data['offset'] ?? null, 69 | include: $data['include'] ?? null, 70 | ); 71 | } 72 | 73 | public function toArray(): array 74 | { 75 | return [ 76 | 'ids' => $this->ids, 77 | 'where' => $this->where, 78 | 'whereDocument' => $this->whereDocument, 79 | 'sort' => $this->sort, 80 | 'limit' => $this->limit, 81 | 'offset' => $this->offset, 82 | 'include' => $this->include, 83 | ]; 84 | } 85 | } -------------------------------------------------------------------------------- /src/Generated/Requests/QueryEmbeddingRequest.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | public readonly ?array $where, 17 | 18 | /** 19 | * Optional query condition to filter results based on document content. 20 | * 21 | * @var array 22 | */ 23 | public readonly ?array $whereDocument, 24 | 25 | /** 26 | * Optional query condition to filter results based on embedding content. 27 | * 28 | * @var float[][] 29 | */ 30 | public readonly ?array $queryEmbeddings, 31 | 32 | /** 33 | * Optional number of results to return. Defaults to 10. 34 | */ 35 | public readonly ?int $nResults, 36 | 37 | /** 38 | * Optional list of items to include in the response. 39 | * 40 | * @var string[] 41 | */ 42 | public readonly ?array $include, 43 | ) 44 | { 45 | } 46 | 47 | public static function create(array $data): self 48 | { 49 | return new self( 50 | where: $data['where'] ?? null, 51 | whereDocument: $data['where_document'] ?? null, 52 | queryEmbeddings: $data['query_embeddings'] ?? null, 53 | nResults: $data['n_results'] ?? null, 54 | include: $data['include'] ?? null, 55 | ); 56 | } 57 | 58 | public function toArray(): array 59 | { 60 | return array_filter([ 61 | 'where' => $this->where, 62 | 'where_document' => $this->whereDocument, 63 | 'query_embeddings' => $this->queryEmbeddings, 64 | 'n_results' => $this->nResults, 65 | 'include' => $this->include, 66 | ], fn($value) => $value !== null); 67 | } 68 | } -------------------------------------------------------------------------------- /src/Generated/Requests/UpdateCollectionRequest.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | public readonly ?array $newMetadata, 22 | 23 | ) 24 | { 25 | } 26 | 27 | public static function create(array $data): self 28 | { 29 | return new self( 30 | newName: $data['new_name'] ?? null, 31 | newMetadata: $data['new_metadata'] ?? null, 32 | ); 33 | } 34 | 35 | public function toArray(): array 36 | { 37 | return array_filter([ 38 | 'new_name' => $this->newName, 39 | 'new_metadata' => $this->newMetadata, 40 | ]); 41 | } 42 | } -------------------------------------------------------------------------------- /src/Generated/Requests/UpdateEmbeddingRequest.php: -------------------------------------------------------------------------------- 1 | [] 30 | */ 31 | public readonly ?array $metadatas, 32 | 33 | /** 34 | * Optional documents of the items to update. 35 | * 36 | * @var string[] 37 | */ 38 | public readonly ?array $documents, 39 | 40 | /** 41 | * Optional uris of the items to update. 42 | * 43 | * @var string[] 44 | */ 45 | public readonly ?array $images, 46 | ) 47 | { 48 | } 49 | 50 | public static function create(array $data): self 51 | { 52 | return new self( 53 | embeddings: $data['embeddings'] ?? null, 54 | ids: $data['ids'], 55 | metadatas: $data['metadatas'] ?? null, 56 | documents: $data['documents'] ?? null, 57 | images: $data['images'] ?? null, 58 | ); 59 | } 60 | 61 | public function toArray(): array 62 | { 63 | return array_filter([ 64 | 'embeddings' => $this->embeddings, 65 | 'ids' => $this->ids, 66 | 'metadatas' => $this->metadatas, 67 | 'documents' => $this->documents, 68 | ]); 69 | } 70 | } -------------------------------------------------------------------------------- /src/Generated/Responses/GetItemsResponse.php: -------------------------------------------------------------------------------- 1 | [] 25 | */ 26 | public readonly ?array $metadatas, 27 | 28 | /** 29 | * List of embeddings of the items. 30 | * 31 | * @var float[][] 32 | */ 33 | public readonly ?array $embeddings, 34 | 35 | /** 36 | * List of documents of the items. 37 | * 38 | * @var string[] 39 | */ 40 | public readonly ?array $documents, 41 | ) 42 | { 43 | } 44 | 45 | public static function from(array $data): self 46 | { 47 | return new self( 48 | ids: $data['ids'], 49 | metadatas: $data['metadatas'] ?? null, 50 | embeddings: $data['embeddings'] ?? null, 51 | documents: $data['documents'] ?? null, 52 | ); 53 | } 54 | 55 | public function toArray(): array 56 | { 57 | return array_filter([ 58 | 'ids' => $this->ids, 59 | 'metadatas' => $this->metadatas, 60 | 'embeddings' => $this->embeddings, 61 | 'documents' => $this->documents, 62 | ]); 63 | } 64 | } -------------------------------------------------------------------------------- /src/Generated/Responses/QueryItemsResponse.php: -------------------------------------------------------------------------------- 1 | [][] 33 | */ 34 | public readonly ?array $metadatas, 35 | 36 | /** 37 | * List of documents of the items. 38 | * 39 | * @var string[][] 40 | */ 41 | public readonly ?array $documents, 42 | 43 | /** 44 | * List of data of the items. 45 | * 46 | * @var string[][] 47 | */ 48 | public readonly ?array $data, 49 | 50 | /** 51 | * List of uris of the items. 52 | * 53 | * @var string[][] 54 | */ 55 | public readonly ?array $uris, 56 | 57 | /** 58 | * List of distances of the items. 59 | * 60 | * @var float[][] 61 | */ 62 | public readonly ?array $distances, 63 | ) 64 | { 65 | } 66 | 67 | public static function from(array $data): self 68 | { 69 | return new self( 70 | ids: $data['ids'], 71 | embeddings: $data['embeddings'] ?? null, 72 | metadatas: $data['metadatas'] ?? null, 73 | documents: $data['documents'] ?? null, 74 | data: $data['data'] ?? null, 75 | uris: $data['uris'] ?? null, 76 | distances: $data['distances'] ?? null, 77 | ); 78 | } 79 | 80 | public function toArray(): array 81 | { 82 | return array_filter([ 83 | 'ids' => $this->ids, 84 | 'embeddings' => $this->embeddings, 85 | 'metadatas' => $this->metadatas, 86 | 'documents' => $this->documents, 87 | 'data' => $this->data, 88 | 'uris' => $this->uris, 89 | 'distances' => $this->distances, 90 | ]); 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/Resources/CollectionResource.php: -------------------------------------------------------------------------------- 1 | name, 66 | id: $collection->id, 67 | metadata: $collection->metadata, 68 | database: $database, 69 | tenant: $tenant, 70 | embeddingFunction: $embeddingFunction, 71 | apiClient: $apiClient, 72 | ); 73 | } 74 | 75 | /** 76 | * Add items to the collection. 77 | * 78 | * @param array $ids The IDs of the items to add. 79 | * @param ?array $embeddings The embeddings of the items to add (optional). 80 | * @param ?array $metadatas The metadatas of the items to add (optional). 81 | * @param ?array $documents The documents of the items to add (optional). 82 | * @param ?array $images The base64 encoded images of the items to add (optional). 83 | * @return void 84 | */ 85 | public function add( 86 | array $ids, 87 | ?array $embeddings = null, 88 | ?array $metadatas = null, 89 | ?array $documents = null, 90 | ?array $images = null 91 | ): void 92 | { 93 | $validated = $this->validate( 94 | ids: $ids, 95 | embeddings: $embeddings, 96 | metadatas: $metadatas, 97 | documents: $documents, 98 | images: $images, 99 | requireEmbeddingsOrDocuments: true, 100 | ); 101 | 102 | 103 | $request = new AddEmbeddingRequest( 104 | embeddings: $validated['embeddings'], 105 | metadatas: $validated['metadatas'], 106 | ids: $validated['ids'], 107 | documents: $validated['documents'], 108 | images: $validated['images'], 109 | ); 110 | 111 | 112 | $this->apiClient->add($this->id, $request); 113 | } 114 | 115 | 116 | /** 117 | * Update the embeddings, documents, and/or metadatas of existing items. 118 | * 119 | * @param array $ids The IDs of the items to update. 120 | * @param ?array $embeddings The embeddings of the items to update (optional). 121 | * @param ?array $metadatas The metadatas of the items to update (optional). 122 | * @param ?array $documents The documents of the items to update (optional). 123 | * @param ?array $images The base64 encoded images of the items to update (optional). 124 | * 125 | */ 126 | public function update( 127 | array $ids, 128 | ?array $embeddings = null, 129 | ?array $metadatas = null, 130 | ?array $documents = null, 131 | ?array $images = null 132 | ) 133 | { 134 | $validated = $this->validate( 135 | ids: $ids, 136 | embeddings: $embeddings, 137 | metadatas: $metadatas, 138 | documents: $documents, 139 | images: $images, 140 | requireEmbeddingsOrDocuments: false, 141 | ); 142 | 143 | $request = new UpdateEmbeddingRequest( 144 | embeddings: $validated['embeddings'], 145 | ids: $validated['ids'], 146 | metadatas: $validated['metadatas'], 147 | documents: $validated['documents'], 148 | images: $validated['images'], 149 | ); 150 | 151 | $this->apiClient->update($this->id, $request); 152 | } 153 | 154 | /** 155 | * Upsert items in the collection. 156 | * 157 | * @param array $ids The IDs of the items to upsert. 158 | * @param ?array $embeddings The embeddings of the items to upsert (optional). 159 | * @param ?array $metadatas The metadatas of the items to upsert (optional). 160 | * @param ?array $documents The documents of the items to upsert (optional). 161 | * @param ?array $images The base64 encoded images of the items to upsert (optional). 162 | * 163 | */ 164 | public function upsert( 165 | array $ids, 166 | ?array $embeddings = null, 167 | ?array $metadatas = null, 168 | ?array $documents = null, 169 | ?array $images = null 170 | ): void 171 | { 172 | $validated = $this->validate( 173 | ids: $ids, 174 | embeddings: $embeddings, 175 | metadatas: $metadatas, 176 | documents: $documents, 177 | images: $images, 178 | requireEmbeddingsOrDocuments: true, 179 | ); 180 | 181 | $request = new AddEmbeddingRequest( 182 | embeddings: $validated['embeddings'], 183 | metadatas: $validated['metadatas'], 184 | ids: $validated['ids'], 185 | documents: $validated['documents'], 186 | images: $validated['images'], 187 | ); 188 | 189 | $this->apiClient->upsert($this->id, $request); 190 | } 191 | 192 | /** 193 | * Count the number of items in the collection. 194 | */ 195 | public function count(): int 196 | { 197 | return $this->apiClient->count($this->id); 198 | } 199 | 200 | /** 201 | * Returns the first `$limit` entries of the collection. 202 | * 203 | * @param int $limit The number of entries to return. Defaults to 10. 204 | * @param string[] $include The list of fields to include in the response (optional). 205 | */ 206 | public function peek( 207 | int $limit = 10, 208 | ?array $include = null 209 | ): GetItemsResponse 210 | { 211 | $include ??= ['embeddings', 'metadatas', 'distances']; 212 | 213 | $request = new GetEmbeddingRequest( 214 | limit: $limit, 215 | include: $include, 216 | ); 217 | 218 | return $this->apiClient->get($this->id, $request); 219 | } 220 | 221 | /** 222 | * Get items from the collection. 223 | * 224 | * @param array $ids The IDs of the items to get (optional). 225 | * @param array $where The where clause to filter items by (optional). 226 | * @param array $whereDocument The where clause to filter items by (optional). 227 | * @param int $limit The limit on the number of items to get (optional). 228 | * @param int $offset The offset on the number of items to get (optional). 229 | * @param string[] $include The list of fields to include in the response (optional). 230 | */ 231 | public function get( 232 | ?array $ids = null, 233 | ?array $where = null, 234 | ?array $whereDocument = null, 235 | ?int $limit = null, 236 | ?int $offset = null, 237 | ?array $include = null 238 | ): GetItemsResponse 239 | { 240 | $include ??= ['embeddings', 'metadatas', 'distances']; 241 | 242 | $request = new GetEmbeddingRequest( 243 | ids: $ids, 244 | where: $where, 245 | whereDocument: $whereDocument, 246 | limit: $limit, 247 | offset: $offset, 248 | include: $include, 249 | ); 250 | 251 | return $this->apiClient->get($this->id, $request); 252 | } 253 | 254 | /** 255 | * Deletes items from the collection. 256 | * 257 | * @param ?array $ids The IDs of the items to delete. 258 | * @param ?array $where The where clause to filter items to delete based on metadata values (optional). 259 | * @param ?array $whereDocument The where clause to filter to delete based on document content (optional). 260 | */ 261 | public function delete(?array $ids = null, ?array $where = null, ?array $whereDocument = null): void 262 | { 263 | $request = new DeleteEmbeddingRequest( 264 | ids: $ids, 265 | where: $where, 266 | whereDocument: $whereDocument, 267 | ); 268 | 269 | $this->apiClient->delete($this->id, $request); 270 | } 271 | 272 | /** 273 | * Performs a query on the collection using the specified parameters. 274 | * 275 | * 276 | */ 277 | public function query( 278 | ?array $queryEmbeddings = null, 279 | ?array $queryTexts = null, 280 | ?array $queryImages = null, 281 | int $nResults = 10, 282 | ?array $where = null, 283 | ?array $whereDocument = null, 284 | ?array $include = null 285 | ): QueryItemsResponse 286 | { 287 | $include ??= ['embeddings', 'metadatas', 'distances']; 288 | 289 | if ( 290 | !(($queryEmbeddings != null xor $queryTexts != null xor $queryImages != null)) 291 | ) { 292 | throw new \InvalidArgumentException( 293 | 'You must provide only one of queryEmbeddings, queryTexts, queryImages, or queryUris' 294 | ); 295 | } 296 | 297 | $finalEmbeddings = []; 298 | 299 | if ($queryEmbeddings == null) { 300 | if ($this->embeddingFunction == null) { 301 | throw new \InvalidArgumentException( 302 | 'You must provide an embedding function if you did not provide embeddings' 303 | ); 304 | } elseif ($queryTexts != null) { 305 | $finalEmbeddings = $this->embeddingFunction->generate($queryTexts); 306 | } elseif ($queryImages != null) { 307 | $finalEmbeddings = $this->embeddingFunction->generate($queryImages); 308 | } else { 309 | throw new \InvalidArgumentException( 310 | 'If you did not provide embeddings, you must provide documents or images' 311 | ); 312 | } 313 | } else { 314 | $finalEmbeddings = $queryEmbeddings; 315 | } 316 | 317 | 318 | $request = new QueryEmbeddingRequest( 319 | where: $where, 320 | whereDocument: $whereDocument, 321 | queryEmbeddings: $finalEmbeddings, 322 | nResults: $nResults, 323 | include: $include, 324 | ); 325 | 326 | return $this->apiClient->getNearestNeighbors($this->id, $request); 327 | 328 | } 329 | 330 | 331 | /** 332 | * Modify the collection name or metadata. 333 | */ 334 | public function modify(string $name, array $metadata): void 335 | { 336 | $request = new UpdateCollectionRequest($name, $metadata); 337 | 338 | $this->apiClient->updateCollection($this->id, $request); 339 | } 340 | 341 | /** 342 | * Validates the inputs to the add, upsert, and update methods. 343 | * 344 | * @return array{ids: string[], embeddings: int[][], metadatas: array[], documents: string[], images: string[], uris: string[]} 345 | */ 346 | protected 347 | function validate( 348 | array $ids, 349 | ?array $embeddings, 350 | ?array $metadatas, 351 | ?array $documents, 352 | ?array $images, 353 | bool $requireEmbeddingsOrDocuments 354 | ): array 355 | { 356 | 357 | if ($requireEmbeddingsOrDocuments) { 358 | if ($embeddings === null && $documents === null && $images === null) { 359 | throw new \InvalidArgumentException( 360 | 'You must provide embeddings, documents, or images' 361 | ); 362 | } 363 | } 364 | 365 | if ( 366 | $embeddings != null && count($embeddings) != count($ids) 367 | || $metadatas != null && count($metadatas) != count($ids) 368 | || $documents != null && count($documents) != count($ids) 369 | || $images != null && count($images) != count($ids) 370 | ) { 371 | throw new \InvalidArgumentException( 372 | 'The number of ids, embeddings, metadatas, documents, and images must be the same' 373 | ); 374 | } 375 | 376 | if ($embeddings == null) { 377 | if ($this->embeddingFunction == null) { 378 | throw new \InvalidArgumentException( 379 | 'You must provide an embedding function if you did not provide embeddings' 380 | ); 381 | } elseif ($documents != null) { 382 | $finalEmbeddings = $this->embeddingFunction->generate($documents); 383 | } elseif ($images != null) { 384 | $finalEmbeddings = $this->embeddingFunction->generate($images); 385 | } else { 386 | throw new \InvalidArgumentException( 387 | 'If you did not provide embeddings, you must provide documents or images' 388 | ); 389 | } 390 | } else { 391 | $finalEmbeddings = $embeddings; 392 | } 393 | 394 | $ids = array_map(function ($id) { 395 | $id = (string)$id; 396 | if ($id === '') { 397 | throw new \InvalidArgumentException('Expected IDs to be non-empty strings'); 398 | } 399 | return $id; 400 | }, $ids); 401 | 402 | $uniqueIds = array_unique($ids); 403 | if (count($uniqueIds) !== count($ids)) { 404 | $duplicateIds = array_filter($ids, function ($id) use ($ids) { 405 | return count(array_keys($ids, $id)) > 1; 406 | }); 407 | throw new \InvalidArgumentException('Expected IDs to be unique, found duplicates for: ' . implode(', ', $duplicateIds)); 408 | } 409 | 410 | 411 | return [ 412 | 'ids' => $ids, 413 | 'embeddings' => $finalEmbeddings, 414 | 'metadatas' => $metadatas, 415 | 'documents' => $documents, 416 | 'images' => $images, 417 | ]; 418 | 419 | 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /tests/ChromaDB.php: -------------------------------------------------------------------------------- 1 | toBeInstanceOf(Client::class); 14 | }); 15 | 16 | it('can connect to a chroma server using factory', function () { 17 | $client = ChromaDB::factory() 18 | ->withHost('http://localhost') 19 | ->withPort(8000) 20 | ->connect(); 21 | 22 | expect($client)->toBeInstanceOf(Client::class); 23 | }); 24 | 25 | test('can connect to an API token authenticated chroma server', function () { 26 | $client = ChromaDB::factory() 27 | ->withPort(8001) 28 | ->withAuthToken('test-token') 29 | ->connect(); 30 | 31 | expect($client)->toBeInstanceOf(Client::class); 32 | }); 33 | 34 | it('cannot connect to an API token authenticated chroma server with wrong token', function () { 35 | ChromaDB::factory() 36 | ->withPort(8001) 37 | ->withAuthToken('wrong-token') 38 | ->connect(); 39 | })->throws(ChromaAuthorizationException::class); 40 | 41 | it('throws exception when connecting to API token authenticated chroma server with no token', function () { 42 | ChromaDB::factory() 43 | ->withPort(8001) 44 | ->connect(); 45 | })->throws(ChromaAuthorizationException::class); 46 | 47 | it('throws a connection exception when connecting to a non-existent chroma server', function () { 48 | ChromaDB::factory() 49 | ->withHost('http://localhost') 50 | ->withPort(8002) 51 | ->connect(); 52 | })->throws(ChromaConnectionException::class); 53 | -------------------------------------------------------------------------------- /tests/Client.php: -------------------------------------------------------------------------------- 1 | client = ChromaDB::factory() 14 | ->withDatabase('test_database') 15 | ->withTenant('test_tenant') 16 | ->connect(); 17 | 18 | $this->client->deleteAllCollections(); 19 | 20 | $this->embeddingFunction = new class implements EmbeddingFunction { 21 | public function generate(array $texts): array 22 | { 23 | return array_map(function ($text) { 24 | return [1.0, 2.0, 3.0, 4.0, 5.0]; 25 | }, $texts); 26 | } 27 | }; 28 | 29 | $this->collection = $this->client->createCollection( 30 | name: 'test_collection', 31 | embeddingFunction: $this->embeddingFunction 32 | ); 33 | }); 34 | 35 | 36 | it('can get the version', function () { 37 | $version = $this->client->version(); 38 | 39 | expect($version) 40 | ->toBeString() 41 | ->toMatch('/^[0-9]+\.[0-9]+\.[0-9]+$/'); 42 | }); 43 | 44 | it('can get the heartbeat', function () { 45 | $heartbeat = $this->client->heartbeat(); 46 | 47 | expect($heartbeat) 48 | ->toBeInt() 49 | ->toBeGreaterThan(0); 50 | }); 51 | 52 | it('can list collections', function () { 53 | $collections = $this->client->listCollections(); 54 | 55 | expect($collections) 56 | ->toBeArray() 57 | ->toHaveCount(1); 58 | 59 | $this->client->createCollection('test_collection_2'); 60 | 61 | $collections = $this->client->listCollections(); 62 | 63 | expect($collections) 64 | ->toBeArray() 65 | ->toHaveCount(2); 66 | }); 67 | 68 | 69 | it('can create or get collections', function () { 70 | $collection = $this->client->getOrCreateCollection('test_collection'); 71 | 72 | expect($collection) 73 | ->toBeInstanceOf(CollectionResource::class) 74 | ->toHaveProperty('name', 'test_collection'); 75 | 76 | $collection = $this->client->getOrCreateCollection('test_collection_2'); 77 | 78 | expect($collection) 79 | ->toBeInstanceOf(CollectionResource::class) 80 | ->toHaveProperty('name', 'test_collection_2'); 81 | }); 82 | 83 | it('can get a collection', function () { 84 | $collection = $this->client->getCollection('test_collection'); 85 | 86 | expect($collection) 87 | ->toBeInstanceOf(CollectionResource::class) 88 | ->toHaveProperty('name', 'test_collection'); 89 | }); 90 | 91 | it('throws a value error when getting a collection that does not exist', function () { 92 | $this->client->getCollection('test_collection_2'); 93 | })->throws(ChromaValueException::class, 'Collection test_collection_2 does not exist.'); 94 | 95 | it('can modify a collection name or metadata', function () { 96 | $this->collection->modify('test_collection_2', ['test' => 'test_2']); 97 | 98 | $collection = $this->client->getCollection('test_collection_2'); 99 | 100 | expect($collection->name) 101 | ->toBe('test_collection_2') 102 | ->and($collection->metadata) 103 | ->toMatchArray(['test' => 'test_2']); 104 | 105 | }); 106 | 107 | it('can delete a collection', function () { 108 | $this->client->deleteCollection('test_collection'); 109 | 110 | expect(fn() => $this->client->getCollection('test_collection')) 111 | ->toThrow(ChromaValueException::class); 112 | }); 113 | 114 | it('can delete all collections', function () { 115 | $this->client->createCollection('test_collection_2'); 116 | 117 | $collections = $this->client->listCollections(); 118 | 119 | expect($collections) 120 | ->toBeArray() 121 | ->toHaveCount(2); 122 | 123 | $this->client->deleteAllCollections(); 124 | 125 | $collections = $this->client->listCollections(); 126 | 127 | expect($collections) 128 | ->toBeArray() 129 | ->toHaveCount(0); 130 | }); 131 | 132 | it('throws a value error when deleting a collection that does not exist', function () { 133 | $this->client->deleteCollection('test_collection_2'); 134 | })->throws(ChromaValueException::class, 'Collection test_collection_2 does not exist.'); 135 | 136 | it('can add single embeddings to a collection', function () { 137 | $ids = ['test1']; 138 | $embeddings = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]; 139 | $metadatas = [['test' => 'test']]; 140 | 141 | $this->collection->add($ids, $embeddings, $metadatas); 142 | 143 | expect($this->collection->count())->toBe(1); 144 | }); 145 | 146 | it('cannot add invalid single embeddings to a collection', function () { 147 | $ids = ['test1']; 148 | $embeddings = ['this is not an embedding']; 149 | $metadatas = [['test' => 'test']]; 150 | 151 | $this->collection->add($ids, $embeddings, $metadatas); 152 | })->throws(ChromaTypeException::class); 153 | 154 | it('can add single text documents to a collection', function () { 155 | $ids = ['test1']; 156 | $documents = ['This is a test document']; 157 | $metadatas = [['test' => 'test']]; 158 | 159 | $this->collection->add( 160 | $ids, 161 | metadatas: $metadatas, 162 | documents: $documents 163 | ); 164 | 165 | expect($this->collection->count())->toBe(1); 166 | }); 167 | 168 | it('cannot add single embeddings to a collection with a different dimensionality', function () { 169 | $ids = ['test1']; 170 | $embeddings = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]; 171 | $metadatas = [['test' => 'test']]; 172 | 173 | $this->collection->add($ids, $embeddings, $metadatas); 174 | 175 | // Dimensionality is now 10. Other embeddings must have the same dimensionality. 176 | 177 | $ids = ['test2']; 178 | $embeddings = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]]; 179 | $metadatas = [['test' => 'test2']]; 180 | 181 | $this->collection->add($ids, $embeddings, $metadatas); 182 | })->throws(ChromaDimensionalityException::class, 'Embedding dimension 11 does not match collection dimensionality 10'); 183 | 184 | it('can upsert single embeddings to a collection', function () { 185 | $ids = ['test1']; 186 | $embeddings = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]; 187 | $metadatas = [['test' => 'test']]; 188 | 189 | $this->collection->upsert($ids, $embeddings, $metadatas); 190 | 191 | expect($this->collection->count())->toBe(1); 192 | 193 | $this->collection->upsert($ids, $embeddings, $metadatas); 194 | 195 | expect($this->collection->count())->toBe(1); 196 | }); 197 | 198 | 199 | it('can update single embeddings in a collection', function () { 200 | $ids = ['test1']; 201 | $embeddings = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]; 202 | $metadatas = [['test' => 'test']]; 203 | 204 | $this->collection->add($ids, $embeddings, $metadatas); 205 | 206 | expect($this->collection->count())->toBe(1); 207 | 208 | $this->collection->update($ids, $embeddings, $metadatas); 209 | 210 | expect($this->collection->count())->toBe(1); 211 | 212 | $collectionItems = $this->collection->get($ids); 213 | 214 | expect($collectionItems->ids) 215 | ->toMatchArray($ids) 216 | ->and($collectionItems->embeddings) 217 | ->toMatchArray($embeddings) 218 | ->and($collectionItems->metadatas) 219 | ->toMatchArray($metadatas); 220 | }); 221 | 222 | it('can update single documents in a collection', function () { 223 | $ids = ['test1']; 224 | $documents = ['This is a test document']; 225 | $metadatas = [['test' => 'test']]; 226 | 227 | $this->collection->add( 228 | $ids, 229 | metadatas: $metadatas, 230 | documents: $documents 231 | ); 232 | 233 | expect($this->collection->count())->toBe(1); 234 | 235 | $newDocuments = ['This is a new test document']; 236 | $newMetadatas = [['test' => 'test2']]; 237 | 238 | $this->collection->update( 239 | $ids, 240 | metadatas: $newMetadatas, 241 | documents: $newDocuments 242 | ); 243 | 244 | expect($this->collection->count())->toBe(1); 245 | 246 | $collectionItems = $this->collection->get($ids, include: ['documents', 'metadatas']); 247 | 248 | expect($collectionItems->ids) 249 | ->toMatchArray($ids) 250 | ->and($collectionItems->documents) 251 | ->toMatchArray($newDocuments) 252 | ->and($collectionItems->metadatas) 253 | ->toMatchArray($newMetadatas); 254 | }); 255 | 256 | it('can add batch embeddings to a collection', function () { 257 | $ids = ['test1', 'test2', 'test3']; 258 | $embeddings = [ 259 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 260 | [11, 12, 13, 14, 15, 16, 17, 18, 19, 20], 261 | [21, 22, 23, 24, 25, 26, 27, 28, 29, 30], 262 | ]; 263 | $metadatas = [ 264 | ['some' => 'metadata1'], 265 | ['some' => 'metadata2'], 266 | ['some' => 'metadata3'], 267 | ]; 268 | 269 | $this->collection->add($ids, $embeddings, $metadatas); 270 | 271 | expect($this->collection->count())->toBe(3); 272 | 273 | $getResponse = $this->collection->get($ids); 274 | 275 | expect($getResponse->ids) 276 | ->toMatchArray($ids) 277 | ->and($getResponse->embeddings) 278 | ->toMatchArray($embeddings) 279 | ->and($getResponse->metadatas) 280 | ->toMatchArray($metadatas); 281 | }); 282 | 283 | it('cannot add batch embeddings with different dimensionality to a collection', function () { 284 | $ids = ['test1', 'test2', 'test3']; 285 | $embeddings = [ 286 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 287 | [11, 12, 13, 14, 15, 16, 17, 18, 19], 288 | [21, 22, 23, 24, 25, 26, 27, 28], 289 | ]; 290 | $metadatas = [ 291 | ['some' => 'metadata1'], 292 | ['some' => 'metadata2'], 293 | ['some' => 'metadata3'], 294 | ]; 295 | 296 | $this->collection->add($ids, $embeddings, $metadatas); 297 | })->throws(ChromaDimensionalityException::class); 298 | 299 | it('can add batch documents to a collection', function () { 300 | $ids = ['test1', 'test2', 'test3']; 301 | $documents = [ 302 | 'This is a test document', 303 | 'This is another test document', 304 | 'This is a third test document', 305 | ]; 306 | $metadatas = [ 307 | ['some' => 'metadata1'], 308 | ['some' => 'metadata2'], 309 | ['some' => 'metadata3'], 310 | ]; 311 | 312 | $this->collection->add( 313 | $ids, 314 | metadatas: $metadatas, 315 | documents: $documents 316 | ); 317 | 318 | expect($this->collection->count())->toBe(3); 319 | 320 | $getResponse = $this->collection->get($ids, include: ['documents', 'metadatas']); 321 | 322 | expect($getResponse->ids) 323 | ->toMatchArray($ids) 324 | ->and($getResponse->documents) 325 | ->toMatchArray($documents) 326 | ->and($getResponse->metadatas) 327 | ->toMatchArray($metadatas); 328 | }); 329 | 330 | 331 | it('can peek a collection', function () { 332 | $ids = ['test1', 'test2', 'test3']; 333 | $embeddings = [ 334 | [1.0, 2.0, 3.0, 4.0, 5.0], 335 | [6.0, 7.0, 8.0, 9.0, 10.0], 336 | [11.0, 12.0, 13.0, 14.0, 15.0], 337 | ]; 338 | 339 | $this->collection->add($ids, $embeddings); 340 | 341 | expect($this->collection->count())->toBe(3); 342 | 343 | $peekResponse = $this->collection->peek(2); 344 | 345 | expect($peekResponse->ids) 346 | ->toMatchArray(['test1', 'test2']); 347 | 348 | }); 349 | 350 | it('can query a collection', function () { 351 | $ids = ['test1', 'test2', 'test3']; 352 | $embeddings = [ 353 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0], 354 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0], 355 | [10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0], 356 | ]; 357 | 358 | $this->collection->add($ids, $embeddings); 359 | 360 | expect($this->collection->count())->toBe(3); 361 | 362 | $queryResponse = $this->collection->query( 363 | queryEmbeddings: [ 364 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] 365 | ], 366 | nResults: 2 367 | ); 368 | 369 | expect($queryResponse->ids[0]) 370 | ->toMatchArray(['test1', 'test2']) 371 | ->and($queryResponse->distances[0]) 372 | ->toMatchArray([0.0, 0.0]); 373 | 374 | }); 375 | 376 | it('can get a collection by id', function () { 377 | $ids = ['test1', 'test2', 'test3']; 378 | $embeddings = [ 379 | [1.0, 2.0, 3.0, 4.0, 5.0], 380 | [6.0, 7.0, 8.0, 9.0, 10.0], 381 | [11.0, 12.0, 13.0, 14.0, 15.0], 382 | ]; 383 | $metadatas = [ 384 | ['some' => 'metadata1'], 385 | ['some' => 'metadata2'], 386 | ['some' => 'metadata3'], 387 | ]; 388 | 389 | $this->collection->add($ids, $embeddings, $metadatas); 390 | 391 | expect($this->collection->count())->toBe(3); 392 | 393 | $collectionItems = $this->collection->get(['test1', 'test2']); 394 | 395 | expect($collectionItems->ids) 396 | ->toMatchArray(['test1', 'test2']) 397 | ->and($collectionItems->embeddings) 398 | ->toMatchArray([ 399 | [1.0, 2.0, 3.0, 4.0, 5.0], 400 | [6.0, 7.0, 8.0, 9.0, 10.0], 401 | ]); 402 | }); 403 | 404 | 405 | it('can get a collection by where', function () { 406 | $ids = ['test1', 'test2', 'test3']; 407 | $embeddings = [ 408 | [1.0, 2.0, 3.0, 4.0, 5.0], 409 | [6.0, 7.0, 8.0, 9.0, 10.0], 410 | [11.0, 12.0, 13.0, 14.0, 15.0], 411 | ]; 412 | $metadatas = [ 413 | ['some' => 'metadata1'], 414 | ['some' => 'metadata2'], 415 | ['some' => 'metadata3'], 416 | ]; 417 | 418 | $this->collection->add($ids, $embeddings, $metadatas); 419 | 420 | expect($this->collection->count())->toBe(3); 421 | 422 | $collectionItems = $this->collection->get( 423 | where: [ 424 | 'some' => ['$eq' => 'metadata1'] 425 | ] 426 | ); 427 | 428 | expect($collectionItems->ids) 429 | ->toHaveCount(1) 430 | ->and($collectionItems->ids[0]) 431 | ->toBe('test1'); 432 | }); 433 | 434 | it('can query a collection using query texts', function () { 435 | $ids = ['test1', 'test2', 'test3']; 436 | $documents = [ 437 | 'This is a test document', 438 | 'This is another test document', 439 | 'This is a third test document', 440 | ]; 441 | $metadatas = [ 442 | ['some' => 'metadata1'], 443 | ['some' => 'metadata2'], 444 | ['some' => 'metadata3'], 445 | ]; 446 | 447 | $this->collection->add( 448 | $ids, 449 | metadatas: $metadatas, 450 | documents: $documents 451 | ); 452 | 453 | expect($this->collection->count())->toBe(3); 454 | 455 | $queryResponse = $this->collection->query( 456 | queryTexts: ['This is a test document'], 457 | nResults: 1 458 | ); 459 | 460 | expect($queryResponse->ids[0]) 461 | ->toMatchArray(['test1']); 462 | }); 463 | 464 | it('throws a value error when getting a collection by where with an invalid operator', function () { 465 | $ids = ['test1', 'test2', 'test3']; 466 | $embeddings = [ 467 | [1.0, 2.0, 3.0, 4.0, 5.0], 468 | [6.0, 7.0, 8.0, 9.0, 10.0], 469 | [11.0, 12.0, 13.0, 14.0, 15.0], 470 | ]; 471 | $metadatas = [ 472 | ['some' => 'metadata1'], 473 | ['some' => 'metadata2'], 474 | ['some' => 'metadata3'], 475 | ]; 476 | 477 | $this->collection->add($ids, $embeddings, $metadatas); 478 | 479 | expect($this->collection->count())->toBe(3); 480 | 481 | $collectionItems = $this->collection->get( 482 | where: [ 483 | 'some' => ['$invalid' => 'metadata1'] 484 | ] 485 | ); 486 | })->throws(ChromaValueException::class); 487 | 488 | it('can delete a collection by id', function () { 489 | $ids = ['test1', 'test2', 'test3']; 490 | $embeddings = [ 491 | [1.0, 2.0, 3.0, 4.0, 5.0], 492 | [6.0, 7.0, 8.0, 9.0, 10.0], 493 | [11.0, 12.0, 13.0, 14.0, 15.0], 494 | ]; 495 | $metadatas = [ 496 | ['some' => 'metadata1'], 497 | ['some' => 'metadata2'], 498 | ['some' => 'metadata3'], 499 | ]; 500 | 501 | $this->collection->add($ids, $embeddings, $metadatas); 502 | 503 | expect($this->collection->count())->toBe(3); 504 | 505 | $this->collection->delete(['test1', 'test2']); 506 | 507 | expect($this->collection->count())->toBe(1); 508 | }); 509 | 510 | it('can delete a collection by where', function () { 511 | $ids = ['test1', 'test2', 'test3']; 512 | $embeddings = [ 513 | [1.0, 2.0, 3.0, 4.0, 5.0], 514 | [6.0, 7.0, 8.0, 9.0, 10.0], 515 | [11.0, 12.0, 13.0, 14.0, 15.0], 516 | ]; 517 | $metadatas = [ 518 | ['some' => 'metadata1'], 519 | ['some' => 'metadata2'], 520 | ['some' => 'metadata3'], 521 | ]; 522 | 523 | $this->collection->add($ids, $embeddings, $metadatas); 524 | 525 | expect($this->collection->count())->toBe(3); 526 | 527 | $this->collection->delete( 528 | where: [ 529 | 'some' => 'metadata1' 530 | ] 531 | ); 532 | 533 | expect($this->collection->count())->toBe(2); 534 | }); --------------------------------------------------------------------------------