forked from ChelseaKR/sprout
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhashEmbedding.ts
More file actions
51 lines (47 loc) · 1.6 KB
/
Copy pathhashEmbedding.ts
File metadata and controls
51 lines (47 loc) · 1.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Signed token-hashing bag-of-tokens embedder — a mirror of `providers/deterministic.py`'s
* `HashingEmbedding`.
*
* Each content token is hashed with SHA-256 (`sha256.ts`); the first 4 bytes pick a
* dimension and the next byte's low bit picks a sign, exactly as the Python side does.
* The same text always yields a byte-identical vector in both implementations, which is
* exactly what the conformance test (`test/conformance.test.ts`) checks transitively
* through retrieval and the final answer.
*/
import { sha256 } from "./sha256.js";
import { contentTokens } from "./text.js";
export class HashingEmbedding {
readonly dim: number;
constructor(dim = 512) {
if (dim <= 0) {
throw new Error("embedding dim must be positive");
}
this.dim = dim;
}
embed(text: string): number[] {
const vec = new Array<number>(this.dim).fill(0.0);
for (const tok of contentTokens(text)) {
const digest = sha256(tok);
// Big-endian uint32 of the first 4 bytes, mod `dim` — mirrors
// `int.from_bytes(digest[:4], "big") % self._dim`.
const idx =
(((digest[0] as number) << 24) |
((digest[1] as number) << 16) |
((digest[2] as number) << 8) |
(digest[3] as number)) >>>
0;
const dimIdx = idx % this.dim;
const sign = ((digest[4] as number) & 1) === 1 ? 1.0 : -1.0;
vec[dimIdx] = (vec[dimIdx] as number) + sign;
}
let norm = 0.0;
for (const v of vec) {
norm += v * v;
}
norm = Math.sqrt(norm);
if (norm === 0.0) {
return vec;
}
return vec.map((v) => v / norm);
}
}