@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
JavaScript
/**
* 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