Skip to content

Releases

2.20.0

Choose a tag to compare

@Laurianti Laurianti released this 06 Nov 19:34
v2.20.0
ce264e4

ChromaDotNet.Client and ChromaDotNet.Client.DependencyInjection 2.20.0, released together.

New

  • SupportsSparseVectorIndexesAsync() and SupportsSparseVectorIndexes() of ChromaClient say whether the server takes sparse vector indexes, like the BM25 index of WithBm25Index: whether the client sends the indexes added with ifSupported. Distributed Chroma, the one Chroma Cloud runs on, takes them: on Chroma Cloud, and elsewhere when the options name it with WithDistributedChroma(). So do the servers that declare them in pre-flight-checks, like VecturaDB. A single Chroma server does not. When the server does not answer within 5 seconds, or its answer cannot be read, the answer is what the options say: the asynchronous method asks again at the next call, the synchronous one keeps it for 30 seconds.
  • The synchronous method is for the callers that cannot await, like a synchronous GetService. Its request runs on the thread pool, so it does not deadlock under the synchronization context of a UI or of ASP.NET on .NET Framework. After an answer of the server it answers at once, and asks again in the background when that answer is two minutes old.
  • DistributedChroma of ChromaConfigurationOptions is what WithDistributedChroma() or Distributed=true in the connection string set, also on the address of Chroma Cloud, for code that passes the setting on. IsDistributedChroma stays whether the client takes the server for distributed Chroma, Chroma Cloud included.

Fixes

  • The comments of GetCollectionByCrnAsync, UpdateTenantAsync and ChromaTenant.ResourceName, and the docs, gave the resource names of the tenants and the get of a collection by its CRN to Chroma Cloud only. They now name distributed Chroma, the one Chroma Cloud runs on.

Docs

  • docs/sparse-vectors-and-schema.md: the two methods. docs/connecting.md: DistributedChroma next to IsDistributedChroma. docs/chroma-cloud.md: the resource names on distributed Chroma.
  • docs/COMPATIBILITY.md:
    • Chroma 1.5.9 answers add_input with 404 without a body, so AddFunctionInputAsync throws a ChromaException "NotFound: POST …/attached_functions/stats/add_input";
    • distributed Chroma keeps the resource name of a tenant and gets a collection by its CRN. It answers 500 "collection not found" for a CRN without a collection, and refuses the name of a database with fewer than 3 characters.

In our tests on 6 November 2026, both methods answered false on Chroma 1.5.9, and true on Chroma Cloud, on VecturaDB 1.0.0-preview.8 and on distributed Chroma at 52ce844, installed with ChromaDB.Operator 0.1.3, with WithDistributedChroma(). The client created the collection of the test with the index added with ifSupported exactly where they answered true.

Nothing breaks for the existing code: the public API only gains members.

NuGet: https://www.nuget.org/packages/ChromaDotNet.Client/2.20.0 and https://www.nuget.org/packages/ChromaDotNet.Client.DependencyInjection/2.20.0

2.19.0

Choose a tag to compare

@Laurianti Laurianti released this 06 Nov 14:26
v2.19.0
8eb9db4

ChromaDotNet.Client and ChromaDotNet.Client.DependencyInjection 2.19.0, released together.

New

  • Conditional transactions: collectionClient.RunConditionalTransactionAsync(operation) reads records and writes them only if none of them changed after its reads, as collection.conditional() of the Python and JavaScript clients of Chroma. After a conflict, a stale read or a 429 Backoff, it runs the operation again in a new transaction, up to maxRetries times, 3 by default. BeginConditionalTransaction() gives a transaction to commit without a run, with GetAsync, AddAsync, UpdateAsync, UpsertAsync, DeleteAsync by ids, and CommitAsync. A conflict throws a ChromaConditionalWriteConflictException (409), a stale read a ChromaStaleReadException (412). Distributed Chroma, the one Chroma Cloud runs on, has conditional transactions for the tenants that the configuration of its frontend enables, and so does VecturaDB; Chroma 1.5.9 does not.
  • AddFunctionInputAsync(name, inputCollectionId) adds another input collection to an asynchronous attached function, as AttachedFunction.add_input of the Python client, and says whether the input was added now: on distributed Chroma, the one Chroma Cloud runs on, and on VecturaDB.
  • Writes refused for compaction: distributed Chroma, the one Chroma Cloud runs on, refuses a write to a collection whose log holds too many records not yet compacted, with 429 "log needs compaction before accepting more writes; please backoff exponentially and retry". Each request of AddAsync, UpdateAsync, UpsertAsync, DeleteAsync and DeleteAllAsync now goes again after that refusal only, and the batches that went are not sent again. ChromaWriteRetries.Default makes 12 attempts, with a base delay of 1 second and a largest delay of 10 seconds: from 42.5 to 85 seconds in all. A Retry-After of the refusal is waited instead; VecturaDB sends one. WithWriteRetries sets other attempts and delays, and ChromaWriteRetries.None turns them off. Any other error of a write, also another 429, stands at once.
  • Distributed Chroma, the one Chroma Cloud runs on, installed elsewhere with the Helm chart of Chroma or an operator: WithDistributedChroma(), or Distributed=true in the connection string, says it. The client then throws an ArgumentException before a filter on the documents that distributed Chroma gets wrong (KD-56, KD-57), also in a delete, sends the indexes added with ifSupported without the key quota of Chroma Cloud, and deletes a collection or a database in one request. Chroma Cloud is distributed Chroma whatever the options say, and keeps its quotas. Distributed with a value other than true or false throws an ArgumentException.
  • The server block of pre-flight-checks, as VecturaDB sends it: ChromaPreFlightChecks.Server has its name, version and features. A server that declares sparse_vector_index gets the indexes added with ifSupported, without the key quota of Chroma Cloud. A server that says what it is in that block is what it says, whatever the options say. Chroma and Chroma Cloud send no block, so nothing changes for them.

Fixes

  • The messages, the comments, the README and the docs about the Search API, fork, the fork count, the indexing status, the read levels, the SPANN index, sparse vector indexes, attached functions and conditional transactions now name the servers that have them: distributed Chroma, the one Chroma Cloud runs on, and VecturaDB. They said Chroma Cloud only. The ones on the filters on the documents of KD-56 and KD-57 say that the client throws when it knows the server is distributed Chroma: on Chroma Cloud, or elsewhere with WithDistributedChroma().
  • On nuget.org the README showed the badges of OpenSSF Scorecard, OpenSSF Best Practices and GitHub Codespaces blank: they come from img.shields.io now, which nuget.org shows.
  • THIRD-PARTY-NOTICES.md, in the package of ChromaDotNet.Client, credits Chroma for the port of its BM25 function, with its license, the Apache License 2.0.

Docs

  • docs/conditional-transactions.md: conditional transactions. docs/errors.md: writes refused for compaction and the exceptions of a conditional transaction. docs/chroma-cloud.md: AddFunctionInputAsync and conditional transactions. docs/connecting.md: distributed Chroma.
  • docs/COMPATIBILITY.md: KD-59, a get during concurrent upserts of the same record that returns metadata mixed from two versions on Chroma 1.5.9, the measures of KD-56 and KD-57 on distributed Chroma, and the order of the databases that distributed Chroma lists.
  • docs/embeddings.md and the README: ChromaDotNet.Embeddings, the embeddings of the Python client. The README points to ChromaDotNet.Local.ChromaDB, the package with the engine. docs/about-this-fork.md: the original ChromaDB.Client package is deprecated on NuGet in favor of ChromaDotNet.Client, and the repository left the fork network of ssone95/ChromaDB.Client.

Nothing breaks for the existing code: the public API only gains members. A write refused because the log needs compaction now goes again, and with ChromaWriteRetries.None it throws at once, as before.

NuGet: https://www.nuget.org/packages/ChromaDotNet.Client/2.19.0 and https://www.nuget.org/packages/ChromaDotNet.Client.DependencyInjection/2.19.0

2.18.1

Choose a tag to compare

@Laurianti Laurianti released this 25 Oct 22:03
v2.18.1
4d229eb

ChromaDotNet.Client and ChromaDotNet.Client.DependencyInjection 2.18.1, released together: the same code as 2.18.0.

Packages

  • The new icon of ChromaDotNet, without the colors of Chroma: the dots of the C in teal and the final dot in indigo, on a dark tile.
  • The package descriptions end with: Independent project. Not affiliated with, endorsed by or sponsored by Chroma Inc. CHROMA is a registered trademark of Chroma Inc., used here only to describe compatibility.
  • The README says who we are, with the same sentences.

Packages: ChromaDotNet.Client 2.18.1, ChromaDotNet.Client.DependencyInjection 2.18.1

2.18.0

Choose a tag to compare

@Laurianti Laurianti released this 21 Oct 12:40
487ba89

ChromaDotNet.Client and ChromaDotNet.Client.DependencyInjection 2.18.0, released together.

New

  • ChromaReadRetries.MinDelay: a read never goes again at once. The delay before a new attempt is a random time from MinDelay to the smaller of MaxDelay and BaseDelay times 2 to the attempts that went again before. new ChromaReadRetries(maxAttempts, minDelay, baseDelay, maxDelay) takes it, from zero to the base; the constructor without it takes 0.8 times the base, the lower bound of the jitter of Azure.Core. The default now waits 40 to 50 ms, then 40 to 100 ms and 40 to 200 ms; ChromaReadRetries.None does not change.
  • Collections created when missing: client.GetCollectionClient(name, createIfMissing: true) gets or creates the collection at its first operation, with one request that the operations starting before it ends share, and await client.GetOrCreateCollectionClientAsync(name) does it at once. Both take a ChromaCollectionDefinition too, keep the collection, and create it again, empty, when it is deleted elsewhere.
  • AddChromaClientFromServices and AddKeyedChromaClientFromServices make the options from the services when the client is resolved, so the address can come from IConfiguration or IOptions of the application.

Fixes

  • A value that is not a text under the metadata key chroma:document, where Chroma keeps the document of a record, throws an ArgumentException before the request, in AddAsync, UpdateAsync and UpsertAsync. Such a value, like a number, a Boolean, a list, or a null in an update or an upsert, makes Chroma 1.0.0 to 1.5.9 fail every add and get of the server, on every collection, until it restarts (KD-58). The same holds for the null of a deleted document or the mark that WithDocumentCopyKey or WithDocumentDeletionKey would write under that key.
  • A text under chroma:document goes as before and sets the document, also a Guid, a date or a byte[], which the client writes as text. The other keys with the prefix chroma: go as before too.
  • The release notes of the packages link the GitHub release of their version. The ones of 2.15.0 to 2.17.0 were still those of 2.14.0.

Docs

  • docs/COMPATIBILITY.md: KD-58. docs/metadata.md: the key chroma:document. docs/errors.md: MinDelay. docs/collections.md: collections created when missing. docs/dependency-injection.md: options made from the services.
  • The migration guide from 1.x says what a real migration had to do: the URI, the collection client, the handlers to remove, dependency injection and the behaviors that differ from 1.x.

Nothing breaks for the existing code: every setting of ChromaReadRetries that was valid stays valid.

NuGet: https://www.nuget.org/packages/ChromaDotNet.Client/2.18.0 and https://www.nuget.org/packages/ChromaDotNet.Client.DependencyInjection/2.18.0

2.17.0

Choose a tag to compare

@Laurianti Laurianti released this 20 Oct 13:10
5f92334

ChromaDotNet.Client and ChromaDotNet.Client.DependencyInjection 2.17.0, released together.

New

  • ChromaCollectionClient.WithEmbeddingGenerator(generator) takes an IEmbeddingGenerator<string, Embedding<float>> of Microsoft.Extensions.AI. The client of the collection then computes the embeddings, as an embedding function does in the Python client of Chroma:
    var records = client.GetCollectionClient(collection).WithEmbeddingGenerator(generator);
    await records.AddAsync(["a", "b"], documents: ["apples are red", "kiwis are green"]);
    var nearest = await records.QueryAsync("a red fruit", nResults: 1);
  • AddAsync, UpdateAsync and UpsertAsync compute the embeddings of the records given with documents and without embeddings. Records given with embeddings keep them.
  • QueryAsync(queryText) and QueryTextsAsync(queryTexts) query by text, as query_texts in the Python client.
  • ChromaCollectionDefinition.EmbeddingGenerator declares the default embedding function of Chroma: with a generator whose metadata has the provider chroma and the model all-MiniLM-L6-v2, a new collection stores the function default, as the Python client does, so the Python client reads it with its default embedding function. Another generator declares nothing.
  • The new API is in the builds for .NET 8 and .NET Framework, which reference Microsoft.Extensions.AI.Abstractions 9.5.0. The .NET Standard 2.0 build, for .NET Core 3.1 to 7, does not change.

Docs

  • docs/embeddings.md is new, and docs/records.md links it.
  • README: under Installation, ChromaDotNet.Local, to run Chroma on your machine without Docker.
  • SECURITY.md: the Cosign command matches the dots of the signing identity as dots, so it accepts only this repository.

The API of the client does not break: without a generator, the requests do not change. One change is visible: on .NET 8 and .NET Framework an application that uses a preview of Microsoft.Extensions.AI before 9.5.0 now gets Microsoft.Extensions.AI.Abstractions 9.5.0, the first stable release, and may need the names of that release, like GenerateVectorAsync for GenerateEmbeddingVectorAsync.

NuGet: https://www.nuget.org/packages/ChromaDotNet.Client/2.17.0 and https://www.nuget.org/packages/ChromaDotNet.Client.DependencyInjection/2.17.0

2.16.0

Choose a tag to compare

@Laurianti Laurianti released this 19 Oct 15:32
3240987

ChromaDotNet.Client and ChromaDotNet.Client.DependencyInjection 2.16.0, released together.

New

  • ChromaCollectionClient.DeleteAllAsync() deletes every record of the collection, which stays, and returns how many it deleted. It reads a page of ids from the start, as large as the batch size of the server and of Chroma Cloud, deletes them, and goes on until none is left.
  • ChromaWhereDocumentOperator.FromDictionary takes a filter on the documents written as the dictionary of the where_document of the Chroma API, as ChromaWhereOperator.FromDictionary does for where. Get, query and delete send it as it is; the Search API gets it on #document.
  • An empty metadata goes as no metadata (null) in AddAsync, UpdateAsync and UpsertAsync, where it threw an ArgumentException: every tested Chroma, 0.4.10 to 1.5.9 and Chroma Cloud, treats the two the same.

Fixes

  • The checks of Chroma Cloud on the filters of the documents (KD-56, KD-57) read the filters given as dictionaries whatever the types of their values: JSON, a typed dictionary, a JsonNode. A pattern like ^$ throws before any request, also in a delete. This holds for ChromaWhereOperator.FromDictionary of 2.15.0 too.
  • A get in pages, a get by ids and DeleteAllAsync learn the quotas of the reads and of the deletes of Chroma Cloud from its 422 answer, and read or delete again in smaller batches, without doing a delete twice. Before, a client with WithBatchSplitting above 300 failed on its first read.
  • A long $and or $or in a filter given as a dictionary, as JSON or as a List<object>, goes split as the same filter written with the operators (KD-4).
  • On Chroma Cloud, a regex pattern beyond the quota of the tenant, 256 bytes in UTF-8 by default, gets a ChromaException that names the pattern, its bytes and the limit. The client still sends every pattern: a tenant can have a higher quota.

Docs

  • docs/records.md, docs/metadata.md, docs/queries-and-filters.md and docs/COMPATIBILITY.md say all of the above.
  • README, Used by: LlmTornado.VectorDatabases.ChromaDB, the Chroma connector of LLM Tornado.

Nothing breaks for the existing code. One change is visible: FromDictionary writes the dictionary to JSON when it gets it, so a value the client cannot write, like a type outside its JSON context under NativeAOT, throws there instead of when the filter goes, and later changes to the dictionary do not change the filter.

NuGet: https://www.nuget.org/packages/ChromaDotNet.Client/2.16.0 and https://www.nuget.org/packages/ChromaDotNet.Client.DependencyInjection/2.16.0

2.15.0

Choose a tag to compare

@Laurianti Laurianti released this 19 Oct 08:52
6bf9c7f

ChromaDotNet.Client and ChromaDotNet.Client.DependencyInjection 2.15.0, released together.

New

  • ChromaWhereOperator.FromDictionary takes a filter already written as the dictionary of the where of the Chroma API, as the Python client takes it, and sends it as it is: FromDictionary(new Dictionary<string, object> { ["year"] = new Dictionary<string, object> { ["$gte"] = 2020 } }). & and | join it to the other filters; Not throws a NotSupportedException on it, as Chroma has no $not and the client does not read the dictionary.

Docs

  • docs/queries-and-filters.md says how to use FromDictionary.
  • README, Used by:
    • BotSharp.Plugin.Chroma, the Chroma vector database plugin of BotSharp;
    • LangChain.Databases in two entries: its integration tests under "Used by", its README, which recommends the provider for Chroma, under "Cited in".

Nothing changes for the existing code.

NuGet: https://www.nuget.org/packages/ChromaDotNet.Client/2.15.0 and https://www.nuget.org/packages/ChromaDotNet.Client.DependencyInjection/2.15.0

2.14.0

Choose a tag to compare

@Laurianti Laurianti released this 13 Oct 19:02
87afc52

ChromaDotNet.Client and ChromaDotNet.Client.DependencyInjection 2.14.0, released together.

New

  • Reads go again after an error that can pass. Each request of GetAsync, PeekAsync, QueryAsync, SearchAsync and CountAsync goes again after:

    • an answer 429, 500, 502, 503 or 504, other than a missing collection;
    • a request that fails on the network;
    • an answer whose lists do not have one item for every record, or, for a query or a search, one list of ids for every query embedding or search.

    So do the reads that a write makes first: the get of ChromaUpsertStrategy.SkipUnchangedEmbeddings, the get of WithReadsBeyondKeyQuota() on Chroma Cloud, and the pages of ids that DeleteCollectionAsync reads with deleteRecordsFirst.

  • Before each new attempt the client waits with the exponential backoff with full jitter of AWS: a random time between zero and the smaller of the largest delay and the base delay times 2 to the attempts that went again before. By default, 4 attempts at most, a base delay of 50 ms and a largest delay of 1 second: a read that keeps failing waits 350 ms at most before its error.

  • ChromaConfigurationOptions.ReadRetries and WithReadRetries(ChromaReadRetries) set the attempts, with ChromaReadRetries.Default, or MaxAttempts, BaseDelay and MaxDelay of your own. ChromaReadRetries.None turns them off, as in 2.13.0.

Changes

  • After the last attempt the error stands. An answer whose lists do not have one item for every record throws a ChromaException that says it, like "The answer of the server to a get has 1 id and 0 embeddings", instead of an ArgumentOutOfRangeException.
  • A write never goes again: one that failed may have gone. A timeout of the HttpClient does not go again either.
  • A query with ids takes an internal error to the workaround of KD-24 at once, without the delays. When every id has a record, the query goes again with the attempts, at most MaxAttempts queries in all.
  • A collection client made by name reads its collection again after the attempts. On Chroma 0.4.23, where the next count of such a client answers 500 after its collection is deleted and created again, that count takes 239 ms with the default, as a median of 20, and 6 ms with ChromaReadRetries.None.
  • In our measurement on Chroma 1.5.9, 14,000 upserts, deletes and gets with embeddings of the same records at the same time gave 28 errors with 2.13.0, and none with 2.14.0.

Docs

  • docs/errors.md says which reads go again, after which errors, and how to change the attempts.

NuGet: https://www.nuget.org/packages/ChromaDotNet.Client/2.14.0 and https://www.nuget.org/packages/ChromaDotNet.Client.DependencyInjection/2.14.0

2.13.0

Choose a tag to compare

@Laurianti Laurianti released this 13 Oct 10:56
b403bb5

ChromaDotNet.Client and ChromaDotNet.Client.DependencyInjection 2.13.0, released together.

New

  • Two more ways to delete a document with NullDocumentsDelete, next to WithDocumentCopyKey:
    • WithDocumentDeletionKey(key) writes an empty document with true under a metadata key, and reads it back as null.
    • WithEmptyDocumentDeletions() writes an empty document alone, which reads back as an empty string.
  • ChromaConfigurationOptions.WithReadsBeyondKeyQuota(): on Chroma Cloud, a write with nulls that would take a record beyond the quota of 32 metadata keys reads the record first and sends only the nulls of its keys, instead of throwing. A write of another client between the read and the write can mix with this one.

Changes

  • UpdateAsync and UpsertAsync delete metadata keys, with a null or an empty list, and documents, with NullDocumentsDelete, without reading the records first: the nulls and the empty document go in the same write. Two writes of the same record at the same time now leave the record of one of them. In our measurement of concurrent writes on 300 records, 131 records mixed the two writes with 2.12.0, and none with 2.13.0.

  • A collection client chooses once how it deletes documents:

    • A null document with NullDocumentsDelete, from a client without WithDocumentCopyKey, WithDocumentDeletionKey or WithEmptyDocumentDeletions, throws an ArgumentException before any request.
    • Another of the three, or the same one with another key, throws an InvalidOperationException. Up to 2.12.0, WithDocumentCopyKey with another key replaced the key.
    • When NullDocumentsDelete deletes documents, a record without a document gets the empty document too, so a where_document filter that matches an empty text finds it.
  • On Chroma Cloud, a write with nulls that would take a record beyond the quota of 32 metadata keys throws an ArgumentException before it goes, unless WithReadsBeyondKeyQuota().

  • On Chroma Cloud, a filter on the documents that gives the wrong records there throws an ArgumentException before the request, in every read and delete:

    • a $regex or $not_regex whose pattern takes an empty text, like ^$, ^ or ^.*$, or that the client cannot read as Chroma does (KD-56);
    • a $contains or $not_contains of fewer than 3 characters (KD-57).

    Regex("(?s).") finds the records with a document that is not empty, NotRegex("(?s).") the others, and Regex or NotRegex with a short text, its special characters escaped, does what Contains and NotContains would.

Fixes

  • An empty text, in a metadata key with a BM25 index or as the document, gets its empty BM25 vector, as with the Python client: the vector of the old text no longer stays, to be found by a search of the old text.
  • A connection string whose Endpoint is not an http or https URI says so, instead of saying that the endpoint is missing.
  • GetAsync with a limit of 0, from a collection client made by name, reads the collection first, so a collection that does not exist throws.
  • The error for a collection created with a schema whose space the server ignores says that Chroma 1.3.0 applies the space only when the schema also turns an index on or off.

Docs

  • The documentation moved from the README to docs/.
  • docs/COMPATIBILITY.md documents the limits of Chroma 1.5.9 and Chroma Cloud verified with this client, verifies the known defects again on 10 October 2026, removes KD-2, KD-31 and KD-32, which that verification did not reproduce, and adds KD-52 to KD-57.
  • UpdateAsync and UpsertAsync say what a null document does on each server: Chroma 1.0.0 and later and Chroma Cloud keep the stored document, and every 0.x server deletes it.

NuGet: https://www.nuget.org/packages/ChromaDotNet.Client/2.13.0 and https://www.nuget.org/packages/ChromaDotNet.Client.DependencyInjection/2.13.0

2.12.0

Choose a tag to compare

@Laurianti Laurianti released this 09 Oct 07:45
4e87aee

ChromaDotNet.Client and ChromaDotNet.Client.DependencyInjection 2.12.0, released together.

New

  • ChromaConfigurationOptions.WithUpsertStrategy says how UpdateAsync and UpsertAsync write the embeddings of records that exist. Chroma 1.0.21 to 1.5.9, installed on your own servers, may lose a record from the vector index after such a write (KD-49).
    • Server, the default, writes as before.
    • SkipUnchangedEmbeddings updates without the embedding a record whose embedding does not change, which does not touch the vector index. It costs a get of the records first, and a record whose embedding changes stays exposed.
  • On the server, RAYON_NUM_THREADS=1 made Chroma 1.5.9 lose no record in our measurements, with upserts of records that exist about 5 times slower. The README says when to use which.

Fixes

  • The creation of a collection sends get_or_create once.

Docs

  • docs/COMPATIBILITY.md adds KD-48 to KD-51: wrong neighbors after records are deleted and written again on 1.0.0 to 1.0.5; records lost by the vector index after updates on 1.0.21 to 1.5.9; the space of a collection that 1.0.0 to 1.0.5 cannot tell; a record missing from a query after some upserts on 0.5.15, 0.5.16, 0.5.20 and 0.6.3, the versions tested.

NuGet: https://www.nuget.org/packages/ChromaDotNet.Client/2.12.0 and https://www.nuget.org/packages/ChromaDotNet.Client.DependencyInjection/2.12.0