@lotargo/memory_plugin 1.1.5 → 1.1.6

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.
@@ -1,30 +1,49 @@
1
+ import os from "node:os";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
1
4
  import { MODELS_DIR } from "../db/database.js";
2
5
 
3
6
  let extractorInstance = null;
4
7
  let loadedModelName = null;
8
+ let loadedDevice = null;
5
9
 
6
10
  let rerankerInstance = null;
7
11
  let loadedRerankerName = null;
8
12
 
9
13
  import { getConfig } from "../config/config_manager.js";
14
+ import { GpuMonitor, ExecutionTracer } from "./gpu_monitor.js";
15
+ export { GpuMonitor, ExecutionTracer };
16
+
17
+ function getOptimalThreadCount() {
18
+ const userSetting = getConfig().onnxThreads;
19
+ if (typeof userSetting === "number" && userSetting > 0) {
20
+ return userSetting;
21
+ }
22
+ const totalCores = os.availableParallelism ? os.availableParallelism() : os.cpus().length;
23
+ return Math.max(1, Math.min(totalCores, 8));
24
+ }
10
25
 
11
26
  export async function getExtractor(modelName = null, progressCallback = null) {
12
27
  const targetModel = modelName || getConfig().embeddingModel || "Xenova/multilingual-e5-small";
13
28
 
14
- if (extractorInstance && loadedModelName === targetModel) {
15
- return extractorInstance;
29
+ // Resolve target device BEFORE cache check so comparison works correctly
30
+ const rawDevice = (getConfig().executionDevice || "cpu").toLowerCase();
31
+ let targetDevice = "cpu";
32
+ if (rawDevice === "webgpu" || rawDevice === "gpu" || rawDevice === "dml" || rawDevice === "cuda") {
33
+ if (process.platform === "win32") {
34
+ targetDevice = "dml";
35
+ } else if (process.platform === "linux") {
36
+ targetDevice = "cuda";
37
+ } else {
38
+ targetDevice = "webgpu";
39
+ }
16
40
  }
17
41
 
18
- const { pipeline, env } = await import("@xenova/transformers");
42
+ if (extractorInstance && loadedModelName === targetModel && loadedDevice === targetDevice) {
43
+ return extractorInstance;
44
+ }
19
45
 
20
- // Fix ONNX Runtime Node.js WASM execution provider warning
21
- try {
22
- const { executionProviders } = await import("@xenova/transformers/src/backends/onnx.js");
23
- const wasmIdx = executionProviders.indexOf("wasm");
24
- if (wasmIdx !== -1) {
25
- executionProviders.splice(wasmIdx, 1);
26
- }
27
- } catch {}
46
+ const { pipeline, env } = await import("@huggingface/transformers");
28
47
 
29
48
  env.cacheDir = MODELS_DIR;
30
49
  env.allowLocalModels = true;
@@ -33,7 +52,32 @@ export async function getExtractor(modelName = null, progressCallback = null) {
33
52
  env.remotePathTemplate = "{model}/resolve/{revision}/";
34
53
  env.sharp = false;
35
54
 
36
- const pipelineOpts = { quantized: true };
55
+ const numThreads = getOptimalThreadCount();
56
+ if (env.backends?.onnx?.wasm) {
57
+ env.backends.onnx.wasm.numThreads = numThreads;
58
+ }
59
+
60
+ const sessionOptions = {
61
+ graphOptimizationLevel: "all",
62
+ executionMode: targetDevice !== "cpu" ? "parallel" : "sequential",
63
+ };
64
+
65
+ if (targetDevice !== "cpu") {
66
+ sessionOptions.enableCpuMemArena = false;
67
+ sessionOptions.enableMemPattern = false;
68
+ if (targetDevice === "dml") {
69
+ sessionOptions.executionProviders = [{ name: "dml", device_id: 0 }];
70
+ } else if (targetDevice === "cuda") {
71
+ sessionOptions.executionProviders = [{ name: "cuda", device_id: 0 }];
72
+ }
73
+ }
74
+
75
+ const pipelineOpts = {
76
+ quantized: true,
77
+ dtype: "q8",
78
+ device: targetDevice,
79
+ session_options: sessionOptions,
80
+ };
37
81
  if (progressCallback) {
38
82
  pipelineOpts.progress_callback = progressCallback;
39
83
  }
@@ -41,11 +85,26 @@ export async function getExtractor(modelName = null, progressCallback = null) {
41
85
  try {
42
86
  extractorInstance = await pipeline("feature-extraction", targetModel, pipelineOpts);
43
87
  loadedModelName = targetModel;
88
+ loadedDevice = targetDevice;
89
+
90
+ if (targetDevice !== "cpu") {
91
+ try {
92
+ await extractorInstance("GPU VRAM Warmup Init", { pooling: "mean", normalize: true });
93
+ } catch {}
94
+ }
44
95
  } catch (err) {
45
- console.warn(`HuggingFace model load failed for ${targetModel}: ${err.message}. Falling back to default Xenova/multilingual-e5-small...`);
46
- if (targetModel !== "Xenova/multilingual-e5-small") {
96
+ if (targetDevice !== "cpu") {
97
+ console.warn(`[GPU Engine] GPU initialization (${targetDevice}) failed: ${err.message}. Falling back to CPU...`);
98
+ pipelineOpts.device = "cpu";
99
+ pipelineOpts.session_options.executionMode = "sequential";
100
+ extractorInstance = await pipeline("feature-extraction", targetModel, pipelineOpts);
101
+ loadedModelName = targetModel;
102
+ loadedDevice = "cpu";
103
+ } else if (targetModel !== "Xenova/multilingual-e5-small") {
104
+ console.warn(`HuggingFace model load failed for ${targetModel}: ${err.message}. Falling back to default Xenova/multilingual-e5-small...`);
47
105
  extractorInstance = await pipeline("feature-extraction", "Xenova/multilingual-e5-small", pipelineOpts);
48
106
  loadedModelName = "Xenova/multilingual-e5-small";
107
+ loadedDevice = targetDevice;
49
108
  } else {
50
109
  throw err;
51
110
  }
@@ -54,6 +113,12 @@ export async function getExtractor(modelName = null, progressCallback = null) {
54
113
  return extractorInstance;
55
114
  }
56
115
 
116
+ export function resetExtractor() {
117
+ extractorInstance = null;
118
+ loadedModelName = null;
119
+ loadedDevice = null;
120
+ }
121
+
57
122
  export function formatInputText(text, isQuery = false, modelName = null, instruction = null) {
58
123
  if (!text) return "";
59
124
  const targetModel = modelName || getConfig().embeddingModel || "Xenova/multilingual-e5-small";
@@ -66,6 +131,11 @@ export function formatInputText(text, isQuery = false, modelName = null, instruc
66
131
  cleanText = cleanText.substring(7).trim();
67
132
  }
68
133
 
134
+ // Safety trim long text to prevent ONNX tensor padding explosions (>4000 chars ~1000 tokens)
135
+ if (cleanText.length > 4000) {
136
+ cleanText = cleanText.substring(0, 4000);
137
+ }
138
+
69
139
  // 1. E5 Model Family (multilingual-e5-small, multilingual-e5-large, etc.)
70
140
  if (name.includes("e5")) {
71
141
  const isInstructModel = name.includes("-instruct");
@@ -98,12 +168,127 @@ export async function embedText(text, isQuery = false, modelName = null, progres
98
168
  const extractor = await getExtractor(targetModel, progressCallback);
99
169
  const formattedText = formatInputText(text, isQuery, targetModel, instruction);
100
170
 
171
+ const isBgeM3 = targetModel.toLowerCase().includes("bge-m3");
172
+ const maxLen = isBgeM3 ? 1024 : 512;
173
+
101
174
  const output = await extractor(formattedText, {
102
175
  pooling: "mean",
103
176
  normalize: true,
177
+ truncation: true,
178
+ max_length: maxLen,
104
179
  });
105
180
 
106
- return new Float32Array(output.data);
181
+ const result = output.data.slice();
182
+ if (typeof output.dispose === 'function') {
183
+ output.dispose();
184
+ }
185
+ return result;
186
+ }
187
+
188
+ export async function embedBatch(texts, isQuery = false, modelName = null, progressCallback = null, instruction = null, traceOptions = {}) {
189
+ if (!texts || texts.length === 0) return [];
190
+ const targetModel = modelName || getConfig().embeddingModel || "Xenova/multilingual-e5-small";
191
+
192
+ const rawDevice = (getConfig().executionDevice || "cpu").toLowerCase();
193
+ const isGpu = rawDevice === "webgpu" || rawDevice === "gpu" || rawDevice === "dml" || rawDevice === "cuda";
194
+
195
+ const tracer = traceOptions.enableTrace ? new ExecutionTracer(`Embed Batch (${texts.length} items)`) : null;
196
+ const monitor = (isGpu && traceOptions.enableMonitor) ? new GpuMonitor(50) : null;
197
+ if (monitor) monitor.start();
198
+
199
+ try {
200
+ if (tracer) tracer.startStage("Model Initialization & Session", "CPU");
201
+ const extractor = await getExtractor(targetModel, progressCallback);
202
+
203
+ if (tracer) tracer.startStage("Text Formatting & Tokenization Preprocessing", "CPU");
204
+ const formattedTexts = texts.map((text) => formatInputText(text, isQuery, targetModel, instruction));
205
+
206
+ const userBatch = getConfig().batchSize || 12;
207
+ const isBgeM3 = targetModel.toLowerCase().includes("bge-m3");
208
+ const isLarge = isBgeM3 || targetModel.toLowerCase().includes("large");
209
+
210
+ // DYNAMIC ATTENTION BUDGET SAMPLER (PyTorch-style Token Budgeting):
211
+ // Attention memory scales quadratically O(seq_len^2).
212
+ // Target max GPU attention budget per ONNX pass: ~2,000,000 token-squared units (~1.5 GB VRAM peak).
213
+ // Short texts (50 tokens) dynamically scale UP to full userBatch (32-64 items per pass) for max GPU compute.
214
+ // Long texts (1000 tokens) dynamically scale DOWN to 2-4 items per pass, keeping VRAM strictly <1.5 GB.
215
+ const configuredBudget = getConfig().gpuAttentionBudget || 2000000;
216
+ const maxAttentionBudget = isGpu
217
+ ? (isLarge ? Math.min(configuredBudget, 2000000) : configuredBudget)
218
+ : 32000000;
219
+
220
+ const CHAR_TO_TOKEN = 3.5;
221
+ const subBatches = [];
222
+ let currentSubBatch = [];
223
+ let currentSubBatchCost = 0;
224
+
225
+ for (const text of formattedTexts) {
226
+ const estimatedTokens = Math.max(1, Math.ceil(text.length / CHAR_TO_TOKEN));
227
+ const cost = estimatedTokens * estimatedTokens;
228
+
229
+ if (
230
+ currentSubBatch.length > 0 &&
231
+ (currentSubBatch.length >= userBatch || currentSubBatchCost + cost > maxAttentionBudget)
232
+ ) {
233
+ subBatches.push(currentSubBatch);
234
+ currentSubBatch = [];
235
+ currentSubBatchCost = 0;
236
+ }
237
+
238
+ currentSubBatch.push(text);
239
+ currentSubBatchCost += cost;
240
+ }
241
+
242
+ if (currentSubBatch.length > 0) {
243
+ subBatches.push(currentSubBatch);
244
+ }
245
+
246
+ if (tracer) tracer.startStage("ONNX Model Tensor Execution", isGpu ? "GPU" : "CPU");
247
+
248
+ const allResults = [];
249
+ const maxLen = isBgeM3 ? 1024 : 512;
250
+
251
+ for (const batchTexts of subBatches) {
252
+ const output = await extractor(batchTexts, {
253
+ pooling: "mean",
254
+ normalize: true,
255
+ truncation: true,
256
+ padding: isGpu ? "max_length" : true,
257
+ max_length: maxLen,
258
+ });
259
+
260
+ const dims = output.dims;
261
+ const batchSize = dims[0];
262
+ const vectorDim = dims[dims.length - 1];
263
+ const rawData = output.data;
264
+
265
+ for (let i = 0; i < batchSize; i++) {
266
+ const byteOffset = i * vectorDim;
267
+ allResults.push(rawData.slice(byteOffset, byteOffset + vectorDim));
268
+ }
269
+
270
+ if (typeof output.dispose === 'function') {
271
+ output.dispose();
272
+ }
273
+
274
+ if (isGpu && global.gc) {
275
+ global.gc({ type: 'minor' });
276
+ }
277
+ }
278
+
279
+ if (tracer) tracer.endStage();
280
+
281
+ const gpuStats = monitor ? monitor.stop() : null;
282
+
283
+ if (tracer && traceOptions.verboseTrace) {
284
+ tracer.printTraceReport(gpuStats);
285
+ }
286
+
287
+ return allResults;
288
+ } catch (err) {
289
+ if (monitor) monitor.stop();
290
+ throw err;
291
+ }
107
292
  }
108
293
 
109
294
  export async function getReranker(modelName = "Xenova/bge-reranker-base", progressCallback = null) {
@@ -111,7 +296,7 @@ export async function getReranker(modelName = "Xenova/bge-reranker-base", progre
111
296
  return rerankerInstance;
112
297
  }
113
298
 
114
- const { pipeline, env } = await import("@xenova/transformers");
299
+ const { pipeline, env } = await import("@huggingface/transformers");
115
300
  env.cacheDir = MODELS_DIR;
116
301
  env.allowLocalModels = true;
117
302
  env.allowRemoteModels = true;
@@ -119,7 +304,32 @@ export async function getReranker(modelName = "Xenova/bge-reranker-base", progre
119
304
  env.remotePathTemplate = "{model}/resolve/{revision}/";
120
305
  env.sharp = false;
121
306
 
122
- const pipelineOpts = { quantized: true };
307
+ const rawDevice = (getConfig().executionDevice || "cpu").toLowerCase();
308
+ let targetDevice = "cpu";
309
+ if (rawDevice === "webgpu" || rawDevice === "gpu" || rawDevice === "dml" || rawDevice === "cuda") {
310
+ targetDevice = process.platform === "win32" ? "dml" : (process.platform === "linux" ? "cuda" : "webgpu");
311
+ }
312
+
313
+ const sessionOptions = {
314
+ graphOptimizationLevel: "all",
315
+ };
316
+
317
+ if (targetDevice !== "cpu") {
318
+ sessionOptions.enableCpuMemArena = true;
319
+ sessionOptions.enableMemPattern = true;
320
+ if (targetDevice === "dml") {
321
+ sessionOptions.executionProviders = [{ name: "dml", device_id: 0 }];
322
+ } else if (targetDevice === "cuda") {
323
+ sessionOptions.executionProviders = [{ name: "cuda", device_id: 0 }];
324
+ }
325
+ }
326
+
327
+ const pipelineOpts = {
328
+ quantized: true,
329
+ dtype: "q8",
330
+ device: targetDevice,
331
+ session_options: sessionOptions,
332
+ };
123
333
  if (progressCallback) {
124
334
  pipelineOpts.progress_callback = progressCallback;
125
335
  }
@@ -189,3 +399,97 @@ export function cosineSimilarity(vecA, vecB) {
189
399
  if (normA === 0 || normB === 0) return 0;
190
400
  return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
191
401
  }
402
+
403
+ export function getModelStorageInfo(modelName) {
404
+ if (!modelName || modelName === "none") return { status: "not_downloaded", sizeMB: "0.00", bytes: 0 };
405
+ const parts = modelName.split("/");
406
+ const modelDir = path.join(MODELS_DIR, ...parts);
407
+
408
+ if (!fs.existsSync(modelDir)) {
409
+ return { status: "not_downloaded", sizeMB: "0.00", bytes: 0, dir: modelDir };
410
+ }
411
+
412
+ let totalBytes = 0;
413
+ let hasConfig = false;
414
+ let hasTokenizer = false;
415
+ let hasOnnxWeights = false;
416
+
417
+ function scan(dir) {
418
+ try {
419
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
420
+ for (const entry of entries) {
421
+ const fullPath = path.join(dir, entry.name);
422
+ if (entry.isDirectory()) {
423
+ scan(fullPath);
424
+ } else if (entry.isFile()) {
425
+ const stat = fs.statSync(fullPath);
426
+ totalBytes += stat.size;
427
+ if (entry.name === "config.json") hasConfig = true;
428
+ if (entry.name.includes("tokenizer")) hasTokenizer = true;
429
+ // ONNX weights must be substantial (>5MB) or external data (.onnx_data)
430
+ if ((entry.name.endsWith(".onnx") || entry.name.endsWith(".onnx_data")) && stat.size > 5 * 1024 * 1024) {
431
+ hasOnnxWeights = true;
432
+ }
433
+ }
434
+ }
435
+ } catch {}
436
+ }
437
+
438
+ scan(modelDir);
439
+
440
+ const sizeMB = (totalBytes / (1024 * 1024)).toFixed(2);
441
+
442
+ if (totalBytes === 0) {
443
+ return { status: "not_downloaded", sizeMB: "0.00", bytes: 0, dir: modelDir };
444
+ }
445
+
446
+ // Model is ready only if it has config, tokenizer, ONNX weights >5MB, and total folder size >10MB
447
+ if (hasConfig && hasTokenizer && hasOnnxWeights && totalBytes > 10 * 1024 * 1024) {
448
+ return { status: "downloaded", sizeMB, bytes: totalBytes, dir: modelDir };
449
+ }
450
+
451
+ return { status: "partial", sizeMB, bytes: totalBytes, dir: modelDir };
452
+ }
453
+
454
+ export function deleteModelCache(modelName) {
455
+ const info = getModelStorageInfo(modelName);
456
+ if (info.status === "not_downloaded" || !fs.existsSync(info.dir)) {
457
+ return { deleted: false, reason: "Model directory not found" };
458
+ }
459
+
460
+ try {
461
+ fs.rmSync(info.dir, { recursive: true, force: true });
462
+ const parentDir = path.dirname(info.dir);
463
+ if (fs.existsSync(parentDir) && fs.readdirSync(parentDir).length === 0) {
464
+ fs.rmdirSync(parentDir);
465
+ }
466
+ resetExtractor();
467
+ return { deleted: true, modelName, freedMB: info.sizeMB };
468
+ } catch (err) {
469
+ return { deleted: false, reason: err.message };
470
+ }
471
+ }
472
+
473
+ export function listAllCachedModels() {
474
+ const result = [];
475
+ if (!fs.existsSync(MODELS_DIR)) return result;
476
+
477
+ try {
478
+ const orgs = fs.readdirSync(MODELS_DIR, { withFileTypes: true });
479
+ for (const org of orgs) {
480
+ if (org.isDirectory()) {
481
+ const orgDir = path.join(MODELS_DIR, org.name);
482
+ const models = fs.readdirSync(orgDir, { withFileTypes: true });
483
+ for (const model of models) {
484
+ if (model.isDirectory()) {
485
+ const modelName = `${org.name}/${model.name}`;
486
+ const info = getModelStorageInfo(modelName);
487
+ result.push({ modelName, ...info });
488
+ }
489
+ }
490
+ }
491
+ }
492
+ } catch {}
493
+
494
+ return result;
495
+ }
@@ -1,44 +1,44 @@
1
- import { execSync } from "child_process";
2
-
3
- // Only run graceful process termination during explicit global npm updates
4
- // Skip if running interactively or inside active MCP sessions to avoid EOF drops
5
- if (process.env.npm_config_global === "true" || process.env.MEMORY_PREINSTALL_FORCE === "true") {
6
- try {
7
- const currentPid = process.pid;
8
- const ppid = process.ppid;
9
-
10
- if (process.platform === "win32") {
11
- try {
12
- const psCmd = `Get-CimInstance Win32_Process | Where-Object { ($_.CommandLine -like '*mcp-server/index.js*' -or $_.CommandLine -like '*mcp-server\\\\index.js*') -and $_.CommandLine -notlike '*install*' -and $_.ProcessId -ne ${currentPid} -and $_.ProcessId -ne ${ppid} } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }`;
13
- execSync(`powershell -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: "ignore" });
14
- } catch {}
15
- } else {
16
- try {
17
- const psOutput = execSync(`ps -eo pid,ppid,args 2>/dev/null || true`, { encoding: "utf-8" });
18
- const lines = psOutput.split("\n");
19
- for (const line of lines) {
20
- const trimmed = line.trim();
21
- if (!trimmed) continue;
22
- const parts = trimmed.split(/\s+/);
23
- const pid = parseInt(parts[0], 10);
24
- const parentPid = parseInt(parts[1], 10);
25
- const cmd = parts.slice(2).join(" ");
26
-
27
- if (!pid || pid === currentPid || pid === ppid || parentPid === currentPid) continue;
28
-
29
- const isServer = cmd.includes("mcp-server/index.js") || cmd.includes("mcp-server/index.js");
30
- const isInstaller = /npm|npx|yarn|pnpm|preinstall|install/i.test(cmd);
31
-
32
- if (isServer && !isInstaller) {
33
- try {
34
- process.kill(pid, "SIGTERM");
35
- } catch {}
36
- }
37
- }
38
- } catch {}
39
- }
40
- } catch (e) {
41
- // Ignore errors
42
- }
43
- }
44
-
1
+ import { execSync } from "child_process";
2
+
3
+ // Only run graceful process termination during explicit global npm updates
4
+ // Skip if running interactively or inside active MCP sessions to avoid EOF drops
5
+ if (process.env.npm_config_global === "true" || process.env.MEMORY_PREINSTALL_FORCE === "true") {
6
+ try {
7
+ const currentPid = process.pid;
8
+ const ppid = process.ppid;
9
+
10
+ if (process.platform === "win32") {
11
+ try {
12
+ const psCmd = `Get-CimInstance Win32_Process | Where-Object { ($_.CommandLine -like '*mcp-server/index.js*' -or $_.CommandLine -like '*mcp-server\\\\index.js*') -and $_.CommandLine -notlike '*install*' -and $_.ProcessId -ne ${currentPid} -and $_.ProcessId -ne ${ppid} } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }`;
13
+ execSync(`powershell -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: "ignore" });
14
+ } catch {}
15
+ } else {
16
+ try {
17
+ const psOutput = execSync(`ps -eo pid,ppid,args 2>/dev/null || true`, { encoding: "utf-8" });
18
+ const lines = psOutput.split("\n");
19
+ for (const line of lines) {
20
+ const trimmed = line.trim();
21
+ if (!trimmed) continue;
22
+ const parts = trimmed.split(/\s+/);
23
+ const pid = parseInt(parts[0], 10);
24
+ const parentPid = parseInt(parts[1], 10);
25
+ const cmd = parts.slice(2).join(" ");
26
+
27
+ if (!pid || pid === currentPid || pid === ppid || parentPid === currentPid) continue;
28
+
29
+ const isServer = cmd.includes("mcp-server/index.js") || cmd.includes("mcp-server/index.js");
30
+ const isInstaller = /npm|npx|yarn|pnpm|preinstall|install/i.test(cmd);
31
+
32
+ if (isServer && !isInstaller) {
33
+ try {
34
+ process.kill(pid, "SIGTERM");
35
+ } catch {}
36
+ }
37
+ }
38
+ } catch {}
39
+ }
40
+ } catch (e) {
41
+ // Ignore errors
42
+ }
43
+ }
44
+
@@ -1,5 +1,5 @@
1
1
  import { getDatabase } from "../db/database.js";
2
- import { embedText, bufferToVector, cosineSimilarity, rerankHits } from "../ml/model_manager.js";
2
+ import { embedText, cosineSimilarity, rerankHits } from "../ml/model_manager.js";
3
3
  import { getRelatedSymbols } from "../graph/graph_extractor.js";
4
4
  import { getConfig } from "../config/config_manager.js";
5
5
 
@@ -40,17 +40,22 @@ export function bm25Search(db, query, limit = 30) {
40
40
  export function vectorSearch(db, queryVector, limit = 30, minSim = 0.25) {
41
41
  if (!queryVector || queryVector.length === 0) return [];
42
42
 
43
+ const vectorDim = queryVector.length;
44
+ const tempBuf = new ArrayBuffer(vectorDim * 4);
45
+ const tempView = new Uint8Array(tempBuf);
46
+ const tempVec = new Float32Array(tempBuf);
47
+
43
48
  const stmt = db.prepare(`
44
49
  SELECT m.id, m.section_id, m.doc_id, m.content, m.vector, s.breadcrumbs
45
50
  FROM micro_chunks m
46
51
  JOIN sections s ON m.section_id = s.id;
47
52
  `);
48
- const rows = stmt.all();
49
53
 
50
54
  const scored = [];
51
- for (const r of rows) {
52
- const vec = bufferToVector(r.vector);
53
- const sim = cosineSimilarity(queryVector, vec);
55
+ for (const r of stmt.iterate()) {
56
+ tempView.set(r.vector.subarray(0, vectorDim * 4));
57
+
58
+ const sim = cosineSimilarity(queryVector, tempVec);
54
59
  if (!isNaN(sim) && sim >= minSim) {
55
60
  scored.push({
56
61
  id: r.id,