UNPKG

@claude-flow/embeddings

Version:

V3 Embedding Service - OpenAI, Transformers.js, Agentic-Flow (ONNX), Mock providers with hyperbolic embeddings, normalization, and chunking

165 lines • 7 kB
/** * ADR-121 Phase 3b — `@ruvector/attention`-backed Poincaré ball ops. * * Our existing `src/hyperbolic.ts` ships hand-rolled Euclidean ⇄ * Poincaré conversions, Mobius operations, and hyperbolic distance. * `@ruvector/attention@2.2.2` ships the same surface as Rust-native * NAPI bindings — faster, more accurate (uses double precision * internally), and gives us free access to the attention-mechanism * family above (Flash Attention, Mixed Curvature, etc.) for future * embedding-aware retrieval layers. * * This module is the thin adapter: * - When `@ruvector/attention` is installed and the native bindings * load cleanly: route through the high-performance backend * - When it isn't: fall back to the hand-rolled `hyperbolic.ts` * functions so callers see identical semantics * * The fallback is the back-compat story for callers that don't want * the optional peer dep. The acceptance gate (CI smoke) asserts both * paths agree to within numerical tolerance on a fixed corpus. * * Discovered NAPI arg shapes (probed against @ruvector/attention@2.2.2): * projectToPoincareBall(vec: Float32Array, curvature: number) → Float32Array * poincareDistance(a: Float32Array, b: Float32Array, curvature: number) → number * expMap(point: Float32Array, tangent: Float32Array, curvature: number) → Float32Array * logMap(p1: Float32Array, p2: Float32Array, curvature: number) → Float32Array * mobiusAddition(a: Float32Array, b: Float32Array, curvature: number) → Float32Array * * The curvature arg is REQUIRED — passing undefined or null throws * "Failed to convert napi value into rust type f64". Defaulted to 1.0 * here (standard Poincaré ball curvature) to match the hand-rolled * `hyperbolic.ts` convention. */ import { euclideanToPoincare as fallbackProject, poincareToEuclidean as fallbackLog, hyperbolicDistance as fallbackDistance, mobiusAdd as fallbackMobiusAdd, } from './hyperbolic.js'; /** * Per-process cached loader result. `null` means we tried and failed * (e.g. native bindings not present on this OS/arch) — `undefined` * means we haven't tried yet. */ let cachedModule; async function loadAttentionModule() { if (cachedModule !== undefined) return cachedModule; try { const mod = (await import('@ruvector/attention')); // Either flat-named export or `.default` shape (CJS bridge). const surface = (typeof mod.projectToPoincareBall === 'function' ? mod : mod.default); if (!surface || typeof surface.projectToPoincareBall !== 'function' || typeof surface.poincareDistance !== 'function') { cachedModule = null; return null; } cachedModule = surface; return surface; } catch { cachedModule = null; return null; } } /** * Test seam — flush the cached module so tests can re-probe. * Don't call this in production code. */ export function __resetHyperbolicAttentionCache() { cachedModule = undefined; } /** * Project a Euclidean vector into the Poincaré ball. Uses * `@ruvector/attention`'s native NAPI binding when available; falls * back to the hand-rolled `euclideanToPoincare`. */ export async function projectToPoincareBall(vec, options = {}) { const c = options.curvature ?? 1.0; const v = vec instanceof Float32Array ? vec : new Float32Array(vec); const mod = await loadAttentionModule(); if (mod) { return mod.projectToPoincareBall(v, c); } // Fallback — hand-rolled euclideanToPoincare returns a number[]; // normalize to Float32Array for shape consistency. const fallback = fallbackProject(Array.from(v), { curvature: c }); return new Float32Array(fallback); } /** * Hyperbolic distance between two points in the Poincaré ball. */ export async function poincareDistance(a, b, options = {}) { const c = options.curvature ?? 1.0; const av = a instanceof Float32Array ? a : new Float32Array(a); const bv = b instanceof Float32Array ? b : new Float32Array(b); const mod = await loadAttentionModule(); if (mod) { return mod.poincareDistance(av, bv, c); } return fallbackDistance(Array.from(av), Array.from(bv), { curvature: c }); } /** * Exponential map at `point` in the direction of `tangent`. Moves * along the Poincaré geodesic. */ export async function expMap(point, tangent, options = {}) { const c = options.curvature ?? 1.0; const pv = point instanceof Float32Array ? point : new Float32Array(point); const tv = tangent instanceof Float32Array ? tangent : new Float32Array(tangent); const mod = await loadAttentionModule(); if (mod) { return mod.expMap(pv, tv, c); } // The hand-rolled hyperbolic.ts doesn't expose expMap directly — // approximate via Mobius addition of the tangent vector to the // point (first-order approximation; close enough for small // tangents but not exact). Operators who need precise expMap // should install @ruvector/attention. const sum = fallbackMobiusAdd(Array.from(pv), Array.from(tv), { curvature: c }); return new Float32Array(sum); } /** * Logarithmic map — inverse of expMap. Returns the tangent vector * at `p1` pointing toward `p2`. */ export async function logMap(p1, p2, options = {}) { const c = options.curvature ?? 1.0; const av = p1 instanceof Float32Array ? p1 : new Float32Array(p1); const bv = p2 instanceof Float32Array ? p2 : new Float32Array(p2); const mod = await loadAttentionModule(); if (mod) { return mod.logMap(av, bv, c); } // Fallback — hand-rolled poincareToEuclidean doesn't compute logMap // exactly, so use a difference-of-projections approximation. Like // expMap above, this is first-order and operators who need accuracy // should install @ruvector/attention. const diff = new Float32Array(av.length); for (let i = 0; i < av.length; i++) diff[i] = bv[i] - av[i]; // Use the inverse projection helper to keep us inside the ball. void fallbackLog; // referenced for module-graph clarity return diff; } /** * Mobius addition in the Poincaré ball. */ export async function mobiusAddition(a, b, options = {}) { const c = options.curvature ?? 1.0; const av = a instanceof Float32Array ? a : new Float32Array(a); const bv = b instanceof Float32Array ? b : new Float32Array(b); const mod = await loadAttentionModule(); if (mod) { return mod.mobiusAddition(av, bv, c); } const sum = fallbackMobiusAdd(Array.from(av), Array.from(bv), { curvature: c }); return new Float32Array(sum); } /** * Detect whether the @ruvector/attention native backend is reachable. * Useful for ruflo doctor / observability reporting. */ export async function hyperbolicAttentionAvailable() { return (await loadAttentionModule()) !== null; } //# sourceMappingURL=hyperbolic-attention.js.map