diff --git a/crates/omnigraph/src/embedding.rs b/crates/omnigraph/src/embedding.rs index 53f6584d..9c8aba3f 100644 --- a/crates/omnigraph/src/embedding.rs +++ b/crates/omnigraph/src/embedding.rs @@ -542,6 +542,25 @@ fn validate_and_normalize_embedding( values.len() )); } + // Reject poisoned vectors BEFORE normalizing: a NaN component makes the + // norm NaN (whose `> EPSILON` comparison is false, silently skipping + // normalization), an Inf component normalizes the rest to 0s and itself + // to NaN, and a zero vector has no direction — all three would persist + // as "successful" embeddings and degrade ANN/IVF for unrelated rows. + if let Some(idx) = values.iter().position(|v| !v.is_finite()) { + return Err(format!( + "embedding component {} is not finite ({})", + idx, values[idx] + )); + } + let norm = values + .iter() + .map(|v| (*v as f64) * (*v as f64)) + .sum::() + .sqrt() as f32; + if norm <= f32::EPSILON { + return Err("embedding has zero norm (no direction)".to_string()); + } Ok(normalize_vector(values)) } diff --git a/docs/user/search/embeddings.md b/docs/user/search/embeddings.md index 11f35406..2f5a55e5 100644 --- a/docs/user/search/embeddings.md +++ b/docs/user/search/embeddings.md @@ -13,7 +13,10 @@ query vectors and document vectors share one model and one vector space. | `gemini` | `POST {base}/models/{model}:embedContent`, `x-goog-api-key`, with `RETRIEVAL_QUERY` / `RETRIEVAL_DOCUMENT` task types | Reaching Google's `generativelanguage` API directly | | `mock` | none — deterministic offline vectors | Tests and local dev without a key | -Vectors are stored L2-normalized as `FixedSizeList(Float32, dim)`; the requested output dimension is driven by +Vectors are stored L2-normalized as `FixedSizeList(Float32, dim)`. Embedding +values must be finite — a vector containing NaN/Inf, or an all-zero vector +(no direction under cosine distance), is rejected at the client boundary +rather than persisted; the requested output dimension is driven by the target column width and sent as Gemini `outputDimensionality` / OpenAI `dimensions`. ## Configuration (cluster)