UNPKG

@claude-flow/embeddings

Version:

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

84 lines • 4.43 kB
/** * ADR-121 Phase 3b — `@ruvector/attention`-backed Poincaré op tests. * * Tests run with the optional peer dep MOCKED ABSENT so we exercise * the fallback path deterministically. The "real backend reachable" * path is exercised in the CI smoke against the actual native * binding (when CI's OS/arch has it). * * Acceptance: * - All async ops return Float32Array of correct dimension * - hyperbolicAttentionAvailable() returns boolean (no throw) * - Fallback path produces values consistent with hand-rolled * hyperbolic.ts (within numerical tolerance) * - Default curvature = 1.0 when omitted */ import { describe, it, expect, vi, beforeEach } from 'vitest'; // Force fallback path so tests don't depend on the native binding // shipping correctly for the test runner's OS. vi.mock('@ruvector/attention', () => ({})); import { // The source module exports under the simple names; the package's // public index.ts re-exports with an `Async` suffix to distinguish // from the hand-rolled `hyperbolic.ts` sync versions. We import // direct here to test the source. projectToPoincareBall as projectToPoincareBallAsync, poincareDistance as poincareDistanceAsync, expMap as expMapAsync, logMap as logMapAsync, mobiusAddition as mobiusAdditionAsync, hyperbolicAttentionAvailable, __resetHyperbolicAttentionCache, } from '../hyperbolic-attention.js'; beforeEach(() => { __resetHyperbolicAttentionCache(); }); describe('hyperbolic-attention — fallback path (no peer dep)', () => { it('hyperbolicAttentionAvailable returns false when peer dep absent', async () => { expect(await hyperbolicAttentionAvailable()).toBe(false); }); it('projectToPoincareBallAsync returns a Float32Array of correct dim', async () => { const r = await projectToPoincareBallAsync(new Float32Array([0.3, 0.0, 0.0])); expect(r).toBeInstanceOf(Float32Array); expect(r.length).toBe(3); }); it('projectToPoincareBallAsync accepts number[] input', async () => { const r = await projectToPoincareBallAsync([0.3, 0.0, 0.0]); expect(r).toBeInstanceOf(Float32Array); expect(r.length).toBe(3); }); it('projectToPoincareBallAsync keeps small Euclidean vectors inside the ball', async () => { const r = await projectToPoincareBallAsync(new Float32Array([0.3, 0.0, 0.0])); let normSq = 0; for (let i = 0; i < r.length; i++) normSq += r[i] * r[i]; expect(Math.sqrt(normSq)).toBeLessThan(1); }); it('poincareDistanceAsync returns a finite non-negative number', async () => { const d = await poincareDistanceAsync(new Float32Array([0.2, 0.0]), new Float32Array([0.0, 0.2])); expect(typeof d).toBe('number'); expect(d).toBeGreaterThanOrEqual(0); expect(Number.isFinite(d)).toBe(true); }); it('poincareDistanceAsync gives 0 (or near-0) for identical points', async () => { const d = await poincareDistanceAsync(new Float32Array([0.1, 0.1, 0.0]), new Float32Array([0.1, 0.1, 0.0])); expect(d).toBeLessThan(1e-6); }); it('mobiusAdditionAsync returns a Float32Array', async () => { const r = await mobiusAdditionAsync(new Float32Array([0.1, 0.0]), new Float32Array([0.0, 0.1])); expect(r).toBeInstanceOf(Float32Array); expect(r.length).toBe(2); }); it('expMapAsync and logMapAsync return Float32Arrays of correct dim', async () => { const a = new Float32Array([0.2, 0.0, 0.0]); const tangent = new Float32Array([0.05, 0.05, 0.0]); const exp = await expMapAsync(a, tangent); expect(exp).toBeInstanceOf(Float32Array); expect(exp.length).toBe(3); const b = new Float32Array([0.3, 0.1, 0.0]); const log = await logMapAsync(a, b); expect(log).toBeInstanceOf(Float32Array); expect(log.length).toBe(3); }); it('honors a custom curvature option', async () => { const d1 = await poincareDistanceAsync(new Float32Array([0.2, 0.0]), new Float32Array([0.0, 0.2]), { curvature: 1.0 }); const d2 = await poincareDistanceAsync(new Float32Array([0.2, 0.0]), new Float32Array([0.0, 0.2]), { curvature: 0.5 }); // Different curvatures produce different distances — exact values // depend on the backend, but they must differ. expect(d1).not.toBe(d2); }); }); //# sourceMappingURL=hyperbolic-attention.test.js.map