Files
qdrant/lib/api/src/rest/validate.rs
Luis Cossío e379d78c3c Custom RRF k parameter (#7065)
* allow custom K parameter for RRF

* generate grpc docs and openapi

* use tagged type approach for parametrized fusions

* use params approach in grpc

* simplify api structure

* upd schema

* nits

* rest: parameterized rrf as query variant

* consistency in doc comments

---------

Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
2025-08-26 13:18:02 +02:00

274 lines
9.6 KiB
Rust

use std::borrow::Cow;
use common::validation::validate_multi_vector;
use segment::index::query_optimization::rescore_formula::parsed_formula::VariableId;
use validator::{Validate, ValidationError, ValidationErrors};
use super::{
Batch, BatchVectorStruct, ContextInput, Expression, FormulaQuery, Fusion, NamedVectorStruct,
OrderByInterface, PointVectors, Query, QueryInterface, RecommendInput, Sample, VectorInput,
};
impl Validate for NamedVectorStruct {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
match self {
NamedVectorStruct::Default(_) => Ok(()),
NamedVectorStruct::Dense(_) => Ok(()),
NamedVectorStruct::Sparse(v) => v.validate(),
}
}
}
impl Validate for QueryInterface {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
match self {
QueryInterface::Nearest(vector) => vector.validate(),
QueryInterface::Query(query) => query.validate(),
}
}
}
impl Validate for Query {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
match self {
Query::Nearest(vector) => vector.validate(),
Query::Recommend(recommend) => recommend.validate(),
Query::Discover(discover) => discover.validate(),
Query::Context(context) => context.validate(),
Query::Fusion(fusion) => fusion.validate(),
Query::Rrf(rrf) => rrf.validate(),
Query::Formula(formula) => formula.validate(),
Query::OrderBy(order_by) => order_by.validate(),
Query::Sample(sample) => sample.validate(),
}
}
}
impl Validate for VectorInput {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
match self {
VectorInput::Id(_id) => Ok(()),
VectorInput::DenseVector(_dense) => Ok(()),
VectorInput::SparseVector(sparse) => sparse.validate(),
VectorInput::MultiDenseVector(multi) => validate_multi_vector(multi),
VectorInput::Document(doc) => doc.validate(),
VectorInput::Image(image) => image.validate(),
VectorInput::Object(obj) => obj.validate(),
}
}
}
impl Validate for RecommendInput {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
let no_positives = self.positive.as_ref().map(|p| p.is_empty()).unwrap_or(true);
let no_negatives = self.negative.as_ref().map(|n| n.is_empty()).unwrap_or(true);
if no_positives && no_negatives {
let mut errors = validator::ValidationErrors::new();
errors.add(
"positives, negatives",
ValidationError::new(
"At least one positive or negative vector/id must be provided",
),
);
return Err(errors);
}
for item in self.iter() {
item.validate()?;
}
Ok(())
}
}
impl Validate for ContextInput {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
for item in self.0.iter().flatten().flat_map(|item| item.iter()) {
item.validate()?;
}
Ok(())
}
}
impl Validate for Fusion {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
match self {
Fusion::Rrf | Fusion::Dbsf => Ok(()),
}
}
}
impl Validate for FormulaQuery {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
let Self { formula, defaults } = self;
// validate formula Expression
formula.validate()?;
let mut errors = validator::ValidationErrors::new();
for (key, value) in defaults.iter() {
let var_id = match key.parse() {
Ok(var_id) => var_id,
Err(err) => {
let validation =
ValidationError::new("Invalid variable name").with_message(Cow::Owned(err));
errors.add("defaults", validation);
continue;
}
};
match var_id {
VariableId::Score(_) if value.as_number().is_none() => {
let validation = ValidationError::new("Score default must be a number");
errors.add("defaults", validation);
}
_ => (),
}
}
Ok(())
}
}
impl Validate for OrderByInterface {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
match self {
OrderByInterface::Key(_key) => Ok(()), // validated during parsing
OrderByInterface::Struct(order_by) => order_by.validate(),
}
}
}
impl Validate for Sample {
fn validate(&self) -> Result<(), ValidationErrors> {
match self {
Sample::Random => Ok(()),
}
}
}
impl Validate for BatchVectorStruct {
fn validate(&self) -> Result<(), ValidationErrors> {
match self {
BatchVectorStruct::Single(_) => Ok(()),
BatchVectorStruct::MultiDense(vectors) => {
for vector in vectors {
validate_multi_vector(vector)?;
}
Ok(())
}
BatchVectorStruct::Named(v) => {
common::validation::validate_iter(v.values().flat_map(|batch| batch.iter()))
}
BatchVectorStruct::Document(_) => Ok(()),
BatchVectorStruct::Image(_) => Ok(()),
BatchVectorStruct::Object(_) => Ok(()),
}
}
}
impl Validate for Batch {
fn validate(&self) -> Result<(), ValidationErrors> {
let batch = self;
let bad_input_description = |ids: usize, vecs: usize| -> String {
format!("number of ids and vectors must be equal ({ids} != {vecs})")
};
let create_error = |message: String| -> ValidationErrors {
let mut errors = ValidationErrors::new();
errors.add("batch", {
let mut error = ValidationError::new("point_insert_operation");
error.message.replace(Cow::from(message));
error
});
errors
};
self.vectors.validate()?;
match &batch.vectors {
BatchVectorStruct::Single(vectors) => {
if batch.ids.len() != vectors.len() {
return Err(create_error(bad_input_description(
batch.ids.len(),
vectors.len(),
)));
}
}
BatchVectorStruct::MultiDense(vectors) => {
if batch.ids.len() != vectors.len() {
return Err(create_error(bad_input_description(
batch.ids.len(),
vectors.len(),
)));
}
}
BatchVectorStruct::Named(named_vectors) => {
for vectors in named_vectors.values() {
if batch.ids.len() != vectors.len() {
return Err(create_error(bad_input_description(
batch.ids.len(),
vectors.len(),
)));
}
}
}
BatchVectorStruct::Document(_) => {}
BatchVectorStruct::Image(_) => {}
BatchVectorStruct::Object(_) => {}
}
if let Some(payload_vector) = &batch.payloads
&& payload_vector.len() != batch.ids.len()
{
return Err(create_error(format!(
"number of ids and payloads must be equal ({} != {})",
batch.ids.len(),
payload_vector.len(),
)));
}
Ok(())
}
}
impl Validate for PointVectors {
fn validate(&self) -> Result<(), ValidationErrors> {
if self.vector.is_empty() {
let mut err = ValidationError::new("length");
err.message = Some(Cow::from("must specify vectors to update for point"));
err.add_param(Cow::from("min"), &1);
let mut errors = ValidationErrors::new();
errors.add("vector", err);
Err(errors)
} else {
self.vector.validate()
}
}
}
impl Validate for Expression {
fn validate(&self) -> Result<(), ValidationErrors> {
match self {
Expression::Constant(_) => Ok(()),
Expression::Variable(_) => Ok(()),
Expression::Condition(condition) => condition.validate(),
Expression::GeoDistance(_) => Ok(()),
Expression::Datetime(_) => Ok(()),
Expression::DatetimeKey(_) => Ok(()),
Expression::Mult(mult_expression) => mult_expression.validate(),
Expression::Sum(sum_expression) => sum_expression.validate(),
Expression::Neg(neg_expression) => neg_expression.validate(),
Expression::Abs(abs_expression) => abs_expression.validate(),
Expression::Div(div_expression) => div_expression.validate(),
Expression::Sqrt(sqrt_expression) => sqrt_expression.validate(),
Expression::Pow(pow_expression) => pow_expression.validate(),
Expression::Exp(exp_expression) => exp_expression.validate(),
Expression::Log10(log10_expression) => log10_expression.validate(),
Expression::Ln(ln_expression) => ln_expression.validate(),
Expression::LinDecay(lin_decay_expression) => lin_decay_expression.validate(),
Expression::ExpDecay(exp_decay_expression) => exp_decay_expression.validate(),
Expression::GaussDecay(gauss_decay_expression) => gauss_decay_expression.validate(),
}
}
}