@adhd/sox-embedding-provider 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/dist/cache.d.ts +70 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +217 -0
- package/dist/cache.js.map +1 -0
- package/dist/embedWorker.d.ts +77 -0
- package/dist/embedWorker.d.ts.map +1 -0
- package/dist/embedWorker.js +326 -0
- package/dist/embedWorker.js.map +1 -0
- package/dist/fastembed.d.ts +62 -0
- package/dist/fastembed.d.ts.map +1 -0
- package/dist/fastembed.js +259 -0
- package/dist/fastembed.js.map +1 -0
- package/dist/fastembedProcessHost.d.ts +60 -0
- package/dist/fastembedProcessHost.d.ts.map +1 -0
- package/dist/fastembedProcessHost.js +166 -0
- package/dist/fastembedProcessHost.js.map +1 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +52 -0
- package/dist/remote.d.ts +23 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +91 -0
- package/dist/remote.js.map +1 -0
- package/dist/sharedFastembedProcess.d.ts +56 -0
- package/dist/sharedFastembedProcess.d.ts.map +1 -0
- package/dist/sharedFastembedProcess.js +180 -0
- package/dist/sharedFastembedProcess.js.map +1 -0
- package/dist/sharedOnnxWorker.d.ts +135 -0
- package/dist/sharedOnnxWorker.d.ts.map +1 -0
- package/dist/sharedOnnxWorker.js +255 -0
- package/dist/sharedOnnxWorker.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @adhd/sox-embedding-provider
|
|
2
|
+
|
|
3
|
+
Pluggable text→vector embedding provider — generic EmbeddingProvider interface, config-driven model resolution, async batch-first API (AsyncIterable), symmetric + asymmetric encoding via role param. Default: fastembed (local ONNX, >=3 model dims proven). Loud-fail: createEmbeddingProvider() throws ResolutionError if config is invalid or model cannot load — no silent hash downgrade.
|
|
4
|
+
|
|
5
|
+
- **area:** data · **group:** embed · **publish:** PUBLIC (`private: false`, publish owner-gated)
|
|
6
|
+
- **engines:** Node >=22
|
|
7
|
+
- **concerns:** text→vector (EmbeddingProvider interface), config-driven model resolution (createEmbeddingProvider factory), async batch embed (AsyncIterable<Float32Array>), asymmetric encoding via role param (document | query), warmUp cache for hot/topic texts, loud-fail ResolutionError at factory time (never mid-call), three-tier error taxonomy (Transient / Permanent / Resolution), deterministic hash provider as first-class alternative
|
|
8
|
+
|
|
9
|
+
## Invariants
|
|
10
|
+
|
|
11
|
+
- createEmbeddingProvider() THROWS ResolutionError synchronously or as a rejection if the config is invalid or the model/runtime cannot load — never silently downgrades to hash
|
|
12
|
+
- every provider advertises { modelId, dimensions, isRemote, isDeterministic, providerUri? } via metadata — callers never hardcode dims
|
|
13
|
+
- embedBatch() returns AsyncIterable<Float32Array> — callers receive first result before last batch finishes (critical for sequential local inference)
|
|
14
|
+
- warmUp() is a no-op when isDeterministic === false
|
|
15
|
+
- TransientEmbeddingError → caller may retry; PermanentEmbeddingError → caller must not retry; ResolutionError → factory-time only, never thrown mid-call
|
|
16
|
+
|
|
17
|
+
## Interface spec
|
|
18
|
+
|
|
19
|
+
See [COMPILED_INTERFACES.md](../../../../docs/plan/memory-refactor/COMPILED_INTERFACES.md) for the authoritative interface contract. `src/index.ts` is a compileable ambient-declaration skeleton;
|
|
20
|
+
implementation is extracted from `libs/memory-core` / `libs/memory-enrich` by the memory-refactor plan.
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ModelCache } from './index.js';
|
|
2
|
+
export declare class EmbeddingCache {
|
|
3
|
+
private cache;
|
|
4
|
+
private maxSize;
|
|
5
|
+
constructor(maxSize?: number);
|
|
6
|
+
get(text: string): Float32Array | undefined;
|
|
7
|
+
set(text: string, vec: Float32Array): void;
|
|
8
|
+
has(text: string): boolean;
|
|
9
|
+
clear(): void;
|
|
10
|
+
get size(): number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* FileSystemModelCache — downloads, caches, and verifies ONNX model binaries.
|
|
14
|
+
*
|
|
15
|
+
* Models are stored at:
|
|
16
|
+
* <baseDir>/<modelId>/main/model.onnx
|
|
17
|
+
* <baseDir>/<modelId>/main/model.onnx.sha256
|
|
18
|
+
*
|
|
19
|
+
* SHA-256 verification runs after every download. Throws ResolutionError on mismatch
|
|
20
|
+
* or download failure.
|
|
21
|
+
*
|
|
22
|
+
* @implements {ModelCache}
|
|
23
|
+
*/
|
|
24
|
+
export declare class FileSystemModelCache implements ModelCache {
|
|
25
|
+
private baseDir;
|
|
26
|
+
constructor(baseDir: string);
|
|
27
|
+
/**
|
|
28
|
+
* Download and verify model binary. Returns once the model is ready.
|
|
29
|
+
* Throws ResolutionError if the model is unknown, download fails, or
|
|
30
|
+
* SHA-256 verification fails.
|
|
31
|
+
*/
|
|
32
|
+
ensure(modelId: string): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Check whether the model binary is already in local cache and its
|
|
35
|
+
* SHA-256 sidecar matches the on-disk binary.
|
|
36
|
+
*/
|
|
37
|
+
cached(modelId: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Remove a single model from cache. Does not affect other models.
|
|
40
|
+
* No-op if the model is not cached.
|
|
41
|
+
*/
|
|
42
|
+
clear(modelId: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Streaming download with byte-level progress.
|
|
45
|
+
* Yields { bytesDownloaded, totalBytes } as chunks arrive.
|
|
46
|
+
* If already cached, yields the full size immediately and returns.
|
|
47
|
+
*/
|
|
48
|
+
ensureStream(modelId: string): AsyncIterable<{
|
|
49
|
+
bytesDownloaded: number;
|
|
50
|
+
totalBytes: number;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Directory for a specific model version.
|
|
54
|
+
* Pattern: <baseDir>/<modelId>/main/
|
|
55
|
+
*/
|
|
56
|
+
private modelDir;
|
|
57
|
+
/**
|
|
58
|
+
* Path to the ONNX model binary.
|
|
59
|
+
*/
|
|
60
|
+
private onnxPath;
|
|
61
|
+
/**
|
|
62
|
+
* Path to the SHA-256 sidecar file.
|
|
63
|
+
*/
|
|
64
|
+
private shaPath;
|
|
65
|
+
/**
|
|
66
|
+
* Build the HuggingFace download URL for an ONNX model.
|
|
67
|
+
*/
|
|
68
|
+
private buildUrl;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAwB,UAAU,EAAE,MAAM,YAAY,CAAC;AAEnE,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,SAAQ;IAI3B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAS3C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI;IAU1C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAiBD;;;;;;;;;;;GAWG;AACH,qBAAa,oBAAqB,YAAW,UAAU;IACzC,OAAO,CAAC,OAAO;gBAAP,OAAO,EAAE,MAAM;IAInC;;;;OAIG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6C5C;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAchC;;;OAGG;IACG,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO3C;;;;OAIG;IACI,YAAY,CACjB,OAAO,EAAE,MAAM,GACd,aAAa,CAAC;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAuDjE;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAIhB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAIhB;;OAEG;IACH,OAAO,CAAC,OAAO;IAIf;;OAEG;IACH,OAAO,CAAC,QAAQ;CAGjB"}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createWriteStream, existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { mkdir, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { ResolutionError } from './index.js';
|
|
6
|
+
export class EmbeddingCache {
|
|
7
|
+
cache = new Map();
|
|
8
|
+
maxSize;
|
|
9
|
+
constructor(maxSize = 10000) {
|
|
10
|
+
this.maxSize = maxSize;
|
|
11
|
+
}
|
|
12
|
+
get(text) {
|
|
13
|
+
const vec = this.cache.get(text);
|
|
14
|
+
if (vec !== undefined) {
|
|
15
|
+
this.cache.delete(text);
|
|
16
|
+
this.cache.set(text, vec);
|
|
17
|
+
}
|
|
18
|
+
return vec;
|
|
19
|
+
}
|
|
20
|
+
set(text, vec) {
|
|
21
|
+
if (this.cache.has(text)) {
|
|
22
|
+
this.cache.delete(text);
|
|
23
|
+
}
|
|
24
|
+
else if (this.cache.size >= this.maxSize) {
|
|
25
|
+
const firstKey = this.cache.keys().next().value;
|
|
26
|
+
if (firstKey !== undefined)
|
|
27
|
+
this.cache.delete(firstKey);
|
|
28
|
+
}
|
|
29
|
+
this.cache.set(text, vec);
|
|
30
|
+
}
|
|
31
|
+
has(text) {
|
|
32
|
+
return this.cache.has(text);
|
|
33
|
+
}
|
|
34
|
+
clear() {
|
|
35
|
+
this.cache.clear();
|
|
36
|
+
}
|
|
37
|
+
get size() {
|
|
38
|
+
return this.cache.size;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Lazily resolve MODEL_CONFIGS from fastembed.js to avoid forcing eager
|
|
43
|
+
* evaluation of fastembed's top-level import.meta.url in CJS bundles.
|
|
44
|
+
*/
|
|
45
|
+
async function getModelConfig(modelId) {
|
|
46
|
+
const { MODEL_CONFIGS } = await import('./fastembed.js');
|
|
47
|
+
const cfg = MODEL_CONFIGS[modelId];
|
|
48
|
+
if (!cfg) {
|
|
49
|
+
throw new ResolutionError(`Unknown model: "${modelId}". Supported: ${Object.keys(MODEL_CONFIGS).join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
return cfg;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* FileSystemModelCache — downloads, caches, and verifies ONNX model binaries.
|
|
55
|
+
*
|
|
56
|
+
* Models are stored at:
|
|
57
|
+
* <baseDir>/<modelId>/main/model.onnx
|
|
58
|
+
* <baseDir>/<modelId>/main/model.onnx.sha256
|
|
59
|
+
*
|
|
60
|
+
* SHA-256 verification runs after every download. Throws ResolutionError on mismatch
|
|
61
|
+
* or download failure.
|
|
62
|
+
*
|
|
63
|
+
* @implements {ModelCache}
|
|
64
|
+
*/
|
|
65
|
+
export class FileSystemModelCache {
|
|
66
|
+
baseDir;
|
|
67
|
+
constructor(baseDir) {
|
|
68
|
+
this.baseDir = baseDir;
|
|
69
|
+
}
|
|
70
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
71
|
+
/**
|
|
72
|
+
* Download and verify model binary. Returns once the model is ready.
|
|
73
|
+
* Throws ResolutionError if the model is unknown, download fails, or
|
|
74
|
+
* SHA-256 verification fails.
|
|
75
|
+
*/
|
|
76
|
+
async ensure(modelId) {
|
|
77
|
+
if (this.cached(modelId))
|
|
78
|
+
return;
|
|
79
|
+
const config = await getModelConfig(modelId);
|
|
80
|
+
await mkdir(this.modelDir(modelId), { recursive: true });
|
|
81
|
+
const url = this.buildUrl(config.hfRepoId);
|
|
82
|
+
const response = await fetch(url);
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw new ResolutionError(`Failed to download model "${modelId}": HTTP ${response.status} ${response.statusText} (${url})`);
|
|
85
|
+
}
|
|
86
|
+
const hash = createHash('sha256');
|
|
87
|
+
const writeStream = createWriteStream(this.onnxPath(modelId));
|
|
88
|
+
try {
|
|
89
|
+
const reader = response.body.getReader();
|
|
90
|
+
while (true) {
|
|
91
|
+
const { done, value } = await reader.read();
|
|
92
|
+
if (done)
|
|
93
|
+
break;
|
|
94
|
+
hash.update(value);
|
|
95
|
+
writeStream.write(value);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
writeStream.close();
|
|
100
|
+
await rm(this.modelDir(modelId), { recursive: true, force: true }).catch(() => { });
|
|
101
|
+
throw new ResolutionError(`Download failed for model "${modelId}": ${err instanceof Error ? err.message : String(err)}`);
|
|
102
|
+
}
|
|
103
|
+
await new Promise((resolve, reject) => {
|
|
104
|
+
writeStream.on('finish', resolve);
|
|
105
|
+
writeStream.on('error', (e) => {
|
|
106
|
+
rm(this.modelDir(modelId), { recursive: true, force: true }).catch(() => { });
|
|
107
|
+
reject(new ResolutionError(`Write failed for model "${modelId}": ${e.message}`));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
const digest = hash.digest('hex');
|
|
111
|
+
await writeFile(this.shaPath(modelId), digest + '\n');
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check whether the model binary is already in local cache and its
|
|
115
|
+
* SHA-256 sidecar matches the on-disk binary.
|
|
116
|
+
*/
|
|
117
|
+
cached(modelId) {
|
|
118
|
+
const onnx = this.onnxPath(modelId);
|
|
119
|
+
const sha = this.shaPath(modelId);
|
|
120
|
+
if (!existsSync(onnx) || !existsSync(sha))
|
|
121
|
+
return false;
|
|
122
|
+
try {
|
|
123
|
+
const expected = readFileSync(sha, 'utf-8').trim();
|
|
124
|
+
const actual = createHash('sha256').update(readFileSync(onnx)).digest('hex');
|
|
125
|
+
return expected === actual;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Remove a single model from cache. Does not affect other models.
|
|
133
|
+
* No-op if the model is not cached.
|
|
134
|
+
*/
|
|
135
|
+
async clear(modelId) {
|
|
136
|
+
const dir = this.modelDir(modelId);
|
|
137
|
+
if (existsSync(dir)) {
|
|
138
|
+
await rm(dir, { recursive: true, force: true });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Streaming download with byte-level progress.
|
|
143
|
+
* Yields { bytesDownloaded, totalBytes } as chunks arrive.
|
|
144
|
+
* If already cached, yields the full size immediately and returns.
|
|
145
|
+
*/
|
|
146
|
+
async *ensureStream(modelId) {
|
|
147
|
+
if (this.cached(modelId)) {
|
|
148
|
+
const stats = await stat(this.onnxPath(modelId));
|
|
149
|
+
yield { bytesDownloaded: stats.size, totalBytes: stats.size };
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const config = await getModelConfig(modelId);
|
|
153
|
+
await mkdir(this.modelDir(modelId), { recursive: true });
|
|
154
|
+
const url = this.buildUrl(config.hfRepoId);
|
|
155
|
+
const response = await fetch(url);
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
throw new ResolutionError(`Failed to download model "${modelId}": HTTP ${response.status} ${response.statusText} (${url})`);
|
|
158
|
+
}
|
|
159
|
+
const totalBytes = Number(response.headers.get('content-length') ?? 0);
|
|
160
|
+
const reader = response.body.getReader();
|
|
161
|
+
const writeStream = createWriteStream(this.onnxPath(modelId));
|
|
162
|
+
const hash = createHash('sha256');
|
|
163
|
+
let bytesDownloaded = 0;
|
|
164
|
+
try {
|
|
165
|
+
while (true) {
|
|
166
|
+
const { done, value } = await reader.read();
|
|
167
|
+
if (done)
|
|
168
|
+
break;
|
|
169
|
+
bytesDownloaded += value.length;
|
|
170
|
+
hash.update(value);
|
|
171
|
+
writeStream.write(value);
|
|
172
|
+
yield { bytesDownloaded, totalBytes };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
writeStream.close();
|
|
177
|
+
await rm(this.modelDir(modelId), { recursive: true, force: true }).catch(() => { });
|
|
178
|
+
throw new ResolutionError(`Stream download failed for model "${modelId}": ${err instanceof Error ? err.message : String(err)}`);
|
|
179
|
+
}
|
|
180
|
+
await new Promise((resolve, reject) => {
|
|
181
|
+
writeStream.on('finish', resolve);
|
|
182
|
+
writeStream.on('error', (e) => {
|
|
183
|
+
rm(this.modelDir(modelId), { recursive: true, force: true }).catch(() => { });
|
|
184
|
+
reject(new ResolutionError(`Write failed for model "${modelId}": ${e.message}`));
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
const digest = hash.digest('hex');
|
|
188
|
+
await writeFile(this.shaPath(modelId), digest + '\n');
|
|
189
|
+
}
|
|
190
|
+
// ── Private helpers ─────────────────────────────────────────────────────────
|
|
191
|
+
/**
|
|
192
|
+
* Directory for a specific model version.
|
|
193
|
+
* Pattern: <baseDir>/<modelId>/main/
|
|
194
|
+
*/
|
|
195
|
+
modelDir(modelId) {
|
|
196
|
+
return join(this.baseDir, modelId, 'main');
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Path to the ONNX model binary.
|
|
200
|
+
*/
|
|
201
|
+
onnxPath(modelId) {
|
|
202
|
+
return join(this.modelDir(modelId), 'model.onnx');
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Path to the SHA-256 sidecar file.
|
|
206
|
+
*/
|
|
207
|
+
shaPath(modelId) {
|
|
208
|
+
return join(this.modelDir(modelId), 'model.onnx.sha256');
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Build the HuggingFace download URL for an ONNX model.
|
|
212
|
+
*/
|
|
213
|
+
buildUrl(hfRepoId) {
|
|
214
|
+
return `https://huggingface.co/${hfRepoId}/resolve/main/onnx/model.onnx`;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared worker thread for @huggingface/transformers-based ONNX inference —
|
|
3
|
+
* cross-encoder rerank and NLI verify.
|
|
4
|
+
*
|
|
5
|
+
* Runs in isolation from the main thread (BL-11 boundary) — onnxruntime-node's
|
|
6
|
+
* thread pool never shares a thread context with better-sqlite3 + sqlite-vec.
|
|
7
|
+
*
|
|
8
|
+
* Supports two operation types:
|
|
9
|
+
* 1. Cross-encoder rerank — 'init' (type: 'rerank'), 'rerank', 'rerankBatch'
|
|
10
|
+
* 2. NLI verification — 'init' (type: 'verify'), 'verify'
|
|
11
|
+
*
|
|
12
|
+
* Protocol:
|
|
13
|
+
* request: { id, type: 'init', type: 'rerank', modelId: string }
|
|
14
|
+
* request: { id, type: 'init', type: 'verify', modelId: string, modelVersion: string }
|
|
15
|
+
* request: { id, type: 'rerank', query: string, candidates: Array<{id, text}> }
|
|
16
|
+
* request: { id, type: 'rerankBatch', queries: string[], candidateSets: ... }
|
|
17
|
+
* request: { id, type: 'verify', jobId: string, claimText: string, sourceText: string }
|
|
18
|
+
* response: { id, initOk: true, dim }
|
|
19
|
+
* response: { id, scores: number[] }
|
|
20
|
+
* response: { id, allScores: number[][] }
|
|
21
|
+
* response: { id, result: { entailment, confidence, ... } }
|
|
22
|
+
* response: { id, error: string }
|
|
23
|
+
* internal: { __shutdown: true }
|
|
24
|
+
*
|
|
25
|
+
* ── BL-238/BL-171 ── This worker is the ONE place cross-encoder rerank and
|
|
26
|
+
* NLI verify (both `@huggingface/transformers`, onnxruntime-node@1.24.3) run,
|
|
27
|
+
* loaded into exactly ONE process-wide `worker_threads.Worker` (constructed
|
|
28
|
+
* exclusively by `sharedOnnxWorker.ts`'s `getSharedOnnxWorker()` singleton —
|
|
29
|
+
* never directly by `@adhd/sox-hybrid-search`'s cross-encoder or
|
|
30
|
+
* `@adhd/sox-claim-verification`'s worker proxy).
|
|
31
|
+
*
|
|
32
|
+
* Root cause #1 (cross-isolate, whole-process fatal — the reason there must
|
|
33
|
+
* be only ONE onnxruntime-bearing `worker_threads.Worker`, proven via a
|
|
34
|
+
* from-scratch minimal repro, no test harness, no mocks): onnxruntime-node's
|
|
35
|
+
* native N-API addon fatally crashes the ENTIRE process — not just the
|
|
36
|
+
* offending worker — with
|
|
37
|
+
*
|
|
38
|
+
* FATAL ERROR: HandleScope::HandleScope Entering the V8 API without
|
|
39
|
+
* proper locking in place
|
|
40
|
+
* ... Napi::FunctionReference::New(...)
|
|
41
|
+
* ... OrtValueToNapiValue(Napi::Env, Ort::Value&&)
|
|
42
|
+
* ... InferenceSessionWrap::Run(...)
|
|
43
|
+
*
|
|
44
|
+
* whenever 2+ *separate* `worker_threads.Worker` instances (i.e. 2+ separate
|
|
45
|
+
* V8 isolates) each hold an active onnxruntime-node `InferenceSession` and
|
|
46
|
+
* run inference concurrently — reproduced even with TWO workers using the
|
|
47
|
+
* exact SAME onnxruntime-node version, so this is a genuine thread-safety
|
|
48
|
+
* limitation of the addon itself, not an ABI/version-mismatch issue (see root
|
|
49
|
+
* `BACKLOG.md` BL-238 for the full repro matrix).
|
|
50
|
+
*
|
|
51
|
+
* fastembed (onnxruntime-node@1.21.0) is DELIBERATELY NOT hosted in this
|
|
52
|
+
* worker, for a SECOND, independent reason (root cause #2): even a single
|
|
53
|
+
* shared worker hosting BOTH onnxruntime-node@1.21.0 (fastembed) AND
|
|
54
|
+
* onnxruntime-node@1.24.3 (transformers) — loaded strictly sequentially, with
|
|
55
|
+
* every JS `await` fully resolved before the next `init` begins (proven via
|
|
56
|
+
* instrumented tracing showing zero JS-level overlap between the two
|
|
57
|
+
* `init`s) — still deterministically threw `std::bad_alloc` the moment
|
|
58
|
+
* fastembed initialised second. That means the two onnxruntime-node major
|
|
59
|
+
* versions leave lingering native state (e.g. background native thread-pool
|
|
60
|
+
* teardown) not synchronized by the JS Promise resolving — a hazard below
|
|
61
|
+
* what JS-level scheduling/serialization can observe or prevent. Only a real
|
|
62
|
+
* OS process boundary is proven safe for fastembed; see
|
|
63
|
+
* `fastembedProcessHost.ts` / `sharedFastembedProcess.ts` for where fastembed
|
|
64
|
+
* actually runs (its own dedicated child PROCESS, never a
|
|
65
|
+
* `worker_threads.Worker`, never sharing an address space with this worker).
|
|
66
|
+
*
|
|
67
|
+
* Fix: every rerank/verify consumer routes through
|
|
68
|
+
* `getSharedOnnxWorker().request(...)` instead of constructing its own
|
|
69
|
+
* `Worker`; every fastembed consumer routes through
|
|
70
|
+
* `getSharedFastembedProcess().request(...)` instead of constructing its own
|
|
71
|
+
* `Worker`/process. There is never a second onnxruntime-bearing WORKER THREAD
|
|
72
|
+
* alive in the process, and fastembed never shares a thread (or process) with
|
|
73
|
+
* this worker at all — both crash classes above are structurally impossible,
|
|
74
|
+
* not merely statistically less likely.
|
|
75
|
+
*/
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=embedWorker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedWorker.d.ts","sourceRoot":"","sources":["../src/embedWorker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG"}
|