feat(gravity): generic UMAP + HDBSCAN clustering via wasm#689
Conversation
Add @bpinternal/gravity: a generic density-based clustering package. The engine (UMAP + HDBSCAN) is written in Rust, compiled to wasm32-unknown-unknown, and exposed through a small TypeScript cluster() wrapper with configurable parameters. No native deps.
Greptile SummaryThis PR introduces
Confidence Score: 5/5The core pipeline is solid and both prior blocking issues have been resolved cleanly. The only open items are minor CI workflow hygiene points that don't affect correctness or the published artefact. The HDBSCAN error path is now Result-based with a readable message rather than a WASM trap, the cosine-distance formula correctly normalises both operands, and all five Vitest tests (including the new empty-dataset regression test) pass. The Rust and TypeScript layer boundary is clean: alloc/dealloc round-trips are capacity-correct, the try/finally in TypeScript guarantees dealloc even on trap, and the serde boundary means NaN/Infinity values are caught by deserialisation before reaching any Rust unwrap. The only nits are in the CI workflow configuration. .github/workflows/gravity.yml — push trigger path filter and unpinned action refs. Important Files Changed
Reviews (3): Last reviewed commit: "fix(gravity): true cosine distance for c..." | Re-trigger Greptile |
The output module's native file-writers were never compiled into wasm, and the raw/umap embedding text exports it fed were not read by the TS wrapper. Remove the module, its wasm exports, and the now-orphaned umap_embeddings field on ClusteringOutput.
…ors cleanly - centroid_cosine_distances now divides by both operand magnitudes, so assignRadius is a correct cosine-distance radius regardless of whether inputs are pre-normalized. - hdbscan_labels / process_embeddings return Result and propagate failures through run_clustering_wasm via set_error + a non-zero exit code, so the TS wrapper throws a readable Error instead of trapping on a panic.
|
@greptile review |
franklevasseur
left a comment
There was a problem hiding this comment.
My brodeur 🔥
For all other Rust WASM packages that we maintain, we use wasm-pack. I'm not sure if it's worth it, but just so you know, this is the one we usually use.
Also, you should add an index.code-workspace like other packages in the repo so we can open it individually as a VS Code workspace.
{
"folders": [
{
"path": "."
}
],
"settings": {
"typescript.tsdk": "node_modules/typescript/lib"
}
}…pattern Align gravity with the other Rust packages in this repo (entities, verel): - Move the crate to rssrc/ with Cargo.toml at the package root. - Replace the hand-written extern "C" boundary with a #[wasm_bindgen] cluster() entrypoint using serde-wasm-bindgen for structured I/O. - Build with wasm-pack (node + web targets) via build.ts; the wasm is base64-inlined into the esbuild bundles, so no loose .wasm ships. - Emit dist/node (CJS), dist/web (ESM), and dist/types; add browser lazy-init via browser-or-node. The cluster(dataset, options) API is unchanged. - group_labels now uses a BTreeMap so cluster ordering is deterministic across calls (the persistent wasm instance exposed HashMap iteration order, which is not stable).
Adds
@bpinternal/gravity— generic density-based clustering (UMAP + HDBSCAN) for high-dimensional embeddings.The engine is Rust, compiled to WebAssembly with
wasm-pack+wasm-bindgenand wrapped in a small TypeScript API (same shape asentities/verel). No native deps, no Python.dist/node(CJS),dist/web(ESM),dist/types; the wasm is inlined, so there's no loose.wasm.pnpm testruns the Rust + TS suites. CODEOWNERS:@botpress/orca.