Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/state/vector-index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
function float32ToBase64(arr: Float32Array): string {
return Buffer.from(arr.buffer).toString("base64");
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString("base64");
}

function base64ToFloat32(b64: string): Float32Array {
return new Float32Array(Buffer.from(b64, "base64").buffer);
const buf = Buffer.from(b64, "base64");
return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
}

function cosineSimilarity(a: Float32Array, b: Float32Array): number {
Expand Down
26 changes: 26 additions & 0 deletions test/vector-index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,30 @@ describe("VectorIndex", () => {
const results = index.search(new Float32Array([1, 0, 0]));
expect(results[0].score).toBe(0);
});

it("serialize round-trip preserves dimension and values for pooled-buffer sizes", () => {
// 384 floats = 1536 bytes, small enough for Node to pool the decoded Buffer.
// Without the byteOffset/byteLength fix, deserialized length becomes 2048
// and values read from pool offset 0 instead of the slice.
const DIM = 384;
const vecs = Array.from({ length: 5 }, (_, n) => {
const v = new Float32Array(DIM);
for (let i = 0; i < DIM; i++) v[i] = n * 1000 + i;
return v;
});

vecs.forEach((v, n) => index.add(`obs_${n}`, "ses_1", v));
const restored = VectorIndex.deserialize(index.serialize());

expect(restored.size).toBe(5);
// Dimension must be exactly DIM, not 2048 (Buffer.poolSize / 4).
const { mismatches } = restored.validateDimensions(DIM);
expect(mismatches).toEqual([]);
// Each vector must be its own nearest neighbour after reload.
for (let n = 0; n < 5; n++) {
const results = restored.search(vecs[n], 1);
expect(results[0].obsId).toBe(`obs_${n}`);
expect(results[0].score).toBeCloseTo(1.0, 4);
}
});
});