A Chroma vector database HTTP client for MoonBit: collections, records, queries and embeddings.
moon add SundayT1998/chroma-client///|
async fn quick_start() -> Unit raise ChromaError {
// AsyncHttpTransport is bundled; point the config at your Chroma server.
let client = ChromaClient::new(
AsyncHttpTransport::new(),
config=Config::new(endpoint="http://localhost:8000"),
)
let collection = client.get_or_create_collection_async("notes")
// Chroma 1.x stores vectors, not text: every record needs coordinates.
client.add_async(collection.id, [
Record::of_embedding("r1", [1.0, 0.0, 0.0]).with_document(
"MoonBit is a language for the web",
),
Record::of_embedding("r2", [0.0, 1.0, 0.0]).with_document(
"Chroma stores embeddings",
),
])
let result = client.query_embeddings_async(
collection.id,
[[1.0, 0.0, 0.0]],
n_results=2,
)
for id in result.ids {
println(id)
}
}Chroma 1.x has no server-side embedding. An add whose records carry documents but no vectors is rejected by the server, so the client refuses it locally and points at Record::with_embedding. Produce the vectors with whatever embedding model you already use; Record::of_document(...).with_embedding(vec) is the usual shape.
///|
async fn query_with_everything(
client : ChromaClient[AsyncHttpTransport],
collection_id : String,
) -> Unit raise ChromaError {
ignore(
client.query_embeddings_async(
collection_id,
[[0.1, 0.9, 0.0]],
n_results=5,
include_fields=Include::all(),
),
)
}///|
async fn filtered_get(
client : ChromaClient[AsyncHttpTransport],
collection_id : String,
) -> Unit raise ChromaError {
let filter = Filter::new()
.with_metadata("source", MString("handbook"))
.with_limit(20)
ignore(client.get_async(collection_id, filter~))
}///|
let clause = where_and([
where_eq("topic", MString("ai")),
where_gt("year", MInt(2020)),
])
///|
let text_clause = where_document_or([
where_document_contains("moon"),
where_document_regex("chroma.*client"),
])| chroma-go | here |
|---|---|
| EqString(k, v) / EqInt / EqFloat / EqBool | where_eq(k, MString(v)) / where_eq(k, MInt(v)) … |
| NotEq* | where_ne |
| GtInt, GtFloat, Lt*, Gte*, Lte* | where_gt, where_lt, where_gte, where_lte |
| InString, InInt, … | where_in(k, [MString(..), ..]) |
| And(...), Or(...) | where_and([..]), where_or([..]) |
| Contains(t), NotContains(t), Regex(t) | where_document_contains / _not_contains / _regex |
| AndDocument, OrDocument | where_document_and, where_document_or |
///|
let greeting : MetadataValue = MString("hello")
///|
let answer : MetadataValue = MInt(42)
///|
let ratio : MetadataValue = MFloat(0.5)
///|
let pinned : MetadataValue = MBool(true)
///|
let nothing : MetadataValue = MNull///|
impl AsyncTransport for MyTransport with fn send_async(self, request) {
let headers : @ah.Headers = Map([])
for entry in request.headers {
let (key, value) = entry
headers[key] = value
}
let payload : &@aio.Data = match request.body {
Some(body) => body
None => ""
}
let (response, response_body) = @ah.request(
request.url,
my_method(request.verb),
headers,
payload,
)
let out_headers : Map[String, String] = Map([])
for entry in response.headers {
let (key, value) = entry
out_headers[key.to_string().to_lower()] = value
}
{ status: response.code, headers: out_headers, body: response_body.text(), }
}///|
pub extend MyTransport with AsyncTransport::{send_async}moon run cmd/main --target native -- http://localhost:8000 my-collection///|
let authed_client = ChromaClient::new(
AsyncHttpTransport::new(),
config=Config::new()
.with_token("my-api-token")
.with_basic_auth("user", "password"),
)///|
let timed_client = ChromaClient::new(
AsyncHttpTransport::new(),
config=Config::new(endpoint="http://localhost:8000", timeout_ms=500),
)///|
let timed_sync_client = ChromaClient::new(
TimeoutTransport::new(MemoryTransport::new(), 5000),
)///|
let timed_async_client = ChromaClient::new(
TimeoutAsyncTransport::new(AsyncHttpTransport::new(), 5000),
)///|
fn count_and_preview(
client : ChromaClient[MemoryTransport],
collection_id : String,
) -> Unit raise ChromaError {
let total = client.count(collection_id)
let sample = client.peek(collection_id, limit=5)
println(
total.to_string() + " records, showing " + sample.ids.length().to_string(),
)
}///|
fn rename_collection(
client : ChromaClient[MemoryTransport],
) -> Unit raise ChromaError {
client.modify_collection(
"notes",
new_name=Some("notes-v2"),
metadata=Some([("topic", MString("docs"))]),
)
}///|
async fn page(
client : ChromaClient[AsyncHttpTransport],
collection_id : String,
) -> Unit raise ChromaError {
ignore(
client.get_async(
collection_id,
filter=Filter::new(limit=Some(100), offset=Some(200)),
include_fields=Include::get_default(),
),
)
}///|
// Vector first, text attached afterwards.
let with_text = Record::of_embedding("r1", [1.0, 0.0]).with_document("hello")
///|
// Text first, vector attached afterwards.
let with_vector = Record::of_document("r2", "world").with_embedding([0.0, 1.0])
///|
// Metadata, either way.
let with_metadata = Record::of_embedding("r3", [1.0, 1.0]).with_metadata([
("source", MString("handbook")),
])///|
fn report(result : Result[QueryResult, ChromaError]) -> Unit {
match result {
Err(Transport(message)) => println("could not send: " + message)
Err(api_error) =>
if api_error.status_code() == Some(404) {
println("nothing there")
} else if api_error.is_retryable() {
println("429 or 5xx, worth trying again: " + api_error.message())
} else {
println(api_error.message())
}
Ok(value) => println(value.ids.length().to_string() + " rows")
}
}///|
let retrying_client = ChromaClient::new(
RetryTransport::new(
AsyncHttpTransport::new(),
policy=RetryPolicy::new(
enabled=true,
max_attempts=3,
initial_delay_ms=200,
max_delay_ms=5000,
backoff_factor=2.0,
),
),
)///|
fn retry_with_logging(
transport : MemoryTransport,
) -> RetryTransport[MemoryTransport] {
RetryTransport::new(transport, policy=RetryPolicy::new(enabled=true)).on_retry((
attempt,
delay_ms,
error,
) => {
println(
"retry " +
attempt.to_string() +
" in " +
delay_ms.to_string() +
"ms: " +
error.to_string(),
)
},
)
}moon run cmd/main --target native -- http://localhost:8000 my-collectionsh scripts/ci.shsh scripts/ci.sh fmt check # just formatting and type-checking
sh scripts/ci.sh test coverage # just tests and coveragesh scripts/integration.sh # starts chromadb/chroma, runs, tears downCHROMA_ENDPOINT=http://localhost:8000 \
moon test --target native -p SundayT1998/chroma-client| chroma-go | here |
|---|---|
| Client.Heartbeat | heartbeat / heartbeat_async |
| Client.GetVersion | version / version_async |
| Client.PreFlight | preflight / preflight_async → PreflightChecks |
| Client.GetIdentity | identity / identity_async → Identity |
| Client.CreateCollection | create_collection (get_or_create: false) |
| Client.GetOrCreateCollection | get_or_create_collection (get_or_create: true) |
| Client.GetCollection | get_collection (None on 404) |
| Client.DeleteCollection | delete_collection |
| Client.ListCollections + ListWithLimit/Offset | list_collections(limit~, offset~) |
| Client.CountCollections | count_collections |
| Collection.Add + WithIDs/WithTexts/WithEmbeddings/WithMetadatas | add(collection_id, [Record]) |
| Collection.Upsert | upsert |
| Collection.Update | update (fields left unset are left alone) |
| Collection.Delete + WithIDs/WithWhere/WithWhereDocument | delete(filter); a filter is required |
| Collection.Count | count |
| Collection.Get + WithInclude/WithLimit/WithOffset | get(filter, include_fields~) |
| Collection.Query + WithQueryEmbeddings/WithNResults/WithWhere/WithIDs | query_embeddings(.., n_results~, include_fields~, filter~) |
| Collection.ModifyName / ModifyMetadata | modify_collection(new_name~, metadata~) |
type ChunkProgress = (Int, Int) -> Unitpub(all) suberror ChromaError {
Transport(String)
Status(Int, String)
Api(ApiError)
Decode(String)
InvalidArgument(String)
PartialFailure(PartialFailure)
}pub struct AsyncHttpTransport {
}impl AsyncTransport for AsyncHttpTransportfn[T : Transport] ChromaClient::add(self : ChromaClient[T], collection_id : String, records : Array[Record], options? : WriteOptions, on_chunk? : (Int, Int) -> Unit) -> Unit raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::add_async(self : ChromaClient[T], collection_id : String, records : Array[Record], options? : WriteOptions, on_chunk? : (Int, Int) -> Unit) -> Unit raise ChromaErrorfn[T : Transport] ChromaClient::count(self : ChromaClient[T], collection_id : String) -> Int raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::count_async(self : ChromaClient[T], collection_id : String) -> Int raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::count_collections_async(self : ChromaClient[T]) -> Int raise ChromaErrorfn[T : Transport] ChromaClient::create_collection(self : ChromaClient[T], name : String, config? : CollectionConfig) -> Collection raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::create_collection_async(self : ChromaClient[T], name : String, config? : CollectionConfig) -> Collection raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::create_database_async(self : ChromaClient[T]) -> Unit raise ChromaErrorfn[T : Transport] ChromaClient::delete(self : ChromaClient[T], collection_id : String, filter? : Filter) -> Unit raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::delete_async(self : ChromaClient[T], collection_id : String, filter? : Filter) -> Unit raise ChromaErrorfn[T : Transport] ChromaClient::delete_collection(self : ChromaClient[T], name : String) -> Unit raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::delete_collection_async(self : ChromaClient[T], name : String) -> Unit raise ChromaErrorfn[T : Transport] ChromaClient::get(self : ChromaClient[T], collection_id : String, filter? : Filter, include_fields? : Include) -> QueryResult raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::get_async(self : ChromaClient[T], collection_id : String, filter? : Filter, include_fields? : Include) -> QueryResult raise ChromaErrorfn[T : Transport] ChromaClient::get_collection(self : ChromaClient[T], name : String) -> Collection? raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::get_collection_async(self : ChromaClient[T], name : String) -> Collection? raise ChromaErrorfn[T : Transport] ChromaClient::get_or_create_collection(self : ChromaClient[T], name : String, config? : CollectionConfig) -> Collection raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::get_or_create_collection_async(self : ChromaClient[T], name : String, config? : CollectionConfig) -> Collection raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::heartbeat_async(self : ChromaClient[T]) -> Int64 raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::identity_async(self : ChromaClient[T]) -> Identity raise ChromaErrorfn[T : Transport] ChromaClient::list_collections(self : ChromaClient[T], limit? : Int, offset? : Int) -> Array[Collection] raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::list_collections_async(self : ChromaClient[T], limit? : Int, offset? : Int) -> Array[Collection] raise ChromaErrorfn[T : Transport] ChromaClient::modify_collection(self : ChromaClient[T], name : String, new_name? : String?, metadata? : Array[(String, MetadataValue)]?) -> Unit raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::modify_collection_async(self : ChromaClient[T], name : String, new_name? : String?, metadata? : Array[(String, MetadataValue)]?) -> Unit raise ChromaErrorfn[T : Transport] ChromaClient::peek(self : ChromaClient[T], collection_id : String, limit? : Int, include_fields? : Include) -> QueryResult raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::peek_async(self : ChromaClient[T], collection_id : String, limit? : Int, include_fields? : Include) -> QueryResult raise ChromaErrorfn[T : Transport] ChromaClient::preflight(self : ChromaClient[T]) -> PreflightChecks raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::preflight_async(self : ChromaClient[T]) -> PreflightChecks raise ChromaErrorfn[T : Transport] ChromaClient::query_embeddings(self : ChromaClient[T], collection_id : String, embeddings : Array[Array[Double]], n_results? : Int, include_fields? : Include, filter? : Filter) -> QueryResult raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::query_embeddings_async(self : ChromaClient[T], collection_id : String, embeddings : Array[Array[Double]], n_results? : Int, include_fields? : Include, filter? : Filter) -> QueryResult raise ChromaErrorfn[T : Transport] ChromaClient::update(self : ChromaClient[T], collection_id : String, records : Array[Record], filter? : Filter) -> Unit raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::update_async(self : ChromaClient[T], collection_id : String, records : Array[Record], filter? : Filter) -> Unit raise ChromaErrorfn[T : Transport] ChromaClient::upsert(self : ChromaClient[T], collection_id : String, records : Array[Record], options? : WriteOptions, on_chunk? : (Int, Int) -> Unit) -> Unit raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::upsert_async(self : ChromaClient[T], collection_id : String, records : Array[Record], options? : WriteOptions, on_chunk? : (Int, Int) -> Unit) -> Unit raise ChromaErrorasync fn[T : AsyncTransport] ChromaClient::version_async(self : ChromaClient[T]) -> String raise ChromaErrorpub(all) struct Collection {
id : String
name : String
metadata : Array[(String, MetadataValue)]
configuration : Json
log_position : Int
version : Int
} derive(Eq, Debug)pub(all) struct CollectionConfig {
metadata : Array[(String, MetadataValue)]
configuration : Json?
} derive(Eq, Debug)fn CollectionConfig::new(metadata? : Array[(String, MetadataValue)], configuration? : Json?) -> CollectionConfigimpl Transport for MemoryTransportpub(all) struct Record {
id : String
document : String?
embedding : Array[Double]?
metadata : Array[(String, MetadataValue)]
} derive(Eq, Debug)fn Record::of_document(id : String, document : String, metadata? : Array[(String, MetadataValue)]) -> Recordfn Record::of_embedding(id : String, embedding : Array[Double], metadata? : Array[(String, MetadataValue)]) -> Recordfn RetryPolicy::new(enabled? : Bool, max_attempts? : Int, initial_delay_ms? : Int, max_delay_ms? : Int, backoff_factor? : Double) -> RetryPolicypub(all) struct RetryTransport[T] {
inner : T
policy : RetryPolicy
on_retry : (Int, Int, ChromaError) -> Unit?
}impl Transport for RetryTransport[T]fn[T] RetryTransport::on_retry(self : RetryTransport[T], handler : (Int, Int, ChromaError) -> Unit) -> RetryTransport[T]fn[T : Transport] RetryTransport::send(self : RetryTransport[T], request : Request) -> Response raisefn[T] RetryTransport::with_policy(self : RetryTransport[T], policy : RetryPolicy) -> RetryTransport[T]pub(all) struct TimeoutAsyncTransport[T] {
inner : T
timeout_ms : Int
}impl AsyncTransport for TimeoutAsyncTransport[T]async fn[T : AsyncTransport] send_async(self : TimeoutAsyncTransport[T], request : Request) -> Responseasync fn[T : AsyncTransport] TimeoutAsyncTransport::send_async(self : TimeoutAsyncTransport[T], request : Request) -> Responsepub(all) struct TimeoutTransport[T] {
inner : T
timeout_ms : Int
}let client = ChromaClient::new(TimeoutTransport::new(http_stack, 5000))impl Transport for TimeoutTransport[T]fn[T : Transport] TimeoutTransport::send(self : TimeoutTransport[T], request : Request) -> Response raiselet MAX_RESPONSE_BYTES : Intlet RETRY_DELAY_HEADER : Stringfn encode_path_segment(segment : String) -> Stringfn encode_query_component(value : String) -> Stringfn is_retryable_status(code : Int) -> Boolfn join_url(base : String, path : String) -> Stringfn no_progress(sent : Int, total : Int) -> UnitInstall
Download zipA Chroma vector database HTTP client for MoonBit: collections, records, queries and embeddings.