chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import { describe, it, beforeEach } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { _injectPipeline } from "../../src/lib/memory/embedding/transformersLocal";
|
||||
|
||||
// Note: @huggingface/transformers is NEVER imported at module level in production code.
|
||||
// This test verifies the singleton pattern and error handling using injected mocks.
|
||||
|
||||
describe("memory-embedding-transformers", () => {
|
||||
beforeEach(() => {
|
||||
// Reset pipeline singleton
|
||||
_injectPipeline(null);
|
||||
});
|
||||
|
||||
it("_injectPipeline and embedTransformers use mock pipeline", async () => {
|
||||
// Inject a mock pipeline that returns a Tensor-like object
|
||||
let callCount = 0;
|
||||
const mockPipeline = async (_text: string | string[], _opts?: Record<string, unknown>) => {
|
||||
callCount++;
|
||||
// Return a Tensor-like object with dims [1, 1, 4] and data
|
||||
return {
|
||||
dims: [1, 1, 4],
|
||||
data: new Float32Array([0.1, 0.2, 0.3, 0.4]),
|
||||
};
|
||||
};
|
||||
|
||||
_injectPipeline(mockPipeline);
|
||||
|
||||
const { embedTransformers } = await import("../../src/lib/memory/embedding/transformersLocal");
|
||||
const result = await embedTransformers("hello world");
|
||||
|
||||
assert.ok("vector" in result, "Should return EmbeddingResult");
|
||||
const r = result as { vector: Float32Array; source: string; dimensions: number; cached: boolean };
|
||||
assert.ok(r.vector instanceof Float32Array);
|
||||
assert.strictEqual(r.source, "transformers");
|
||||
assert.strictEqual(r.dimensions, 4);
|
||||
assert.strictEqual(r.cached, false);
|
||||
assert.strictEqual(callCount, 1);
|
||||
});
|
||||
|
||||
it("singleton: second call reuses existing pipeline (no double init)", async () => {
|
||||
let initCount = 0;
|
||||
_injectPipeline(async () => {
|
||||
initCount++;
|
||||
return { dims: [1, 1, 4], data: new Float32Array([0.5, 0.6, 0.7, 0.8]) };
|
||||
});
|
||||
|
||||
const { embedTransformers } = await import("../../src/lib/memory/embedding/transformersLocal");
|
||||
await embedTransformers("first call");
|
||||
await embedTransformers("second call");
|
||||
|
||||
// Pipeline function was called twice (once per text), but init should
|
||||
// only happen once since _injectPipeline sets the singleton directly
|
||||
assert.strictEqual(initCount, 2, "pipeline function called twice but init (inject) happened once");
|
||||
});
|
||||
|
||||
it("returns EmbeddingError{reason:model_load_failed} when pipeline throws on load", async () => {
|
||||
// Clear the singleton so getOrLoadPipeline() tries to load
|
||||
_injectPipeline(null);
|
||||
|
||||
// Override dynamic import to fail
|
||||
// We do this by testing the error-handling code path directly
|
||||
// Since we can't easily mock dynamic imports in Node.js native test runner,
|
||||
// we verify the error structure is correct
|
||||
|
||||
// Simulate what happens when pipeline() rejects
|
||||
const errorSource = "transformers";
|
||||
const errorReason = "model_load_failed";
|
||||
const errMsg = "Network error loading model";
|
||||
|
||||
const { sanitizeErrorMessage } = await import("@omniroute/open-sse/utils/error.ts");
|
||||
const sanitized = sanitizeErrorMessage(errMsg);
|
||||
|
||||
const embErr = {
|
||||
source: errorSource,
|
||||
model: "Xenova/all-MiniLM-L6-v2",
|
||||
reason: errorReason,
|
||||
message: sanitized,
|
||||
};
|
||||
|
||||
assert.strictEqual(embErr.source, "transformers");
|
||||
assert.strictEqual(embErr.reason, "model_load_failed");
|
||||
assert.ok(typeof embErr.message === "string");
|
||||
assert.ok(!embErr.message.includes("at /"), "No stack trace in message");
|
||||
});
|
||||
|
||||
it("handles Tensor with 2D dims [seq_len, hidden_size]", async () => {
|
||||
_injectPipeline(async () => {
|
||||
return {
|
||||
dims: [2, 4], // [seq_len=2, hidden=4]
|
||||
data: new Float32Array([1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]),
|
||||
};
|
||||
});
|
||||
|
||||
const { embedTransformers } = await import("../../src/lib/memory/embedding/transformersLocal");
|
||||
const result = await embedTransformers("test");
|
||||
|
||||
assert.ok("vector" in result);
|
||||
const r = result as { vector: Float32Array; dimensions: number };
|
||||
assert.strictEqual(r.dimensions, 4);
|
||||
// Mean of rows [1,0,0,0] and [0,1,0,0] = [0.5, 0.5, 0, 0]
|
||||
assert.ok(Math.abs(r.vector[0] - 0.5) < 0.001);
|
||||
assert.ok(Math.abs(r.vector[1] - 0.5) < 0.001);
|
||||
});
|
||||
|
||||
it("handles 3D Tensor dims [batch=1, seq_len, hidden_size]", async () => {
|
||||
_injectPipeline(async () => {
|
||||
return {
|
||||
dims: [1, 2, 4], // [batch=1, seq_len=2, hidden=4]
|
||||
data: new Float32Array([2.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0]),
|
||||
};
|
||||
});
|
||||
|
||||
const { embedTransformers } = await import("../../src/lib/memory/embedding/transformersLocal");
|
||||
const result = await embedTransformers("test");
|
||||
|
||||
assert.ok("vector" in result);
|
||||
const r = result as { vector: Float32Array; dimensions: number };
|
||||
assert.strictEqual(r.dimensions, 4);
|
||||
assert.ok(Math.abs(r.vector[0] - 1.0) < 0.001);
|
||||
assert.ok(Math.abs(r.vector[1] - 1.0) < 0.001);
|
||||
});
|
||||
|
||||
it("pipeline error in embed() returns EmbeddingError{reason:request_failed}", async () => {
|
||||
_injectPipeline(async () => {
|
||||
throw new Error("Unexpected model output");
|
||||
});
|
||||
|
||||
const { embedTransformers } = await import("../../src/lib/memory/embedding/transformersLocal");
|
||||
const result = await embedTransformers("test");
|
||||
|
||||
assert.ok("reason" in result);
|
||||
const r = result as { reason: string; source: string; message: string };
|
||||
assert.strictEqual(r.source, "transformers");
|
||||
assert.ok(r.reason === "request_failed" || r.reason === "timeout");
|
||||
assert.ok(!r.message.includes("at /"), "No stack trace in sanitized message");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user