@equationalapplications/core-llm-wiki 4.20.0 → 4.22.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 +35 -0
- package/dist/{chunk-HKB5FEJG.mjs → chunk-LCAU6NYC.mjs} +270 -94
- package/dist/chunk-LCAU6NYC.mjs.map +1 -0
- package/dist/index.d.mts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +328 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +126 -11
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-BVN_g414.d.mts → testing-Bk8J6QLX.d.mts} +100 -7
- package/dist/{testing-BVN_g414.d.ts → testing-Bk8J6QLX.d.ts} +100 -7
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +167 -6
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-HKB5FEJG.mjs.map +0 -1
package/dist/testing.js
CHANGED
|
@@ -853,6 +853,15 @@ Return ONLY a valid JSON object matching this schema:
|
|
|
853
853
|
"facts": [{ "title": "string (max 80 chars)", "body": "string (max 800 chars)", "tags": ["string"], "confidence": "certain|inferred|tentative" }]
|
|
854
854
|
}
|
|
855
855
|
Extract verbatim factual content. Do not return markdown, just raw JSON.`;
|
|
856
|
+
var ONTOLOGY_BACKFILL_SYSTEM_PROMPT = `You are a knowledge classification agent. You will receive existing memory facts that currently have no ontology type. For each input fact { "id", "title", "body", "tags" }, assign the best matching okf_type from the ontology manifest and optionally propose edges to related facts by title.
|
|
857
|
+
Return ONLY a valid JSON object matching this schema:
|
|
858
|
+
{
|
|
859
|
+
"classifications": [
|
|
860
|
+
{ "id": "string (input fact id, copied verbatim)", "okf_type": "string (manifest node type slug)", "edges": [{ "edge_type": "string", "target_title": "string" }] }
|
|
861
|
+
]
|
|
862
|
+
}
|
|
863
|
+
If no manifest type fits a fact, omit that fact from "classifications" entirely \u2014 do not guess.
|
|
864
|
+
Do not return markdown, just raw JSON.`;
|
|
856
865
|
|
|
857
866
|
// src/services/PromptService.ts
|
|
858
867
|
var PromptService = class {
|
|
@@ -942,6 +951,22 @@ ${JSON.stringify(recentEvents, null, 2)}
|
|
|
942
951
|
The following document anchors are provided for contradiction detection only. Do not include them in \`downgraded\`, \`deleted\`, or \`newFacts\`.`
|
|
943
952
|
};
|
|
944
953
|
}
|
|
954
|
+
buildOntologyBackfillPrompt(facts, runtimeOverride, ontologyContext) {
|
|
955
|
+
const template = runtimeOverride ?? this.globalOverrides?.ontologyBackfillSystemPrompt ?? ONTOLOGY_BACKFILL_SYSTEM_PROMPT;
|
|
956
|
+
const hasFacts = /\{\{\s*facts\s*\}\}/.test(template);
|
|
957
|
+
if (hasFacts || this.hasOntologyPlaceholders(template)) {
|
|
958
|
+
return {
|
|
959
|
+
systemPrompt: this.buildSystemPrompt(template, { facts }, ontologyContext),
|
|
960
|
+
userPrompt: hasFacts ? "Please classify the facts." : `Facts:
|
|
961
|
+
${JSON.stringify(facts, null, 2)}`
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
return {
|
|
965
|
+
systemPrompt: this.appendOntology(template, ontologyContext),
|
|
966
|
+
userPrompt: `Facts:
|
|
967
|
+
${JSON.stringify(facts, null, 2)}`
|
|
968
|
+
};
|
|
969
|
+
}
|
|
945
970
|
};
|
|
946
971
|
|
|
947
972
|
// src/services/IngestionService.ts
|
|
@@ -1123,6 +1148,9 @@ function parseEmbedding(blob, text) {
|
|
|
1123
1148
|
// src/services/MaintenanceService.ts
|
|
1124
1149
|
var FUZZY_THRESHOLD = 0.5;
|
|
1125
1150
|
var MIN_TOKENS_TO_QUALIFY = 3;
|
|
1151
|
+
var ONTOLOGY_BACKFILL_BATCH_SIZE = 25;
|
|
1152
|
+
var ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS = 4e4;
|
|
1153
|
+
var ONTOLOGY_BACKFILL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1126
1154
|
var MaintenanceService = class {
|
|
1127
1155
|
constructor(db, prefix, options, entryRepo, taskRepo, eventRepo, metadataRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
|
|
1128
1156
|
this.db = db;
|
|
@@ -1228,6 +1256,14 @@ var MaintenanceService = class {
|
|
|
1228
1256
|
this.jobManager.releaseLock("heal", entityId);
|
|
1229
1257
|
}
|
|
1230
1258
|
}
|
|
1259
|
+
async runOntologyBackfill(entityId, options) {
|
|
1260
|
+
this.jobManager.acquireLock("ontologyBackfill", entityId);
|
|
1261
|
+
try {
|
|
1262
|
+
return await this.doRunOntologyBackfill(entityId, options);
|
|
1263
|
+
} finally {
|
|
1264
|
+
this.jobManager.releaseLock("ontologyBackfill", entityId);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1231
1267
|
async runReembed(entityId, opts) {
|
|
1232
1268
|
const embedFn = this.options.llmProvider.embed;
|
|
1233
1269
|
if (!embedFn) return { embedded: 0, skipped: 0, failed: 0 };
|
|
@@ -1579,6 +1615,121 @@ var MaintenanceService = class {
|
|
|
1579
1615
|
}
|
|
1580
1616
|
this.searchService.evictCache(entityId);
|
|
1581
1617
|
}
|
|
1618
|
+
/** Core ontology backfill pass (locks handled by {@link runOntologyBackfill}). Package-internal orchestration hook. */
|
|
1619
|
+
async doRunOntologyBackfill(entityId, options) {
|
|
1620
|
+
const batchSize = options?.batchSize ?? ONTOLOGY_BACKFILL_BATCH_SIZE;
|
|
1621
|
+
if (!Number.isInteger(batchSize) || batchSize < 1) {
|
|
1622
|
+
throw new Error("Invalid batchSize: must be an integer >= 1");
|
|
1623
|
+
}
|
|
1624
|
+
const now = Date.now();
|
|
1625
|
+
const recheckCutoff = now - ONTOLOGY_BACKFILL_RECHECK_MS;
|
|
1626
|
+
const zeroed = { scanned: 0, typed: 0, failedValidation: 0, edgesAdded: 0 };
|
|
1627
|
+
const ontologyService = this.ontologyService;
|
|
1628
|
+
if (!ontologyService) {
|
|
1629
|
+
return { ...zeroed, remaining: 0, deferred: 0 };
|
|
1630
|
+
}
|
|
1631
|
+
const { mode } = await ontologyService.getEffectiveState(entityId);
|
|
1632
|
+
if (mode === "off") {
|
|
1633
|
+
const counts2 = await this.entryRepo.countUntypedByEntityId(entityId, recheckCutoff);
|
|
1634
|
+
return { ...zeroed, remaining: 0, deferred: counts2.deferred };
|
|
1635
|
+
}
|
|
1636
|
+
const candidates = await this.entryRepo.findUntypedByEntityId(entityId, batchSize, recheckCutoff);
|
|
1637
|
+
if (candidates.length === 0) {
|
|
1638
|
+
const counts2 = await this.entryRepo.countUntypedByEntityId(entityId, recheckCutoff);
|
|
1639
|
+
return { ...zeroed, remaining: counts2.eligible, deferred: counts2.deferred };
|
|
1640
|
+
}
|
|
1641
|
+
const ontologyContext = await ontologyService.buildPromptContext(entityId);
|
|
1642
|
+
const toPromptShape = (f) => ({ id: f.id, title: f.title, body: f.body, tags: f.tags });
|
|
1643
|
+
const buildPrompt = (facts) => this.promptService.buildOntologyBackfillPrompt(
|
|
1644
|
+
facts.map(toPromptShape),
|
|
1645
|
+
options?.promptOverride,
|
|
1646
|
+
ontologyContext
|
|
1647
|
+
);
|
|
1648
|
+
const batch = [candidates[0]];
|
|
1649
|
+
let built = buildPrompt(batch);
|
|
1650
|
+
for (let i = 1; i < candidates.length; i++) {
|
|
1651
|
+
const next = buildPrompt([...batch, candidates[i]]);
|
|
1652
|
+
if (next.systemPrompt.length + next.userPrompt.length > ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS) break;
|
|
1653
|
+
batch.push(candidates[i]);
|
|
1654
|
+
built = next;
|
|
1655
|
+
}
|
|
1656
|
+
const { systemPrompt, userPrompt } = built;
|
|
1657
|
+
const responseText = await this.options.llmProvider.generateText({ systemPrompt, userPrompt });
|
|
1658
|
+
const parsed = parseJsonResponse(responseText);
|
|
1659
|
+
const classifications = Array.isArray(parsed.classifications) ? parsed.classifications : [];
|
|
1660
|
+
let typed = 0;
|
|
1661
|
+
let failedValidation = 0;
|
|
1662
|
+
let edgesAdded = 0;
|
|
1663
|
+
let abortedOntologyOff = false;
|
|
1664
|
+
await this.db.withTransactionAsync(async (tx) => {
|
|
1665
|
+
let { mode: txMode, manifest } = await ontologyService.getEffectiveState(entityId, tx);
|
|
1666
|
+
if (txMode === "off") {
|
|
1667
|
+
abortedOntologyOff = true;
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
if (txMode === "emergent" && parsed.ontology_updates) {
|
|
1671
|
+
manifest = await ontologyService.mergeEmergentUpdates(entityId, parsed.ontology_updates, tx);
|
|
1672
|
+
}
|
|
1673
|
+
const titleRows = await this.entryRepo.findTitleIndexByEntityId(entityId, tx);
|
|
1674
|
+
const titleIndex = /* @__PURE__ */ new Map();
|
|
1675
|
+
for (const row of titleRows) {
|
|
1676
|
+
titleIndex.set(normalizeTitleKey(row.title), { id: row.id, okf_type: row.okf_type });
|
|
1677
|
+
}
|
|
1678
|
+
const batchById = new Map(batch.map((f) => [f.id, f]));
|
|
1679
|
+
const applied = /* @__PURE__ */ new Set();
|
|
1680
|
+
const pendingEdges = [];
|
|
1681
|
+
for (const classification of classifications) {
|
|
1682
|
+
const fact = typeof classification.id === "string" ? batchById.get(classification.id) : void 0;
|
|
1683
|
+
if (!fact || applied.has(fact.id)) {
|
|
1684
|
+
failedValidation++;
|
|
1685
|
+
continue;
|
|
1686
|
+
}
|
|
1687
|
+
applied.add(fact.id);
|
|
1688
|
+
const normalized = ontologyService.validateAndNormalizeFact(
|
|
1689
|
+
{
|
|
1690
|
+
title: fact.title,
|
|
1691
|
+
body: fact.body,
|
|
1692
|
+
tags: fact.tags,
|
|
1693
|
+
confidence: fact.confidence,
|
|
1694
|
+
okf_type: classification.okf_type,
|
|
1695
|
+
edges: classification.edges
|
|
1696
|
+
},
|
|
1697
|
+
manifest
|
|
1698
|
+
);
|
|
1699
|
+
if (!normalized.okf_type) {
|
|
1700
|
+
failedValidation++;
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
const result = await this.entryRepo.updateOkfType(fact.id, entityId, normalized.okf_type, tx);
|
|
1704
|
+
if (result.changes === 0) continue;
|
|
1705
|
+
typed++;
|
|
1706
|
+
titleIndex.set(normalizeTitleKey(fact.title), { id: fact.id, okf_type: normalized.okf_type });
|
|
1707
|
+
if (normalized.edges.length > 0) {
|
|
1708
|
+
pendingEdges.push({ sourceId: fact.id, sourceType: normalized.okf_type, edges: normalized.edges });
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
for (const item of pendingEdges) {
|
|
1712
|
+
edgesAdded += await ontologyService.resolveAndPersistEdges(
|
|
1713
|
+
entityId,
|
|
1714
|
+
item.sourceId,
|
|
1715
|
+
item.sourceType,
|
|
1716
|
+
item.edges,
|
|
1717
|
+
manifest,
|
|
1718
|
+
titleIndex,
|
|
1719
|
+
tx,
|
|
1720
|
+
now
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
await this.entryRepo.markOntologyChecked(batch.map((f) => f.id), entityId, now, tx);
|
|
1724
|
+
});
|
|
1725
|
+
if (abortedOntologyOff) {
|
|
1726
|
+
const counts2 = await this.entryRepo.countUntypedByEntityId(entityId, recheckCutoff);
|
|
1727
|
+
return { ...zeroed, remaining: 0, deferred: counts2.deferred };
|
|
1728
|
+
}
|
|
1729
|
+
this.searchService.evictCache(entityId);
|
|
1730
|
+
const counts = await this.entryRepo.countUntypedByEntityId(entityId, recheckCutoff);
|
|
1731
|
+
return { scanned: batch.length, typed, failedValidation, edgesAdded, remaining: counts.eligible, deferred: counts.deferred };
|
|
1732
|
+
}
|
|
1582
1733
|
_validatePruneDuration(value, name) {
|
|
1583
1734
|
if (value !== null && value !== void 0 && (typeof value !== "number" || !isFinite(value) || value < 0)) {
|
|
1584
1735
|
throw new Error(`Invalid ${name}: must be a non-negative finite number or null`);
|
|
@@ -2360,7 +2511,8 @@ var JobManager = class {
|
|
|
2360
2511
|
heal: (id) => this._healKey(id),
|
|
2361
2512
|
reembed: (id) => this._reembedKey(id),
|
|
2362
2513
|
import: (id) => this._importKey(id),
|
|
2363
|
-
forget: (id) => this._forgetKey(id)
|
|
2514
|
+
forget: (id) => this._forgetKey(id),
|
|
2515
|
+
ontologyBackfill: (id) => this._ontologyBackfillKey(id)
|
|
2364
2516
|
};
|
|
2365
2517
|
}
|
|
2366
2518
|
_pruneKey(entityId) {
|
|
@@ -2387,6 +2539,9 @@ var JobManager = class {
|
|
|
2387
2539
|
_healKey(entityId) {
|
|
2388
2540
|
return `${this.prefix}:${entityId}:heal`;
|
|
2389
2541
|
}
|
|
2542
|
+
_ontologyBackfillKey(entityId) {
|
|
2543
|
+
return `${this.prefix}:${entityId}:ontologyBackfill`;
|
|
2544
|
+
}
|
|
2390
2545
|
_isReembedActive(entityId) {
|
|
2391
2546
|
return this.activeMaintenanceJobs.has(this._reembedKey(entityId)) || this.activeMaintenanceJobs.has(this._globalReembedKey());
|
|
2392
2547
|
}
|
|
@@ -2437,14 +2592,16 @@ var JobManager = class {
|
|
|
2437
2592
|
if (this.activeMaintenanceJobs.has(this._pruneKey(entityId))) blockingOperation = "prune";
|
|
2438
2593
|
else if (this.activeMaintenanceJobs.has(this._librarianKey(entityId))) blockingOperation = "librarian";
|
|
2439
2594
|
else if (this.activeMaintenanceJobs.has(this._healKey(entityId))) blockingOperation = "heal";
|
|
2595
|
+
else if (this.activeMaintenanceJobs.has(this._ontologyBackfillKey(entityId))) blockingOperation = "ontologyBackfill";
|
|
2440
2596
|
else if (this._isReembedActive(entityId)) blockingOperation = "reembed";
|
|
2441
2597
|
else if (this._isIngestActiveFor(entityId)) blockingOperation = "ingest";
|
|
2442
2598
|
else if (this._isImportActiveFor(entityId)) blockingOperation = "import";
|
|
2443
2599
|
else if (this._isForgetActiveFor(entityId)) blockingOperation = "forget";
|
|
2444
2600
|
break;
|
|
2445
2601
|
case "librarian":
|
|
2446
|
-
case "heal":
|
|
2447
|
-
|
|
2602
|
+
case "heal":
|
|
2603
|
+
case "ontologyBackfill": {
|
|
2604
|
+
const opKey = this.lockKeyFns[operation](entityId);
|
|
2448
2605
|
if (this.activeMaintenanceJobs.has(opKey)) blockingOperation = operation;
|
|
2449
2606
|
else if (this.activeMaintenanceJobs.has(this._pruneKey(entityId))) blockingOperation = "prune";
|
|
2450
2607
|
else if (this._isReembedActive(entityId)) blockingOperation = "reembed";
|
|
@@ -2458,6 +2615,7 @@ var JobManager = class {
|
|
|
2458
2615
|
else if (this.activeMaintenanceJobs.has(this._pruneKey(entityId))) blockingOperation = "prune";
|
|
2459
2616
|
else if (this.activeMaintenanceJobs.has(this._librarianKey(entityId))) blockingOperation = "librarian";
|
|
2460
2617
|
else if (this.activeMaintenanceJobs.has(this._healKey(entityId))) blockingOperation = "heal";
|
|
2618
|
+
else if (this.activeMaintenanceJobs.has(this._ontologyBackfillKey(entityId))) blockingOperation = "ontologyBackfill";
|
|
2461
2619
|
else if (this._isIngestActiveFor(entityId)) blockingOperation = "ingest";
|
|
2462
2620
|
else if (this._isImportActiveFor(entityId)) blockingOperation = "import";
|
|
2463
2621
|
else if (this._isForgetActiveFor(entityId)) blockingOperation = "forget";
|
|
@@ -2468,6 +2626,7 @@ var JobManager = class {
|
|
|
2468
2626
|
else if (this._isAnyMaintenanceActiveWithSuffix(":prune")) blockingOperation = "prune";
|
|
2469
2627
|
else if (this._isAnyMaintenanceActiveWithSuffix(":librarian")) blockingOperation = "librarian";
|
|
2470
2628
|
else if (this._isAnyMaintenanceActiveWithSuffix(":heal")) blockingOperation = "heal";
|
|
2629
|
+
else if (this._isAnyMaintenanceActiveWithSuffix(":ontologyBackfill")) blockingOperation = "ontologyBackfill";
|
|
2471
2630
|
else if (this.activeIngestJobs.size > 0) blockingOperation = "ingest";
|
|
2472
2631
|
else if (this._isAnyMaintenanceActiveWithSuffix(":import")) blockingOperation = "import";
|
|
2473
2632
|
else if (this._isAnyMaintenanceActiveWithSuffix(":forget")) blockingOperation = "forget";
|
|
@@ -2478,6 +2637,7 @@ var JobManager = class {
|
|
|
2478
2637
|
if (this.activeMaintenanceJobs.has(selfKey)) blockingOperation = operation;
|
|
2479
2638
|
else if (this.activeMaintenanceJobs.has(this._librarianKey(entityId))) blockingOperation = "librarian";
|
|
2480
2639
|
else if (this.activeMaintenanceJobs.has(this._healKey(entityId))) blockingOperation = "heal";
|
|
2640
|
+
else if (this.activeMaintenanceJobs.has(this._ontologyBackfillKey(entityId))) blockingOperation = "ontologyBackfill";
|
|
2481
2641
|
else if (this.activeMaintenanceJobs.has(this._pruneKey(entityId))) blockingOperation = "prune";
|
|
2482
2642
|
else if (this._isReembedActive(entityId)) blockingOperation = "reembed";
|
|
2483
2643
|
else if (this._isIngestActiveFor(entityId)) blockingOperation = "ingest";
|
|
@@ -2535,11 +2695,11 @@ var JobManager = class {
|
|
|
2535
2695
|
if (operation !== "global_import" && this.activeMaintenanceJobs.has(this._globalImportKey())) return true;
|
|
2536
2696
|
switch (operation) {
|
|
2537
2697
|
case "librarian":
|
|
2538
|
-
return this.activeMaintenanceJobs.has(this._librarianKey(entityId)) || this.activeMaintenanceJobs.has(this._pruneKey(entityId)) || this._isReembedActive(entityId) || this._isImportActiveFor(entityId) || this._isForgetActiveFor(entityId);
|
|
2539
2698
|
case "heal":
|
|
2540
|
-
|
|
2699
|
+
case "ontologyBackfill":
|
|
2700
|
+
return this.activeMaintenanceJobs.has(this.lockKeyFns[operation](entityId)) || this.activeMaintenanceJobs.has(this._pruneKey(entityId)) || this._isReembedActive(entityId) || this._isImportActiveFor(entityId) || this._isForgetActiveFor(entityId);
|
|
2541
2701
|
case "prune":
|
|
2542
|
-
return this.activeMaintenanceJobs.has(this._pruneKey(entityId)) || this.activeMaintenanceJobs.has(this._librarianKey(entityId)) || this.activeMaintenanceJobs.has(this._healKey(entityId)) || this._isReembedActive(entityId) || this._isIngestActiveFor(entityId) || this._isImportActiveFor(entityId) || this._isForgetActiveFor(entityId);
|
|
2702
|
+
return this.activeMaintenanceJobs.has(this._pruneKey(entityId)) || this.activeMaintenanceJobs.has(this._librarianKey(entityId)) || this.activeMaintenanceJobs.has(this._healKey(entityId)) || this.activeMaintenanceJobs.has(this._ontologyBackfillKey(entityId)) || this._isReembedActive(entityId) || this._isIngestActiveFor(entityId) || this._isImportActiveFor(entityId) || this._isForgetActiveFor(entityId);
|
|
2543
2703
|
default:
|
|
2544
2704
|
return false;
|
|
2545
2705
|
}
|
|
@@ -2564,6 +2724,7 @@ var JobManager = class {
|
|
|
2564
2724
|
if (this.activeMaintenanceJobs.has(this._importKey(entityId))) throw new WikiBusyError("import", entityId);
|
|
2565
2725
|
if (this.activeMaintenanceJobs.has(this._librarianKey(entityId))) throw new WikiBusyError("librarian", entityId);
|
|
2566
2726
|
if (this.activeMaintenanceJobs.has(this._healKey(entityId))) throw new WikiBusyError("heal", entityId);
|
|
2727
|
+
if (this.activeMaintenanceJobs.has(this._ontologyBackfillKey(entityId))) throw new WikiBusyError("ontologyBackfill", entityId);
|
|
2567
2728
|
if (this.activeMaintenanceJobs.has(this._pruneKey(entityId))) throw new WikiBusyError("prune", entityId);
|
|
2568
2729
|
if (this._isReembedActive(entityId)) throw new WikiBusyError("reembed", entityId);
|
|
2569
2730
|
if (this._isIngestActiveFor(entityId)) throw new WikiBusyError("ingest", entityId);
|