fix(cluster-rag): reject invalid coordinates

This commit is contained in:
ruvnet 2026-08-08 18:35:48 -04:00
parent 16badffb1d
commit 49a928cdfd
4 changed files with 32 additions and 10 deletions

View file

@ -38,6 +38,13 @@ pub fn kmeans(vectors: &[Vec<f32>], k: usize, iters: usize) -> KMeansResult {
vectors.iter().all(|vector| vector.len() == dim),
"all vectors must have the same dimension"
);
assert!(
vectors
.iter()
.flatten()
.all(|coordinate| coordinate.is_finite()),
"all vector coordinates must be finite"
);
// Initialise centroids from distinct points (deterministic k-means++ max-dist).
let init_ids = crate::dataset::initial_centroid_indices(vectors, k);
@ -233,4 +240,16 @@ mod tests {
assert_eq!(result.assignments, vec![0, 0, 1]);
assert_eq!(result.cluster_sizes, vec![2, 1]);
}
#[test]
#[should_panic(expected = "all vector coordinates must be finite")]
fn nan_coordinates_are_rejected() {
let _ = kmeans(&[vec![0.0], vec![f32::NAN]], 1, 1);
}
#[test]
#[should_panic(expected = "all vector coordinates must be finite")]
fn infinite_coordinates_are_rejected() {
let _ = kmeans(&[vec![0.0], vec![f32::INFINITY]], 1, 1);
}
}

View file

@ -12,19 +12,20 @@
## Context
Agent memory corpora grow continuously. A corpus of 10K vectors (a single agent session) can be searched by brute force at ~670 QPS. At 1M vectors the same approach yields ~6 QPS — too slow for interactive use. RuVector needs a simple, zero-dependency cluster index that:
Agent memory corpora grow continuously. In the refreshed synthetic benchmark, a corpus of 10K vectors is searched by brute force at ~2,000 QPS. Assuming linear scan scaling, 1M vectors would yield roughly 20 QPS — too slow for latency-sensitive interactive use. RuVector needs a simple, zero-dependency cluster index that:
1. Reduces per-query scan cost without requiring a full HNSW graph.
2. Integrates with the coherence primitives already in `ruvector-coherence`.
3. Compiles to WASM for edge deployments.
4. Supports incremental inserts without graph maintenance.
This ADR records the design decision for a two-level cluster-summary index (ClusterTree) and a coherence-weighted query routing variant (CoherenceTree), both benchmarked against brute-force baseline.
This Proposed ADR records a prototype for a two-level cluster-summary index (`ClusterTree`) and a coherence-weighted query routing variant (`CoherenceTree`), both benchmarked against a brute-force baseline. The decision remains Proposed until validation on a real embedding corpus is complete.
**Correction (2026-08-08):** renumbered this Proposed decision from ADR-298
to ADR-300 because ADR-298 is already accepted for namespace-merge routing.
K-means now performs a final assignment pass against the returned centroids so
cohesion and inverted lists cannot retain membership from the prior Lloyd step.
The prototype K-means now performs a final assignment pass against the returned
centroids so cohesion and inverted lists cannot retain membership from the prior
Lloyd step.
---
@ -102,7 +103,7 @@ Full RAPTOR builds cluster summaries using an LLM — the text summary becomes t
## Implementation Plan
1. `ruvector-cluster-rag` crate: **done** (this ADR).
1. `ruvector-cluster-rag` prototype crate: **done**; acceptance remains pending real-corpus validation.
2. Validate on real embedding corpus: next step (ann-benchmarks SIFT1M or MS-MARCO embeddings).
3. Online insert feature: buffer new vectors, absorb into nearest centroid after `N` inserts or `ttl` seconds.
4. Adaptive nprobe controller: borrow ruFlo feedback loop from `ruvector-speculative-ann`.
@ -112,7 +113,7 @@ Full RAPTOR builds cluster summaries using an LLM — the text summary becomes t
---
## Benchmark Evidence
## Prototype Benchmark Evidence
Run: `cargo run --release -p ruvector-cluster-rag --bin benchmark`
Date: 2026-08-08, x86_64 Linux, release build.
@ -125,9 +126,11 @@ Dataset: n=10,000, dim=128, k=10, 500 queries, k_clusters=40, nprobe=20.
| CoherenceTree (50% nprobe) | 336.4 | 469.1 | 2972 | 0.775 |
Memory overhead: 2.0% above raw leaf storage.
Acceptance gate: PASS (both cluster variants ≥ 0.70 recall@10).
Synthetic prototype gate: PASS (both cluster variants ≥ 0.70 recall@10).
All numbers are from a real `cargo run --release` invocation. No aspirational values.
This evidence validates only the synthetic prototype gate; it does not accept the
ADR or establish production readiness without the planned real-corpus validation.
---

View file

@ -293,7 +293,7 @@ The 2% overhead is negligible. For edge/WASM deployments the centroid-only struc
Search cost per query: O(k × d + nprobe × (n/k) × d)
= O(d × (k + nprobe × n/k))
Optimal nprobe balances the two terms. At k=40, n=10K, d=128, nprobe=20: this is 128 × (40 + 20 × 250) = 128 × 5040 ≈ 645K FLOP vs. FlatBrute's 128 × 10K = 1.28M FLOP — a theoretical 1.99× speedup. Measured speedup is 1.441.52×, consistent (remainder from sorting overhead and memory bandwidth).
Optimal nprobe balances the two terms. At k=40, n=10K, d=128, nprobe=20: this is 128 × (40 + 20 × 250) = 128 × 5040 ≈ 645K FLOP vs. FlatBrute's 128 × 10K = 1.28M FLOP — a theoretical 1.99× speedup. Measured speedup is 1.491.52×, consistent (remainder from sorting overhead and memory bandwidth).
---

View file

@ -16,11 +16,11 @@ Decision record: [ADR-300](../../../adr/ADR-300-hierarchical-cluster-rag.md).
AI agents that run across multiple sessions accumulate memory — past decisions, retrieved context, user preferences, task histories. A single long-running agent session can build tens of thousands of embedding vectors. Retrieving the right memory at query time is the linchpin of effective agent reasoning, and the naive approach — brute-force cosine or L2 scan over all stored vectors — does not scale.
At 10K vectors with 128 dimensions, brute-force search takes ~1.5ms per query on x86_64. At 1M vectors it would take ~150ms — far too slow for interactive agents or high-throughput pipelines. The industry default solution is HNSW (Hierarchical Navigable Small World graphs), which achieves excellent recall (~95%) with sub-millisecond latency but requires O(n·M·log n) memory and a significant bookkeeping cost for every insert and delete. For growing agent memory corpora that are continuously updated, this maintenance overhead is a real production burden.
At 10K vectors with 128 dimensions, the refreshed benchmark measures brute-force search at ~0.5ms per query on x86_64. Assuming linear scan scaling, 1M vectors would take roughly 50ms — too slow for latency-sensitive interactive agents or high-throughput pipelines. The industry default solution is HNSW (Hierarchical Navigable Small World graphs), which achieves excellent recall (~95%) with sub-millisecond latency but requires O(n·M·log n) memory and a significant bookkeeping cost for every insert and delete. For growing agent memory corpora that are continuously updated, this maintenance overhead is a real production burden.
This nightly research implements a simpler alternative: a two-level cluster tree, loosely inspired by RAPTOR (Paranjape et al., ICLR 2024). The idea is to partition agent memory into k clusters via k-means, then at query time score each cluster's relevance and expand only the top-nprobe most promising ones. This is structurally equivalent to Inverted File Indexing (IVF) from FAISS, with one important addition: each cluster is also scored by its *internal cohesion* — the mean cosine similarity of members to their centroid — so tight, semantically concentrated clusters are preferred over loose, spread-out ones.
The result: 1.441.52× speedup over brute force at 50% nprobe coverage, with only 2% memory overhead above the raw vector storage, in a zero-dependency Rust crate that compiles to WASM.
The result: 1.491.52× speedup over brute force at 50% nprobe coverage, with only 2% memory overhead above the raw vector storage, in a zero-dependency Rust crate that compiles to WASM.
The honest finding: on *uniform random data*, the coherence weighting adds no recall advantage — all clusters look equally cohesive. The benefit emerges on *structured data* where topic clusters have meaningfully different tightness. Measuring this on real agent memory embeddings is the next step.