mirror of
https://github.com/qdrant/qdrant.git
synced 2026-09-21 13:37:46 -05:00
fix: reject mismatched dense dims in recommend average (#10374)
* fix: reject mismatched dense dims in recommend average Stop silently truncating oversized negative examples during average_vector merge. Validate dense dimensions within each example group and between positive/negative averages before zip-merge. Fixes #10369 * Simplify: keep only the merge-time dimension check The zip truncation in merge_positive_and_negative_avg is the only place an oversized negative can silently pass the downstream dimension check; within-group mismatches already grow the average to the max length and fail the segment-entry check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style(query): make recommendation conversion explicit * test: assert recommendation dimension errors Issue: #10369 Make the regression test verify the exact WrongVectorDimension payload for mismatched recommendation vectors. --------- Co-authored-by: qdrant-cloud-bot <111755117+qdrant-cloud-bot@users.noreply.github.com> Co-authored-by: generall <andrey@vasnetsov.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
qdrant-cloud-bot
generall
parent
f8512cbf94
commit
602503a854
@@ -234,6 +234,12 @@ fn merge_positive_and_negative_avg(
|
||||
) -> OperationResult<VectorInternal> {
|
||||
match (positive, negative) {
|
||||
(VectorInternal::Dense(positive), VectorInternal::Dense(negative)) => {
|
||||
if positive.len() != negative.len() {
|
||||
return Err(OperationError::WrongVectorDimension {
|
||||
expected_dim: positive.len(),
|
||||
received_dim: negative.len(),
|
||||
});
|
||||
}
|
||||
let vector: DenseVector = positive
|
||||
.iter()
|
||||
.zip(negative.iter())
|
||||
@@ -268,6 +274,7 @@ mod test {
|
||||
use sparse::common::sparse_vector::SparseVector;
|
||||
|
||||
use super::{avg_vector_for_recommendation, avg_vectors};
|
||||
use crate::common::operation_error::OperationError;
|
||||
use crate::data_types::vectors::{VectorInternal, VectorRef};
|
||||
use crate::vector_storage::query::{Query, RecoBestScoreQuery, RecoQuery};
|
||||
|
||||
@@ -448,5 +455,19 @@ mod test {
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(vector, vec![1.0, 0.0].into());
|
||||
|
||||
// Negative average with a different dimension is rejected, not truncated by zip.
|
||||
let negatives: Vec<VectorInternal> = vec![vec![0.0, 1.0, 2.0].into()];
|
||||
let result = avg_vector_for_recommendation(
|
||||
positives.iter().map(VectorRef::from),
|
||||
negatives.iter().map(VectorRef::from).peekable(),
|
||||
);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(OperationError::WrongVectorDimension {
|
||||
expected_dim: 2,
|
||||
received_dim: 3,
|
||||
})
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,12 +427,12 @@ impl From<QueryEnum> for grpc::QueryEnum {
|
||||
},
|
||||
QueryEnum::RecommendBestScore(named) => grpc::QueryEnum {
|
||||
query: Some(grpc::query_enum::Query::RecommendBestScore(
|
||||
named.query.into(),
|
||||
grpc::RecoQuery::from(named.query),
|
||||
)),
|
||||
},
|
||||
QueryEnum::RecommendSumScores(named) => grpc::QueryEnum {
|
||||
query: Some(grpc::query_enum::Query::RecommendSumScores(
|
||||
named.query.into(),
|
||||
grpc::RecoQuery::from(named.query),
|
||||
)),
|
||||
},
|
||||
QueryEnum::Discover(named) => grpc::QueryEnum {
|
||||
|
||||
Reference in New Issue
Block a user