@danielblomma/cortex-mcp 2.1.1 → 2.2.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 +72 -24
- package/bin/cortex.mjs +50 -21
- package/mcp-registry-submission.json +63 -0
- package/package.json +19 -5
- package/scaffold/AGENTS.md +13 -9
- package/scaffold/CLAUDE.md +13 -9
- package/scaffold/docs/architecture.md +1 -1
- package/scaffold/mcp/build.mjs +92 -0
- package/scaffold/mcp/package.json +4 -4
- package/scaffold/mcp/src/cli/query.ts +455 -0
- package/scaffold/mcp/src/embed.ts +181 -77
- package/scaffold/mcp/src/embedScheduler.ts +509 -0
- package/scaffold/mcp/src/embeddings.ts +31 -30
- package/scaffold/mcp/src/graphCsv.ts +65 -0
- package/scaffold/mcp/src/jsonl.ts +69 -14
- package/scaffold/mcp/src/loadGraph.ts +276 -40
- package/scaffold/mcp/src/lruCache.ts +41 -0
- package/scaffold/mcp/src/searchCore.ts +1 -1
- package/scaffold/mcp/src/searchResults.ts +3 -3
- package/scaffold/mcp/src/types.ts +6 -1
- package/scaffold/mcp/tests/embed-scheduler.test.mjs +474 -0
- package/scaffold/mcp/tests/embedding-jsonl.test.mjs +39 -0
- package/scaffold/mcp/tests/graph-bulk-load.test.mjs +258 -0
- package/scaffold/mcp/tests/graph-csv.test.mjs +118 -0
- package/scaffold/mcp/tests/lru-cache.test.mjs +37 -0
- package/scaffold/mcp/tests/query-cli.test.mjs +115 -0
- package/scaffold/mcp/tests/vector-index.test.mjs +109 -0
- package/scaffold/mcp/tsconfig.json +3 -1
- package/scaffold/scripts/bootstrap.sh +37 -4
- package/scaffold/scripts/dashboard.mjs +1 -1
- package/scaffold/scripts/doctor.sh +9 -8
- package/scaffold/scripts/embed.sh +3 -2
- package/scaffold/scripts/ingest-parsers.mjs +387 -0
- package/scaffold/scripts/ingest-worker.mjs +52 -0
- package/scaffold/scripts/ingest.mjs +550 -378
- package/scaffold/scripts/load-ryu.sh +3 -2
- package/scaffold/scripts/memory-compile.mjs +5 -2
- package/scaffold/scripts/memory-lint.mjs +5 -2
- package/scaffold/scripts/parsers/csharp.mjs +2 -1
- package/scaffold/scripts/parsers/javascript/ast.mjs +160 -8
- package/scaffold/scripts/parsers/javascript/chunks.mjs +129 -22
- package/scaffold/scripts/parsers/javascript/imports.mjs +38 -4
- package/scaffold/scripts/parsers/vbnet.mjs +2 -1
- package/scaffold/scripts/status.sh +50 -14
- package/scaffold/scripts/parsers/node_modules/.package-lock.json +0 -56
- package/scaffold/scripts/parsers/node_modules/acorn/CHANGELOG.md +0 -972
- package/scaffold/scripts/parsers/node_modules/acorn/LICENSE +0 -21
- package/scaffold/scripts/parsers/node_modules/acorn/README.md +0 -301
- package/scaffold/scripts/parsers/node_modules/acorn/bin/acorn +0 -4
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.mts +0 -883
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.ts +0 -883
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.js +0 -6295
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.mjs +0 -6266
- package/scaffold/scripts/parsers/node_modules/acorn/dist/bin.js +0 -90
- package/scaffold/scripts/parsers/node_modules/acorn/package.json +0 -50
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/CHANGELOG.md +0 -421
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/LICENSE +0 -21
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/README.md +0 -81
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.d.ts +0 -103
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js +0 -78
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.d.ts +0 -167
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js +0 -75
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.d.ts +0 -177
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js +0 -56
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.d.ts +0 -198
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js +0 -327
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.d.ts +0 -256
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js +0 -256
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.d.ts +0 -472
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.mjs +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.d.ts +0 -159
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js +0 -2
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.d.ts +0 -10
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js +0 -38
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.d.ts +0 -12
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js +0 -29
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.d.ts +0 -2
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js +0 -118
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.d.ts +0 -60
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js +0 -2
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.d.ts +0 -2
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js +0 -19
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/package.json +0 -53
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/tsconfig.json +0 -19
- package/scaffold/scripts/parsers/node_modules/acorn-walk/CHANGELOG.md +0 -209
- package/scaffold/scripts/parsers/node_modules/acorn-walk/LICENSE +0 -21
- package/scaffold/scripts/parsers/node_modules/acorn-walk/README.md +0 -124
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.mts +0 -152
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.ts +0 -152
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.js +0 -485
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.mjs +0 -467
- package/scaffold/scripts/parsers/node_modules/acorn-walk/package.json +0 -50
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/LICENSE +0 -24
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/README.md +0 -23
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-bash.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c_sharp.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-cpp.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-css.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-dart.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elisp.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elixir.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elm.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-embedded_template.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-go.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-html.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-java.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-javascript.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-json.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-kotlin.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-lua.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-objc.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ocaml.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-php.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-python.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ql.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rescript.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ruby.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rust.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-scala.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-solidity.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-swift.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-systemrdl.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tlaplus.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-toml.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tsx.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-typescript.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-vue.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-yaml.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-zig.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/package.json +0 -64
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/LICENSE +0 -21
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/README.md +0 -198
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/package.json +0 -37
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter-web.d.ts +0 -242
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.js +0 -1
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.wasm +0 -0
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
5
6
|
import { env, pipeline } from "@huggingface/transformers";
|
|
6
|
-
import { readJsonl, asString, asNumber, asBoolean } from "./jsonl.js";
|
|
7
|
+
import { readJsonl, readJsonlRecords, writeJsonlRecords, asString, asNumber, asBoolean } from "./jsonl.js";
|
|
7
8
|
import { CACHE_DIR, PATHS } from "./paths.js";
|
|
9
|
+
import {
|
|
10
|
+
createTokenCounter,
|
|
11
|
+
DEFAULT_SCHEDULER_OPTIONS,
|
|
12
|
+
groupDuplicates,
|
|
13
|
+
packWorkUnits,
|
|
14
|
+
resolveInFlightTokens,
|
|
15
|
+
resolveMemoryHeadroom,
|
|
16
|
+
resolveModelMaxTokens,
|
|
17
|
+
resolvePoolConfig,
|
|
18
|
+
runWorkUnits,
|
|
19
|
+
type EmbedExtractor,
|
|
20
|
+
type MeasuredText,
|
|
21
|
+
type PendingText
|
|
22
|
+
} from "./embedScheduler.js";
|
|
8
23
|
import type { JsonObject, JsonValue } from "./types.js";
|
|
9
24
|
|
|
10
25
|
const EMBEDDINGS_PATH = PATHS.embeddingsEntities;
|
|
@@ -137,11 +152,6 @@ function normalizeText(value: string): string {
|
|
|
137
152
|
return value.replace(/\s+/g, " ").trim();
|
|
138
153
|
}
|
|
139
154
|
|
|
140
|
-
function writeJsonl(filePath: string, records: EmbeddingRecord[]): void {
|
|
141
|
-
const body = records.map((record) => JSON.stringify(record)).join("\n");
|
|
142
|
-
fs.writeFileSync(filePath, body ? `${body}\n` : "", "utf8");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
155
|
function ensureRequiredFiles(): void {
|
|
146
156
|
const required = [
|
|
147
157
|
path.join(CACHE_DIR, "documents.jsonl"),
|
|
@@ -350,7 +360,7 @@ function parseProjectEntities(raw: JsonObject[]): ProjectEntity[] {
|
|
|
350
360
|
.filter((value): value is ProjectEntity => value !== null);
|
|
351
361
|
}
|
|
352
362
|
|
|
353
|
-
function parseExistingEmbeddings(raw: JsonObject
|
|
363
|
+
function parseExistingEmbeddings(raw: Iterable<JsonObject>, modelId: string): Map<string, EmbeddingRecord> {
|
|
354
364
|
const index = new Map<string, EmbeddingRecord>();
|
|
355
365
|
|
|
356
366
|
for (const item of raw) {
|
|
@@ -360,9 +370,12 @@ function parseExistingEmbeddings(raw: JsonObject[], modelId: string): Map<string
|
|
|
360
370
|
const vectorRaw = item.vector;
|
|
361
371
|
if (!Array.isArray(vectorRaw)) continue;
|
|
362
372
|
|
|
363
|
-
const vector =
|
|
364
|
-
|
|
365
|
-
|
|
373
|
+
const vector: number[] = [];
|
|
374
|
+
for (const value of vectorRaw) {
|
|
375
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
376
|
+
vector.push(value);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
366
379
|
|
|
367
380
|
if (vector.length === 0) continue;
|
|
368
381
|
const model = asString(item.model);
|
|
@@ -388,23 +401,20 @@ function parseExistingEmbeddings(raw: JsonObject[], modelId: string): Map<string
|
|
|
388
401
|
return index;
|
|
389
402
|
}
|
|
390
403
|
|
|
391
|
-
function toEmbeddingVector(output: unknown): number[] {
|
|
392
|
-
if (!output || typeof output !== "object") {
|
|
393
|
-
throw new Error("Invalid embedding output type");
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const data = (output as { data?: unknown }).data;
|
|
397
|
-
if (!data || typeof (data as ArrayLike<number>).length !== "number") {
|
|
398
|
-
throw new Error("Missing embedding data");
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return Array.from(data as ArrayLike<number>).map((value) => Number(value));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
404
|
function roundVector(values: number[]): number[] {
|
|
405
405
|
return values.map((value) => Number(value.toFixed(6)));
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
+
function* presentEmbeddingRecords(
|
|
409
|
+
slots: Array<EmbeddingRecord | null>
|
|
410
|
+
): Generator<EmbeddingRecord> {
|
|
411
|
+
for (const record of slots) {
|
|
412
|
+
if (record) {
|
|
413
|
+
yield record;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
408
418
|
async function main(): Promise<void> {
|
|
409
419
|
const { mode } = parseArgs(process.argv);
|
|
410
420
|
ensureRequiredFiles();
|
|
@@ -429,36 +439,28 @@ async function main(): Promise<void> {
|
|
|
429
439
|
|
|
430
440
|
const entities: SearchEntity[] = [...documents, ...rules, ...adrs, ...modules, ...projects, ...chunks].sort((a, b) => a.id.localeCompare(b.id));
|
|
431
441
|
|
|
432
|
-
const existing = parseExistingEmbeddings(
|
|
442
|
+
const existing = parseExistingEmbeddings(readJsonlRecords(EMBEDDINGS_PATH), modelId);
|
|
433
443
|
|
|
434
444
|
env.cacheDir = MODEL_CACHE_DIR;
|
|
435
|
-
//
|
|
436
|
-
// jobs
|
|
437
|
-
//
|
|
445
|
+
// Total thread budget for embedding. CORTEX_EMBED_THREADS caps it so
|
|
446
|
+
// co-located embedders (parallel CI jobs, eval containers) do not
|
|
447
|
+
// oversubscribe shared cores; unset = all cores.
|
|
438
448
|
const threadsRaw = Number(process.env.CORTEX_EMBED_THREADS);
|
|
439
|
-
const
|
|
440
|
-
Number.isFinite(threadsRaw) && threadsRaw >= 1
|
|
441
|
-
? { session_options: { intraOpNumThreads: Math.floor(threadsRaw), interOpNumThreads: 1 } }
|
|
442
|
-
: undefined;
|
|
443
|
-
const extractor = await pipeline(
|
|
444
|
-
"feature-extraction",
|
|
445
|
-
modelId,
|
|
446
|
-
sessionOptions as Parameters<typeof pipeline>[2]
|
|
447
|
-
);
|
|
449
|
+
const threadBudget =
|
|
450
|
+
Number.isFinite(threadsRaw) && threadsRaw >= 1 ? Math.floor(threadsRaw) : os.cpus().length;
|
|
448
451
|
|
|
449
452
|
let reused = 0;
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const
|
|
453
|
-
const output: EmbeddingRecord[] = [];
|
|
453
|
+
// Slot per entity keeps output in entity order; failed slots stay null.
|
|
454
|
+
const slots: Array<EmbeddingRecord | null> = entities.map(() => null);
|
|
455
|
+
const pending: PendingText[] = [];
|
|
454
456
|
let dimensions = 0;
|
|
455
457
|
|
|
456
|
-
|
|
458
|
+
entities.forEach((entity, index) => {
|
|
457
459
|
const previous = existing.get(entity.id);
|
|
458
460
|
if (previous && previous.signature === entity.signature && previous.vector.length > 0) {
|
|
459
461
|
reused += 1;
|
|
460
462
|
dimensions = dimensions || previous.vector.length;
|
|
461
|
-
|
|
463
|
+
slots[index] = {
|
|
462
464
|
...previous,
|
|
463
465
|
entity_type: entity.type,
|
|
464
466
|
kind: entity.kind,
|
|
@@ -471,46 +473,146 @@ async function main(): Promise<void> {
|
|
|
471
473
|
signature: entity.signature,
|
|
472
474
|
model: modelId,
|
|
473
475
|
dimensions: previous.vector.length
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
+
};
|
|
477
|
+
return;
|
|
476
478
|
}
|
|
479
|
+
pending.push({ index, text: normalizeText(entity.text) });
|
|
480
|
+
});
|
|
477
481
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
482
|
+
// Deduplicate identical texts (lossless: identical input -> identical
|
|
483
|
+
// vector), then measure token lengths for routing and batch packing.
|
|
484
|
+
const unique = groupDuplicates(pending);
|
|
485
|
+
// Memory headroom drives pool size and the concurrency gate; everything
|
|
486
|
+
// adapts to the machine so no tuning is expected from users. Container
|
|
487
|
+
// limits (cgroups) and platforms that under-report free memory are both
|
|
488
|
+
// handled inside resolveMemoryHeadroom.
|
|
489
|
+
const readHeadroom = () =>
|
|
490
|
+
resolveMemoryHeadroom({
|
|
491
|
+
freeMemory: os.freemem(),
|
|
492
|
+
totalMemory: os.totalmem(),
|
|
493
|
+
constrainedMemory: process.constrainedMemory?.() ?? null,
|
|
494
|
+
availableMemory: process.availableMemory?.() ?? null
|
|
495
|
+
});
|
|
496
|
+
const memoryHeadroom = readHeadroom();
|
|
497
|
+
const poolConfig = resolvePoolConfig({
|
|
498
|
+
threadBudget,
|
|
499
|
+
poolOverride: Number(process.env.CORTEX_EMBED_POOL) || null,
|
|
500
|
+
uniqueCount: unique.length,
|
|
501
|
+
memoryBytes: memoryHeadroom
|
|
502
|
+
});
|
|
487
503
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
504
|
+
const batchSizeRaw = Number(process.env.CORTEX_EMBED_BATCH_SIZE);
|
|
505
|
+
const batchTokensRaw = Number(process.env.CORTEX_EMBED_BATCH_TOKENS);
|
|
506
|
+
const shortTokensRaw = Number(process.env.CORTEX_EMBED_SHORT_TOKENS);
|
|
507
|
+
const schedulerOptions = {
|
|
508
|
+
...DEFAULT_SCHEDULER_OPTIONS,
|
|
509
|
+
// 0 (or any value below 1) disables micro-batching rather than silently
|
|
510
|
+
// meaning "use the default".
|
|
511
|
+
...(Number.isFinite(batchSizeRaw) && batchSizeRaw >= 0
|
|
512
|
+
? { batchMaxItems: Math.max(1, Math.floor(batchSizeRaw)) }
|
|
513
|
+
: {}),
|
|
514
|
+
...(Number.isFinite(batchTokensRaw) && batchTokensRaw >= 16
|
|
515
|
+
? { batchTokenBudget: Math.floor(batchTokensRaw) }
|
|
516
|
+
: {}),
|
|
517
|
+
...(Number.isFinite(shortTokensRaw) && shortTokensRaw >= 1
|
|
518
|
+
? { shortMaxTokens: Math.floor(shortTokensRaw) }
|
|
519
|
+
: {})
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// Fully warm cache: nothing to embed, so skip model loading entirely —
|
|
523
|
+
// this is the common repeat-bootstrap / small-update path.
|
|
524
|
+
let embedded = 0;
|
|
525
|
+
let result: { vectors: Map<number, number[]>; failures: Array<{ index: number; message: string }> } = {
|
|
526
|
+
vectors: new Map(),
|
|
527
|
+
failures: []
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
if (unique.length > 0) {
|
|
531
|
+
const makeExtractor = async (threads: number) =>
|
|
532
|
+
(await pipeline("feature-extraction", modelId, {
|
|
533
|
+
session_options: { intraOpNumThreads: threads, interOpNumThreads: 1 }
|
|
534
|
+
} as Parameters<typeof pipeline>[2])) as unknown as EmbedExtractor & {
|
|
535
|
+
tokenizer?: ((text: string) => { input_ids?: { dims?: number[] } }) & {
|
|
536
|
+
model_max_length?: number;
|
|
537
|
+
};
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
// First session loads (and caches) the model; the rest load in parallel.
|
|
541
|
+
// Extra sessions failing (e.g. memory pressure) degrades the pool instead
|
|
542
|
+
// of aborting the run — one session can always finish the work.
|
|
543
|
+
const first = await makeExtractor(poolConfig.threadsPerSession);
|
|
544
|
+
const extraSessions = await Promise.allSettled(
|
|
545
|
+
Array.from({ length: poolConfig.sessions - 1 }, () =>
|
|
546
|
+
makeExtractor(poolConfig.threadsPerSession)
|
|
547
|
+
)
|
|
548
|
+
);
|
|
549
|
+
const extractors: EmbedExtractor[] = [
|
|
550
|
+
first,
|
|
551
|
+
...extraSessions
|
|
552
|
+
.filter(
|
|
553
|
+
(settled): settled is PromiseFulfilledResult<Awaited<ReturnType<typeof makeExtractor>>> =>
|
|
554
|
+
settled.status === "fulfilled"
|
|
555
|
+
)
|
|
556
|
+
.map((settled) => settled.value)
|
|
557
|
+
];
|
|
558
|
+
const failedSessions = extraSessions.length + 1 - extractors.length;
|
|
559
|
+
if (failedSessions > 0) {
|
|
560
|
+
const firstFailure = extraSessions.find(
|
|
561
|
+
(settled): settled is PromiseRejectedResult => settled.status === "rejected"
|
|
562
|
+
);
|
|
563
|
+
const reason =
|
|
564
|
+
firstFailure?.reason instanceof Error ? firstFailure.reason.message : String(firstFailure?.reason ?? "unknown");
|
|
565
|
+
console.warn(
|
|
566
|
+
`[embed] ${failedSessions} pool session(s) failed to load (${reason}); continuing with ${extractors.length}`
|
|
509
567
|
);
|
|
510
568
|
}
|
|
569
|
+
|
|
570
|
+
// Inference truncates at the model max; token counts must too, or one
|
|
571
|
+
// giant file inflates scheduling cost and gate mass far beyond reality.
|
|
572
|
+
const modelMaxTokens = resolveModelMaxTokens(first.tokenizer?.model_max_length);
|
|
573
|
+
const countTokens = createTokenCounter(first.tokenizer, modelMaxTokens);
|
|
574
|
+
|
|
575
|
+
const measured: MeasuredText[] = unique.map((item) => ({ ...item, tokens: countTokens(item.text) }));
|
|
576
|
+
const units = packWorkUnits(measured, schedulerOptions);
|
|
577
|
+
// Recompute headroom after the model copies are resident so the gate
|
|
578
|
+
// reflects what is actually left for inference activations.
|
|
579
|
+
const inFlightRaw = Number(process.env.CORTEX_EMBED_INFLIGHT_TOKENS);
|
|
580
|
+
const maxInFlightTokens =
|
|
581
|
+
Number.isFinite(inFlightRaw) && inFlightRaw >= 1024
|
|
582
|
+
? Math.floor(inFlightRaw)
|
|
583
|
+
: resolveInFlightTokens({ memoryBytes: readHeadroom(), modelMaxTokens });
|
|
584
|
+
result = await runWorkUnits(units, extractors, {
|
|
585
|
+
maxInFlightTokens,
|
|
586
|
+
onVector(index, rawVector) {
|
|
587
|
+
const entity = entities[index];
|
|
588
|
+
const vector = roundVector(rawVector);
|
|
589
|
+
embedded += 1;
|
|
590
|
+
dimensions = dimensions || vector.length;
|
|
591
|
+
slots[index] = {
|
|
592
|
+
id: entity.id,
|
|
593
|
+
entity_type: entity.type,
|
|
594
|
+
kind: entity.kind,
|
|
595
|
+
label: entity.label,
|
|
596
|
+
path: entity.path,
|
|
597
|
+
status: entity.status,
|
|
598
|
+
source_of_truth: entity.source_of_truth,
|
|
599
|
+
trust_level: entity.trust_level,
|
|
600
|
+
updated_at: entity.updated_at,
|
|
601
|
+
signature: entity.signature,
|
|
602
|
+
model: modelId,
|
|
603
|
+
dimensions: vector.length,
|
|
604
|
+
vector
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
});
|
|
511
608
|
}
|
|
512
609
|
|
|
513
|
-
|
|
610
|
+
const failures = result.failures.map(
|
|
611
|
+
(failure) => `${entities[failure.index].id}: ${failure.message}`
|
|
612
|
+
);
|
|
613
|
+
const failed = result.failures.length;
|
|
614
|
+
|
|
615
|
+
const outputCount = writeJsonlRecords(EMBEDDINGS_PATH, presentEmbeddingRecords(slots));
|
|
514
616
|
|
|
515
617
|
const manifest = {
|
|
516
618
|
generated_at: new Date().toISOString(),
|
|
@@ -519,7 +621,7 @@ async function main(): Promise<void> {
|
|
|
519
621
|
dimensions,
|
|
520
622
|
counts: {
|
|
521
623
|
entities: entities.length,
|
|
522
|
-
output:
|
|
624
|
+
output: outputCount,
|
|
523
625
|
embedded,
|
|
524
626
|
reused,
|
|
525
627
|
failed
|
|
@@ -529,7 +631,9 @@ async function main(): Promise<void> {
|
|
|
529
631
|
|
|
530
632
|
fs.writeFileSync(EMBEDDINGS_MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
531
633
|
|
|
532
|
-
console.log(
|
|
634
|
+
console.log(
|
|
635
|
+
`[embed] mode=${mode} model=${modelId} dim=${dimensions} pool=${poolConfig.sessions}x${poolConfig.threadsPerSession} batch<=${schedulerOptions.batchMaxItems}`
|
|
636
|
+
);
|
|
533
637
|
console.log(
|
|
534
638
|
`[embed] entities=${entities.length} embedded=${embedded} reused=${reused} failed=${failed}`
|
|
535
639
|
);
|