use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value};
use crate::client::Client;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum VectorMetric {
#[default]
Cosine,
Dot,
L2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum VectorQuantization {
#[default]
None,
Int8,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct VectorItem {
pub id: String,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub vector: Vec<f32>,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub metadata: Map<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct VectorHit {
pub id: String,
#[serde(default)]
pub score: f32,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub metadata: Map<String, Value>,
#[serde(default)]
pub vector: Option<Vec<f32>>,
#[serde(default)]
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct VectorSearchResult {
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub results: Vec<VectorHit>,
#[serde(default)]
pub index: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct VectorCollectionSummary {
pub name: String,
#[serde(default)]
pub dimension: usize,
#[serde(default)]
pub metric: VectorMetric,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct VectorCollectionInfo {
pub collection: String,
#[serde(default)]
pub dimension: usize,
#[serde(default)]
pub metric: VectorMetric,
#[serde(default)]
pub count: i64,
#[serde(default)]
pub quantization: VectorQuantization,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct VectorPage {
pub collection: String,
#[serde(default)]
pub count: usize,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub vectors: Vec<VectorItem>,
#[serde(default)]
pub truncated: bool,
#[serde(default)]
pub total: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ScoreMetric {
Cosine,
Dot,
L2,
L1,
Linf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FilterStrategy {
#[default]
Auto,
Graph,
Exact,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnnIndex {
Hnsw,
Ivf,
IvfPq,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FusionMethod {
#[default]
Rrf,
Max,
Mean,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct SparseVector {
#[serde(default)]
pub indices: Vec<u32>,
#[serde(default)]
pub values: Vec<f32>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct VectorSearchOptions {
pub filter: Option<Value>,
pub min_score: Option<f32>,
pub include_vectors: bool,
pub offset: Option<usize>,
pub include_metadata: Option<bool>,
pub max_distance: Option<f32>,
pub ef_search: Option<usize>,
pub exact: bool,
pub score_metric: Option<ScoreMetric>,
pub filter_strategy: Option<FilterStrategy>,
pub using: Option<String>,
pub tenant: Option<String>,
pub namespace: Option<String>,
pub sparse: Option<SparseVector>,
pub hybrid_alpha: Option<f32>,
pub explain: bool,
pub score_expr: Option<String>,
pub explain_text: Option<String>,
pub ann: Option<AnnIndex>,
pub nprobe: Option<usize>,
}
impl VectorSearchOptions {
fn write(&self, body: &mut Map<String, Value>) {
fn put(body: &mut Map<String, Value>, key: &str, value: Option<impl Serialize>) {
if let Some(value) = value {
body.insert(key.to_string(), json!(value));
}
}
put(body, "filter", self.filter.as_ref());
put(body, "min_score", self.min_score);
put(
body,
"include_vectors",
self.include_vectors.then_some(true),
);
put(body, "offset", self.offset);
put(body, "include_metadata", self.include_metadata);
put(body, "max_distance", self.max_distance);
put(body, "ef_search", self.ef_search);
put(body, "exact", self.exact.then_some(true));
put(body, "score_metric", self.score_metric);
put(body, "filter_strategy", self.filter_strategy);
put(body, "using", self.using.as_ref());
put(body, "tenant", self.tenant.as_ref());
put(body, "namespace", self.namespace.as_ref());
put(body, "sparse", self.sparse.as_ref());
put(body, "hybrid_alpha", self.hybrid_alpha);
put(body, "explain", self.explain.then_some(true));
put(body, "score_expr", self.score_expr.as_ref());
put(body, "explain_text", self.explain_text.as_ref());
put(body, "ann", self.ann);
put(body, "nprobe", self.nprobe);
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(untagged)]
pub enum VectorExample {
Id(String),
Vector(Vec<f32>),
}
impl VectorExample {
pub fn id(id: &str) -> Self {
VectorExample::Id(id.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct VectorUpsert {
pub id: String,
pub vector: Vec<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Map<String, Value>>,
}
impl VectorUpsert {
pub fn new(id: &str, vector: impl Into<Vec<f32>>) -> Self {
Self {
id: id.to_string(),
vector: vector.into(),
metadata: None,
}
}
pub fn metadata(mut self, metadata: Map<String, Value>) -> Self {
self.metadata = Some(metadata);
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct TextUpsert {
pub id: String,
pub text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Map<String, Value>>,
}
impl TextUpsert {
pub fn new(id: &str, text: &str) -> Self {
Self {
id: id.to_string(),
text: text.to_string(),
metadata: None,
}
}
pub fn metadata(mut self, metadata: Map<String, Value>) -> Self {
self.metadata = Some(metadata);
self
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct Embedding {
#[serde(default)]
pub embedder: String,
#[serde(default)]
pub dimension: usize,
#[serde(default)]
pub vectors: Vec<Vec<f32>>,
#[serde(default)]
pub sparse: Vec<SparseVector>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct VectorAlias {
pub alias: String,
#[serde(default)]
pub collection: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct VectorPartition {
pub name: String,
#[serde(default)]
pub count: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct VectorPartitions {
#[serde(default)]
pub field: String,
#[serde(default)]
pub partitions: Vec<VectorPartition>,
#[serde(default)]
pub unpartitioned: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct VectorIndexRebuild {
#[serde(default)]
pub rebuilt: bool,
#[serde(default)]
pub vectors: i64,
#[serde(default)]
pub build_ms: i64,
#[serde(default)]
pub persisted: bool,
#[serde(default)]
pub tombstones_cleared: i64,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct VectorIndexStats {
#[serde(default)]
pub collection: String,
#[serde(default)]
pub index: String,
#[serde(default)]
pub stored: i64,
#[serde(default)]
pub flat_scan_threshold: Option<i64>,
#[serde(default)]
pub layers: Option<i64>,
#[serde(default)]
pub nodes: Option<i64>,
#[serde(default)]
pub live: Option<i64>,
#[serde(default)]
pub tombstones: Option<i64>,
#[serde(default)]
pub reachable: Option<i64>,
#[serde(default)]
pub unreachable: Option<i64>,
#[serde(default)]
pub should_rebuild: Option<bool>,
#[serde(default)]
pub persisted: Option<bool>,
#[serde(default)]
pub entry: Option<String>,
#[serde(default)]
pub live_per_layer: Option<Vec<i64>>,
#[serde(default)]
pub degree: Option<Value>,
#[serde(default)]
pub memory: Option<Value>,
#[serde(default)]
pub parameters: Option<Value>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct RecallOptions {
pub sample: Option<usize>,
pub top_k: Option<usize>,
pub ef_search: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct VectorRecall {
#[serde(default)]
pub collection: String,
#[serde(default)]
pub index: String,
#[serde(default)]
pub measured: bool,
#[serde(default)]
pub recall: f64,
#[serde(default)]
pub min_recall: Option<f64>,
#[serde(default)]
pub queries: Option<i64>,
#[serde(default)]
pub top_k: Option<i64>,
#[serde(default)]
pub ef_search: Option<i64>,
#[serde(default)]
pub flat_fallbacks: Option<i64>,
#[serde(default)]
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct FusedHit {
pub id: String,
#[serde(default)]
pub score: f32,
#[serde(default)]
pub matched: usize,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub metadata: Map<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct FusedSearchResult {
#[serde(default)]
pub fusion: FusionMethod,
#[serde(default)]
pub queries: usize,
#[serde(default)]
pub depth: usize,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub results: Vec<FusedHit>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct VectorConfigChange {
pub normalize: Option<bool>,
pub m: Option<usize>,
pub ef_construction: Option<usize>,
pub multi_tenant: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct NamedVector {
pub name: String,
#[serde(default)]
pub dimension: usize,
#[serde(default)]
pub metric: VectorMetric,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct VectorConfig {
#[serde(default)]
pub collection: String,
#[serde(default)]
pub normalize: bool,
#[serde(default)]
pub m: usize,
#[serde(default)]
pub ef_construction: usize,
#[serde(default)]
pub multi_tenant: bool,
#[serde(default)]
pub named: Vec<NamedVector>,
}
fn op(body: Value) -> Value {
json!({"Vector": body})
}
impl Client {
pub fn vector_create_collection(
&mut self,
collection: &str,
dimension: usize,
metric: VectorMetric,
) -> Result<()> {
self.vector_create_collection_quantized(
collection,
dimension,
metric,
VectorQuantization::None,
)
}
pub fn vector_create_collection_quantized(
&mut self,
collection: &str,
dimension: usize,
metric: VectorMetric,
quantization: VectorQuantization,
) -> Result<()> {
self.send(op(json!({"CreateCollection": {
"collection": collection,
"dimension": dimension,
"metric": metric,
"quantization": quantization,
}})))
.map(|_| ())
}
pub fn vector_drop_collection(&mut self, collection: &str) -> Result<()> {
self.send(op(json!({"DropCollection": {"collection": collection}})))
.map(|_| ())
}
pub fn vector_list_collections(&mut self) -> Result<Vec<VectorCollectionSummary>> {
#[derive(Deserialize)]
struct Details {
#[serde(default)]
details: Vec<VectorCollectionSummary>,
}
let response = self.send(op(Value::String("ListCollections".into())))?;
let out: Details = response.decode("ListCollections")?;
Ok(out.details)
}
pub fn vector_describe_collection(&mut self, collection: &str) -> Result<VectorCollectionInfo> {
let response = self.send(op(
json!({"DescribeCollection": {"collection": collection}}),
))?;
response.decode("DescribeCollection")
}
pub fn vector_upsert(
&mut self,
collection: &str,
id: &str,
vector: &[f32],
metadata: Option<&Map<String, Value>>,
) -> Result<()> {
self.send(op(json!({"Upsert": {
"collection": collection,
"id": id,
"vector": vector,
"metadata": metadata,
}})))
.map(|_| ())
}
pub fn vector_get(&mut self, collection: &str, id: &str) -> Result<Option<VectorItem>> {
let response = self.send(op(json!({"Get": {"collection": collection, "id": id}})))?;
let data = response.expect("Json", "Get")?;
if data.is_null() {
return Ok(None);
}
response.decode("Get").map(Some)
}
pub fn vector_delete(&mut self, collection: &str, id: &str) -> Result<()> {
self.send(op(json!({"Delete": {"collection": collection, "id": id}})))
.map(|_| ())
}
pub fn vector_search(
&mut self,
collection: &str,
vector: &[f32],
top_k: usize,
) -> Result<VectorSearchResult> {
self.vector_search_filtered(collection, vector, top_k, &BTreeMap::new())
}
pub fn vector_search_filtered(
&mut self,
collection: &str,
vector: &[f32],
top_k: usize,
filter: &BTreeMap<String, Value>,
) -> Result<VectorSearchResult> {
let filter = if filter.is_empty() {
Value::Null
} else {
json!(filter)
};
let response = self.send(op(json!({"Search": {
"collection": collection,
"vector": vector,
"top_k": top_k,
"filter": filter,
}})))?;
response.decode("Search")
}
pub fn vector_list_vectors(
&mut self,
collection: &str,
limit: Option<usize>,
offset: Option<usize>,
) -> Result<VectorPage> {
let mut body = Map::new();
body.insert("collection".into(), json!(collection));
if let Some(limit) = limit.filter(|l| *l > 0) {
body.insert("limit".into(), json!(limit));
}
if let Some(offset) = offset.filter(|o| *o > 0) {
body.insert("offset".into(), json!(offset));
}
let response = self.send(op(json!({"ListVectors": Value::Object(body)})))?;
response.decode("ListVectors")
}
pub fn vector_embed(&mut self, texts: &[&str], dimension: usize) -> Result<Embedding> {
let response = self.send(op(json!({"Embed": {
"texts": texts,
"dimension": dimension,
}})))?;
response.decode("Embed")
}
pub fn vector_upsert_text(&mut self, collection: &str, items: &[TextUpsert]) -> Result<i64> {
let response = self.send(op(json!({"UpsertText": {
"collection": collection,
"items": items,
}})))?;
Ok(response.decode::<Upserted>("UpsertText")?.upserted)
}
pub fn vector_set_alias(&mut self, alias: &str, collection: Option<&str>) -> Result<()> {
self.send(op(json!({"SetAlias": {
"alias": alias,
"collection": collection,
}})))
.map(|_| ())
}
pub fn vector_list_aliases(&mut self) -> Result<Vec<VectorAlias>> {
#[derive(Deserialize)]
struct Aliases {
#[serde(default)]
aliases: Vec<VectorAlias>,
}
let response = self.send(op(Value::String("ListAliases".into())))?;
let out: Aliases = response.decode("ListAliases")?;
Ok(out.aliases)
}
pub fn vector_add_named_vector(
&mut self,
collection: &str,
name: &str,
dimension: usize,
metric: VectorMetric,
) -> Result<()> {
self.send(op(json!({"AddNamedVector": {
"collection": collection,
"name": name,
"dimension": dimension,
"metric": metric,
}})))
.map(|_| ())
}
pub fn vector_upsert_named(
&mut self,
collection: &str,
id: &str,
vectors: &BTreeMap<String, Vec<f32>>,
metadata: Option<&Map<String, Value>>,
) -> Result<()> {
self.send(op(json!({"UpsertNamed": {
"collection": collection,
"id": id,
"vectors": vectors,
"metadata": metadata,
}})))
.map(|_| ())
}
pub fn vector_upsert_many(&mut self, collection: &str, items: &[VectorUpsert]) -> Result<i64> {
let response = self.send(op(json!({"UpsertMany": {
"collection": collection,
"items": items,
}})))?;
Ok(response.decode::<Upserted>("UpsertMany")?.upserted)
}
pub fn vector_delete_many(&mut self, collection: &str, ids: &[&str]) -> Result<i64> {
let response = self.send(op(json!({"DeleteMany": {
"collection": collection,
"ids": ids,
}})))?;
Ok(response.decode::<DeletedCount>("DeleteMany")?.deleted)
}
pub fn vector_search_batch(
&mut self,
collection: &str,
vectors: &[Vec<f32>],
top_k: usize,
options: &VectorSearchOptions,
) -> Result<Vec<Vec<VectorHit>>> {
#[derive(Deserialize)]
struct Batch {
#[serde(default)]
results: Vec<Vec<VectorHit>>,
}
let mut body = Map::new();
body.insert("collection".into(), json!(collection));
body.insert("vectors".into(), json!(vectors));
body.insert("top_k".into(), json!(top_k));
options.write(&mut body);
let response = self.send(op(json!({"SearchBatch": Value::Object(body)})))?;
let out: Batch = response.decode("SearchBatch")?;
Ok(out.results)
}
pub fn vector_search_multi(
&mut self,
collection: &str,
vectors: &[Vec<f32>],
top_k: usize,
fusion: FusionMethod,
filter: Option<&Value>,
) -> Result<FusedSearchResult> {
let mut body = Map::new();
body.insert("collection".into(), json!(collection));
body.insert("vectors".into(), json!(vectors));
body.insert("top_k".into(), json!(top_k));
body.insert("fusion".into(), json!(fusion));
if let Some(filter) = filter {
body.insert("filter".into(), filter.clone());
}
let response = self.send(op(json!({"SearchMulti": Value::Object(body)})))?;
response.decode("SearchMulti")
}
pub fn vector_search_by_examples(
&mut self,
collection: &str,
positive: &[VectorExample],
negative: &[VectorExample],
top_k: usize,
exclude_examples: bool,
options: &VectorSearchOptions,
) -> Result<VectorSearchResult> {
let mut body = Map::new();
body.insert("collection".into(), json!(collection));
body.insert("positive".into(), json!(positive));
body.insert("negative".into(), json!(negative));
body.insert("top_k".into(), json!(top_k));
body.insert("exclude_examples".into(), json!(exclude_examples));
options.write(&mut body);
let response = self.send(op(json!({"SearchByExamples": Value::Object(body)})))?;
response.decode("SearchByExamples")
}
pub fn vector_delete_by_filter(&mut self, collection: &str, filter: &Value) -> Result<i64> {
let response = self.send(op(json!({"DeleteByFilter": {
"collection": collection,
"filter": filter,
}})))?;
Ok(response.decode::<DeletedCount>("DeleteByFilter")?.deleted)
}
pub fn vector_update_by_filter(
&mut self,
collection: &str,
filter: &Value,
set: &Map<String, Value>,
) -> Result<i64> {
#[derive(Deserialize)]
struct Updated {
#[serde(default)]
updated: i64,
}
let response = self.send(op(json!({"UpdateByFilter": {
"collection": collection,
"filter": filter,
"set": set,
}})))?;
Ok(response.decode::<Updated>("UpdateByFilter")?.updated)
}
pub fn vector_count(&mut self, collection: &str, filter: Option<&Value>) -> Result<i64> {
#[derive(Deserialize)]
struct Count {
#[serde(default)]
count: i64,
}
let mut body = Map::new();
body.insert("collection".into(), json!(collection));
if let Some(filter) = filter {
body.insert("filter".into(), filter.clone());
}
let response = self.send(op(json!({"Count": Value::Object(body)})))?;
Ok(response.decode::<Count>("Count")?.count)
}
pub fn vector_list_partitions(
&mut self,
collection: &str,
field: Option<&str>,
) -> Result<VectorPartitions> {
let mut body = Map::new();
body.insert("collection".into(), json!(collection));
if let Some(field) = field {
body.insert("field".into(), json!(field));
}
let response = self.send(op(json!({"ListPartitions": Value::Object(body)})))?;
response.decode("ListPartitions")
}
pub fn vector_rename_collection(&mut self, collection: &str, to: &str) -> Result<i64> {
#[derive(Deserialize)]
struct Moved {
#[serde(default)]
moved: i64,
}
let response = self.send(op(json!({"RenameCollection": {
"collection": collection,
"to": to,
}})))?;
Ok(response.decode::<Moved>("RenameCollection")?.moved)
}
pub fn vector_copy_collection(&mut self, collection: &str, to: &str) -> Result<i64> {
#[derive(Deserialize)]
struct Copied {
#[serde(default)]
copied: i64,
}
let response = self.send(op(json!({"CopyCollection": {
"collection": collection,
"to": to,
}})))?;
Ok(response.decode::<Copied>("CopyCollection")?.copied)
}
pub fn vector_rebuild_index(&mut self, collection: &str) -> Result<VectorIndexRebuild> {
let response = self.send(op(json!({"RebuildIndex": {"collection": collection}})))?;
response.decode("RebuildIndex")
}
pub fn vector_index_stats(&mut self, collection: &str) -> Result<VectorIndexStats> {
let response = self.send(op(json!({"IndexStats": {"collection": collection}})))?;
response.decode("IndexStats")
}
pub fn vector_measure_recall(
&mut self,
collection: &str,
options: &RecallOptions,
) -> Result<VectorRecall> {
let mut body = Map::new();
body.insert("collection".into(), json!(collection));
if let Some(sample) = options.sample {
body.insert("sample".into(), json!(sample));
}
if let Some(top_k) = options.top_k {
body.insert("top_k".into(), json!(top_k));
}
if let Some(ef_search) = options.ef_search {
body.insert("ef_search".into(), json!(ef_search));
}
let response = self.send(op(json!({"MeasureRecall": Value::Object(body)})))?;
response.decode("MeasureRecall")
}
pub fn vector_set_collection_config(
&mut self,
collection: &str,
change: &VectorConfigChange,
) -> Result<VectorConfig> {
if *change == VectorConfigChange::default() {
return Err(Error::invalid(
"a configuration change must set at least one of `normalize`, `m`, `ef_construction` or `multi_tenant`",
));
}
let mut body = Map::new();
body.insert("collection".into(), json!(collection));
if let Some(normalize) = change.normalize {
body.insert("normalize".into(), json!(normalize));
}
if let Some(m) = change.m {
body.insert("m".into(), json!(m));
}
if let Some(ef_construction) = change.ef_construction {
body.insert("ef_construction".into(), json!(ef_construction));
}
if let Some(multi_tenant) = change.multi_tenant {
body.insert("multi_tenant".into(), json!(multi_tenant));
}
let response = self.send(op(json!({"SetCollectionConfig": Value::Object(body)})))?;
response.decode("SetCollectionConfig")
}
}
#[derive(Deserialize)]
struct Upserted {
#[serde(default)]
upserted: i64,
}
#[derive(Deserialize)]
struct DeletedCount {
#[serde(default)]
deleted: i64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn metrics_and_quantizations_use_the_servers_spelling() {
assert_eq!(
serde_json::to_value(VectorMetric::Cosine).unwrap(),
"cosine"
);
assert_eq!(serde_json::to_value(VectorMetric::Dot).unwrap(), "dot");
assert_eq!(serde_json::to_value(VectorMetric::L2).unwrap(), "l2");
assert_eq!(
serde_json::to_value(VectorQuantization::None).unwrap(),
"none"
);
assert_eq!(
serde_json::to_value(VectorQuantization::Int8).unwrap(),
"int8"
);
}
#[test]
fn metrics_round_trip_from_the_wire() {
let metric: VectorMetric = serde_json::from_str("\"l2\"").unwrap();
assert_eq!(metric, VectorMetric::L2);
assert_eq!(VectorMetric::default(), VectorMetric::Cosine);
assert_eq!(VectorQuantization::default(), VectorQuantization::None);
}
#[test]
fn a_hit_decodes_with_its_metadata() {
let hit: VectorHit =
serde_json::from_value(json!({"id": "a", "score": -0.02, "metadata": {"kind": "doc"}}))
.unwrap();
assert_eq!(hit.id, "a");
assert_eq!(hit.metadata["kind"], "doc");
}
}