///|
/// Configuration for talking to a Chroma server.
///
/// The client speaks the v2 API, so `endpoint` is normally
/// `http://localhost:8000` and the tenant/database are separate fields.
pub(all) struct Config {
endpoint : String
tenant : String
database : String
headers : Map[String, String]
timeout_ms : Int32
} derive(Eq, Debug)
///|
/// Builds a `Config` with the Chroma defaults: `localhost:8000`,
/// tenant `default_tenant`, database `default_database`.
pub fn Config::new(
endpoint~ : String = "http://localhost:8000",
tenant~ : String = "default_tenant",
database~ : String = "default_database",
headers~ : Map[String, String] = {},
timeout_ms~ : Int32 = 30000,
) -> Config {
{ endpoint, tenant, database, headers, timeout_ms, }
}
///|
/// Returns a copy of the configuration with a Bearer token in
/// `Authorization`.
pub fn Config::with_token(self : Config, token : String) -> Config {
self.with_header("authorization", "Bearer " + token)
}
///|
/// Returns a copy of the configuration with HTTP Basic authentication.
pub fn Config::with_basic_auth(
self : Config,
username : String,
password : String,
) -> Config {
self.with_header(
"authorization",
"Basic " + @base64.encode(@utf8.encode(username + ":" + password)),
)
}
///|
/// Returns a copy of the configuration with a merged header map, leaving
/// `self` untouched.
pub fn Config::with_headers(
self : Config,
headers : Map[String, String],
) -> Config {
let merged = self.headers.copy()
for entry in headers {
let (key, value) = entry
merged[key] = value
}
{ ..self, headers: merged, }
}
///|
/// Returns a copy of the configuration with one extra header set.
pub fn Config::with_header(
self : Config,
name : String,
value : String,
) -> Config {
let headers = self.headers.copy()
headers[name] = value
{ ..self, headers, }
}
///|
/// Metadata attached to a collection or a record.
///
/// Chroma only accepts scalar metadata values, which `MetadataValue`
/// models directly.
pub(all) enum MetadataValue {
MString(String)
MInt(Int32)
MFloat(Double)
MBool(Bool)
MNull
} derive(Eq, Debug)
///|
/// Ordered list of `(key, value)` metadata pairs.
pub type Metadata = Array[(String, MetadataValue)]
///|
/// Converts a JSON scalar into a `MetadataValue`, returning `None` for
/// arrays and objects since Chroma rejects them as metadata.
pub fn MetadataValue::from_json(value : Json) -> MetadataValue? {
match value {
Json::String(s) => Some(MString(s))
Json::Number(n, ..) =>
// Chroma round-trips integers and floats through the same JSON number
// type, so integral values are reported back as `MInt`.
if n == n.floor() && n.abs() < 9.007199254740992e15 {
Some(MInt(n.to_int()))
} else {
Some(MFloat(n))
}
Json::True => Some(MBool(true))
Json::False => Some(MBool(false))
Json::Null => Some(MNull)
Json::Array(_) | Json::Object(_) => None
}
}
///|
/// Converts a `MetadataValue` into the JSON representation Chroma expects.
pub fn MetadataValue::to_json(self : MetadataValue) -> Json {
match self {
MString(s) => Json::string(s)
MInt(i) => Json::number(i.to_double(), repr=i.to_string())
MFloat(f) => Json::number(f)
MBool(b) => Json::boolean(b)
MNull => Json::null()
}
}
///|
/// Converts a `Metadata` list into a JSON object.
pub fn metadata_to_json(metadata : Metadata) -> Json {
let fields : Map[String, Json] = Map([])
for entry in metadata {
let (key, value) = entry
fields[key] = value.to_json()
}
Json::object(fields)
}
///|
/// Reads a JSON object into a `Metadata`.
///
/// Values Chroma cannot store (arrays and objects) are skipped rather than
/// failing the whole response.
pub fn metadata_from_json(json : Json) -> Metadata {
match json {
Json::Object(fields) => {
let out : Metadata = []
for entry in fields {
let (key, value) = entry
match MetadataValue::from_json(value) {
Some(v) => out.push((key, v))
None => ()
}
}
out
}
_ => []
}
}
///|
/// A vector embedding as returned or requested by Chroma.
pub type Embedding = Array[Double]
///|
/// Converts an embedding to a JSON array of numbers.
pub fn embedding_to_json(embedding : Embedding) -> Json {
let items : Array[Json] = []
for value in embedding {
items.push(Json::number(value))
}
Json::array(items)
}
///|
/// Reads a JSON array of numbers into an embedding.
pub fn embedding_from_json(json : Json) -> Embedding raise ChromaError {
match json {
Json::Array(items) => {
let out : Embedding = []
for item in items {
match item {
Json::Number(n, ..) => out.push(n)
_ =>
raise ChromaError::Decode("embedding contains a non-number element")
}
}
out
}
_ => raise ChromaError::Decode("expected an array of numbers for embedding")
}
}
///|
pub extend Config with Eq::{not_equal, equal}
///|
pub extend Config with @debug.Debug::{to_repr}
///|
pub extend MetadataValue with Eq::{not_equal, equal}
///|
pub extend MetadataValue with @debug.Debug::{to_repr}