chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
import { describe, it, beforeEach } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
tokenizeWordPiece,
|
||||
meanPool,
|
||||
_injectModel,
|
||||
type PotionModel,
|
||||
} from "../../src/lib/memory/embedding/staticPotion";
|
||||
import type { EmbeddingError } from "../../src/lib/memory/embedding/types";
|
||||
import { invalidate as invalidateCache } from "../../src/lib/memory/embedding/cache";
|
||||
|
||||
// ---- Mock model setup ----
|
||||
// Vocab: {"[UNK]":0, "hello":1, "world":2}
|
||||
// Matrix: 3 rows × 4 dims
|
||||
// Row 0 ([UNK]): [0.0, 0.0, 0.0, 0.0]
|
||||
// Row 1 (hello): [1.0, 0.0, 0.0, 0.0]
|
||||
// Row 2 (world): [0.0, 1.0, 0.0, 0.0]
|
||||
|
||||
function makeMockModel(): PotionModel {
|
||||
const vocab: Record<string, number> = { "[UNK]": 0, "hello": 1, "world": 2 };
|
||||
const matrix = new Float32Array([
|
||||
0.0, 0.0, 0.0, 0.0, // row 0 = [UNK]
|
||||
1.0, 0.0, 0.0, 0.0, // row 1 = hello
|
||||
0.0, 1.0, 0.0, 0.0, // row 2 = world
|
||||
]);
|
||||
return { vocab, matrix, dim: 4, vocabSize: 3, unkIdx: 0 };
|
||||
}
|
||||
|
||||
describe("memory-embedding-static-potion tokenizer", () => {
|
||||
const mock = makeMockModel();
|
||||
|
||||
it("tokenizes known words to their vocab IDs", () => {
|
||||
const ids = tokenizeWordPiece("hello world", mock.vocab);
|
||||
assert.deepStrictEqual(ids, [1, 2]);
|
||||
});
|
||||
|
||||
it("unknown words fall back to [UNK] (id=0)", () => {
|
||||
const ids = tokenizeWordPiece("foo bar", mock.vocab);
|
||||
assert.deepStrictEqual(ids, [0, 0]);
|
||||
});
|
||||
|
||||
it("mixed known and unknown tokens", () => {
|
||||
const ids = tokenizeWordPiece("hello foo world", mock.vocab);
|
||||
assert.deepStrictEqual(ids, [1, 0, 2]);
|
||||
});
|
||||
|
||||
it("empty string returns no tokens", () => {
|
||||
const ids = tokenizeWordPiece("", mock.vocab);
|
||||
assert.deepStrictEqual(ids, []);
|
||||
});
|
||||
|
||||
it("case-insensitive tokenization", () => {
|
||||
// tokenizeWordPiece lowercases input
|
||||
const ids = tokenizeWordPiece("Hello World", mock.vocab);
|
||||
assert.deepStrictEqual(ids, [1, 2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("memory-embedding-static-potion mean pooling", () => {
|
||||
const mock = makeMockModel();
|
||||
|
||||
it("mean pools hello + world to [0.5, 0.5, 0, 0]", () => {
|
||||
const ids = [1, 2]; // hello, world
|
||||
const result = meanPool(ids, mock.matrix, mock.dim, mock.vocabSize, mock.unkIdx);
|
||||
assert.ok(result instanceof Float32Array);
|
||||
assert.strictEqual(result.length, 4);
|
||||
assert.ok(Math.abs(result[0] - 0.5) < 0.001, `dim0 should be 0.5, got ${result[0]}`);
|
||||
assert.ok(Math.abs(result[1] - 0.5) < 0.001, `dim1 should be 0.5, got ${result[1]}`);
|
||||
assert.ok(Math.abs(result[2] - 0.0) < 0.001, `dim2 should be 0, got ${result[2]}`);
|
||||
});
|
||||
|
||||
it("pooling [UNK] returns zero vector", () => {
|
||||
const ids = [0]; // [UNK]
|
||||
const result = meanPool(ids, mock.matrix, mock.dim, mock.vocabSize, mock.unkIdx);
|
||||
for (const v of result) {
|
||||
assert.ok(Math.abs(v) < 0.001, `All dims should be 0, got ${v}`);
|
||||
}
|
||||
});
|
||||
|
||||
it("empty token list returns zero vector", () => {
|
||||
const result = meanPool([], mock.matrix, mock.dim, mock.vocabSize, mock.unkIdx);
|
||||
for (const v of result) {
|
||||
assert.ok(Math.abs(v) < 0.001, `All dims should be 0, got ${v}`);
|
||||
}
|
||||
});
|
||||
|
||||
it("out-of-range token ID falls back to unkIdx", () => {
|
||||
const ids = [999]; // out of range
|
||||
const result = meanPool(ids, mock.matrix, mock.dim, mock.vocabSize, mock.unkIdx);
|
||||
// Should use row 0 ([UNK]) = all zeros
|
||||
for (const v of result) {
|
||||
assert.ok(Math.abs(v) < 0.001, `All dims should be 0 (unk), got ${v}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("memory-embedding-static-potion embedStatic with mock", () => {
|
||||
beforeEach(() => {
|
||||
invalidateCache();
|
||||
_injectModel(makeMockModel());
|
||||
});
|
||||
|
||||
it("embedStatic returns EmbeddingResult for 'hello world'", async () => {
|
||||
const { embedStatic } = await import("../../src/lib/memory/embedding/staticPotion");
|
||||
const result = await embedStatic("hello world");
|
||||
assert.ok("vector" in result, "Should return EmbeddingResult");
|
||||
assert.ok((result as { vector: Float32Array }).vector instanceof Float32Array);
|
||||
assert.strictEqual((result as { dimensions: number }).dimensions, 4);
|
||||
assert.strictEqual((result as { source: string }).source, "static");
|
||||
});
|
||||
|
||||
it("embedStatic uses [UNK] for 'foo' (not in mock vocab)", async () => {
|
||||
const { embedStatic } = await import("../../src/lib/memory/embedding/staticPotion");
|
||||
const result = await embedStatic("foo");
|
||||
assert.ok("vector" in result);
|
||||
const vec = (result as { vector: Float32Array }).vector;
|
||||
// foo -> [UNK] -> row 0 = [0, 0, 0, 0]
|
||||
for (const v of vec) {
|
||||
assert.ok(Math.abs(v) < 0.001, `Should be 0 for UNK, got ${v}`);
|
||||
}
|
||||
});
|
||||
|
||||
it("model load failure returns EmbeddingError with reason model_load_failed", async () => {
|
||||
// Plan 21 fix: previously this test was tautological (`assert.ok(true)`).
|
||||
// Force a real load-failure path: point the cache dir at /dev/null/<subdir>
|
||||
// so fs.mkdir() fails with ENOTDIR (/dev/null is a file, not a dir).
|
||||
// embedStatic catches the error and must return EmbeddingError with
|
||||
// reason="model_load_failed" (staticPotion.ts:225-232).
|
||||
_injectModel(null);
|
||||
const prevCacheDir = process.env.MEMORY_STATIC_CACHE_DIR;
|
||||
process.env.MEMORY_STATIC_CACHE_DIR = `/dev/null/potion-load-fail-${process.pid}-${Date.now()}`;
|
||||
try {
|
||||
const { embedStatic } = await import(
|
||||
"../../src/lib/memory/embedding/staticPotion"
|
||||
);
|
||||
const result = await embedStatic("hello world");
|
||||
assert.ok(
|
||||
!("vector" in result),
|
||||
`Expected EmbeddingError but got result with vector: ${JSON.stringify(result)}`
|
||||
);
|
||||
const err = result as EmbeddingError;
|
||||
assert.strictEqual(err.source, "static");
|
||||
assert.strictEqual(err.reason, "model_load_failed");
|
||||
assert.ok(
|
||||
typeof err.message === "string" && err.message.length > 0,
|
||||
"EmbeddingError.message must be a non-empty sanitized string"
|
||||
);
|
||||
} finally {
|
||||
if (prevCacheDir === undefined) {
|
||||
delete process.env.MEMORY_STATIC_CACHE_DIR;
|
||||
} else {
|
||||
process.env.MEMORY_STATIC_CACHE_DIR = prevCacheDir;
|
||||
}
|
||||
_injectModel(makeMockModel()); // restore for other tests
|
||||
}
|
||||
});
|
||||
|
||||
it("second call reuses singleton model (no re-load)", async () => {
|
||||
const { embedStatic } = await import("../../src/lib/memory/embedding/staticPotion");
|
||||
// First call
|
||||
const r1 = await embedStatic("hello");
|
||||
// Second call — should reuse singleton
|
||||
const r2 = await embedStatic("hello");
|
||||
assert.ok("vector" in r1);
|
||||
assert.ok("vector" in r2);
|
||||
// Both succeed with same model
|
||||
assert.strictEqual((r1 as { source: string }).source, "static");
|
||||
assert.strictEqual((r2 as { source: string }).source, "static");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user