@equationalapplications/core-llm-wiki 6.4.0 → 6.5.1
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 +15 -0
- package/dist/{chunk-DPFM33WV.mjs → chunk-YNFYSLR6.mjs} +110 -22
- package/dist/chunk-YNFYSLR6.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +192 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +86 -4
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-DWpAgNLt.d.mts → testing-BtsN1pTK.d.mts} +123 -15
- package/dist/{testing-DWpAgNLt.d.ts → testing-BtsN1pTK.d.ts} +123 -15
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +108 -20
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-DPFM33WV.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -454,6 +454,21 @@ wikiMemory.clearVectorCache();
|
|
|
454
454
|
|
|
455
455
|
The cache is also automatically invalidated on any mutation (`runLibrarian`, `runHeal`, `runPrune`, `runReembed`, `ingestDocument`, `importDump`, `forget`).
|
|
456
456
|
|
|
457
|
+
## Re-Embedding & Retry Behavior
|
|
458
|
+
|
|
459
|
+
`runReembed(entityId?, opts?)` returns `{ embedded, skipped, failed, deferred, permanentlyFailed }`.
|
|
460
|
+
|
|
461
|
+
- `failed` — attempted this sweep and failed.
|
|
462
|
+
- `deferred` — previously failed and still inside its exponential backoff window (60s doubling, capped at 24h). Not an error; a later sweep will retry.
|
|
463
|
+
- `permanentlyFailed` — excluded for good: a `float32_overflow` failure, or 5 failed attempts. Pass `{ force: true }` to retry these anyway.
|
|
464
|
+
|
|
465
|
+
Convergence loops should test `failed`, never `deferred` — deferred rows clear themselves once their backoff elapses.
|
|
466
|
+
|
|
467
|
+
Two caveats on that guidance:
|
|
468
|
+
|
|
469
|
+
- A `storage_error` failure counts in `failed` but is never marked for backoff — a failure marker is itself a DB write, which is exactly what is broken. While DB writes fail, every sweep re-attempts every row, so a loop testing `failed` can spin without converging. Treat repeated `storage_error` failures as an infrastructure signal to fix, not a retry window to wait out.
|
|
470
|
+
- `{ force: true }` bypasses more than `permanentlyFailed`: it also skips the backoff window, so rows that would otherwise be `deferred` are re-attempted immediately.
|
|
471
|
+
|
|
457
472
|
## Entity Status
|
|
458
473
|
|
|
459
474
|
`WikiMemory` exposes the in-flight job state for a single entity through two complementary APIs.
|
|
@@ -2514,6 +2514,23 @@ var HEAL_ANCHOR_SEARCH_OVERFETCH = 4;
|
|
|
2514
2514
|
var HEAL_MAX_PROMPT_CHARS = 4e4;
|
|
2515
2515
|
var HEAL_BATCH_SIZE = 25;
|
|
2516
2516
|
var HEAL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2517
|
+
var MAX_EMBED_ATTEMPTS = 5;
|
|
2518
|
+
var EMBED_RETRY_BASE_MS = 6e4;
|
|
2519
|
+
var EMBED_RETRY_CAP_MS = 24 * 60 * 60 * 1e3;
|
|
2520
|
+
function embedRetryDelayMs(attempts) {
|
|
2521
|
+
const n = Math.max(1, Math.trunc(attempts || 0));
|
|
2522
|
+
const delay = EMBED_RETRY_BASE_MS * Math.pow(2, n - 1);
|
|
2523
|
+
return Math.min(delay, EMBED_RETRY_CAP_MS);
|
|
2524
|
+
}
|
|
2525
|
+
function classifyReembedRow(row, now, force) {
|
|
2526
|
+
if (force) return "attempt";
|
|
2527
|
+
const failedAt = row.embedding_failed_at;
|
|
2528
|
+
if (failedAt === null || failedAt === void 0) return "attempt";
|
|
2529
|
+
if (row.embedding_failure_kind === "float32_overflow") return "permanent";
|
|
2530
|
+
const attempts = row.embedding_attempts ?? 0;
|
|
2531
|
+
if (attempts >= MAX_EMBED_ATTEMPTS) return "permanent";
|
|
2532
|
+
return now - failedAt >= embedRetryDelayMs(attempts) ? "attempt" : "defer";
|
|
2533
|
+
}
|
|
2517
2534
|
var SKIP_ERROR_LOG_CHARS = 4096;
|
|
2518
2535
|
var formatSkipError = (err) => {
|
|
2519
2536
|
let base;
|
|
@@ -2652,9 +2669,24 @@ var MaintenanceService = class {
|
|
|
2652
2669
|
this.jobManager.releaseLock("ontologyBackfill", entityId);
|
|
2653
2670
|
}
|
|
2654
2671
|
}
|
|
2672
|
+
/**
|
|
2673
|
+
* Re-embed facts, honouring per-row failure markers and exponential backoff.
|
|
2674
|
+
*
|
|
2675
|
+
* Marker lifecycle (spec §2): markers are cleared by a successful embed, by
|
|
2676
|
+
* an upsert carrying a valid blob, and by an embedding-dimension promotion.
|
|
2677
|
+
* A promotion revives rows for the NEXT sweep, not this one — reconciliation
|
|
2678
|
+
* runs after candidates were already classified.
|
|
2679
|
+
*
|
|
2680
|
+
* Residual, by design: swapping to a provider with the SAME dimension never
|
|
2681
|
+
* sets embedding_dimension_mismatch, so promotion never fires and markers
|
|
2682
|
+
* survive the swap. Use `runReembed({ force: true })` to clear that state —
|
|
2683
|
+
* `force` bypasses classification entirely and retries every row.
|
|
2684
|
+
*/
|
|
2655
2685
|
async runReembed(entityId, opts) {
|
|
2656
2686
|
const embedFn = this.options.llmProvider.embed;
|
|
2657
|
-
if (
|
|
2687
|
+
if (typeof embedFn !== "function") {
|
|
2688
|
+
return { embedded: 0, skipped: 0, failed: 0, deferred: 0, permanentlyFailed: 0 };
|
|
2689
|
+
}
|
|
2658
2690
|
const op = entityId ? "reembed" : "global_reembed";
|
|
2659
2691
|
this.jobManager.acquireLock(op, entityId ?? "*");
|
|
2660
2692
|
try {
|
|
@@ -2677,6 +2709,10 @@ var MaintenanceService = class {
|
|
|
2677
2709
|
let embedded = 0;
|
|
2678
2710
|
let skipped = 0;
|
|
2679
2711
|
let failed = 0;
|
|
2712
|
+
let deferred = 0;
|
|
2713
|
+
let permanentlyFailed = 0;
|
|
2714
|
+
const force = opts?.force ?? false;
|
|
2715
|
+
const now = Date.now();
|
|
2680
2716
|
try {
|
|
2681
2717
|
for (const row of rows) {
|
|
2682
2718
|
const existingBlob = row.embedding_blob;
|
|
@@ -2688,8 +2724,17 @@ var MaintenanceService = class {
|
|
|
2688
2724
|
continue;
|
|
2689
2725
|
}
|
|
2690
2726
|
}
|
|
2691
|
-
const
|
|
2692
|
-
if (
|
|
2727
|
+
const disposition = classifyReembedRow(row, now, force);
|
|
2728
|
+
if (disposition === "defer") {
|
|
2729
|
+
deferred++;
|
|
2730
|
+
continue;
|
|
2731
|
+
}
|
|
2732
|
+
if (disposition === "permanent") {
|
|
2733
|
+
permanentlyFailed++;
|
|
2734
|
+
continue;
|
|
2735
|
+
}
|
|
2736
|
+
const result = await this.embeddingService.tryEmbedFact(row);
|
|
2737
|
+
if (result.ok) embedded++;
|
|
2693
2738
|
else failed++;
|
|
2694
2739
|
}
|
|
2695
2740
|
if (embedded > 0) {
|
|
@@ -2698,7 +2743,7 @@ var MaintenanceService = class {
|
|
|
2698
2743
|
} finally {
|
|
2699
2744
|
this.searchService.evictCache(entityId);
|
|
2700
2745
|
}
|
|
2701
|
-
return { embedded, skipped, failed };
|
|
2746
|
+
return { embedded, skipped, failed, deferred, permanentlyFailed };
|
|
2702
2747
|
} finally {
|
|
2703
2748
|
this.jobManager.releaseLock(op, entityId ?? "*");
|
|
2704
2749
|
}
|
|
@@ -3876,20 +3921,37 @@ var EmbeddingService = class {
|
|
|
3876
3921
|
await this.metadataRepo.setMeta("embedding_dimension", String(dim), this.db);
|
|
3877
3922
|
}
|
|
3878
3923
|
}
|
|
3879
|
-
/**
|
|
3924
|
+
/**
|
|
3925
|
+
* Promotes embedding_dimension_mismatch to canonical embedding_dimension when
|
|
3926
|
+
* safe, and clears embedding failure markers as part of the same event.
|
|
3927
|
+
*
|
|
3928
|
+
* Marker reset (spec §2.3): a promoted dimension means a different model is
|
|
3929
|
+
* producing the vectors, so prior failures no longer predict future ones.
|
|
3930
|
+
* `float32_overflow` is cleared too — it is terminal only because retrying is
|
|
3931
|
+
* the same arithmetic on the same vector, and after a model change it is not.
|
|
3932
|
+
*
|
|
3933
|
+
* All three writes commit atomically; see the transaction note inline.
|
|
3934
|
+
*
|
|
3935
|
+
* Revived rows are NOT embedded here. They become eligible and are picked up
|
|
3936
|
+
* by the NEXT sweep, because runReembed calls this after its candidates were
|
|
3937
|
+
* already classified; same-sweep revival would be re-entrant.
|
|
3938
|
+
*/
|
|
3880
3939
|
async reconcileEmbeddingDimension() {
|
|
3881
3940
|
const mismatchValue = await this.metadataRepo.getMeta("embedding_dimension_mismatch");
|
|
3882
3941
|
if (!mismatchValue) return;
|
|
3883
3942
|
const newDim = parseInt(mismatchValue, 10);
|
|
3884
3943
|
const residualCount = await this.entryRepo.countStaleEmbeddings(newDim);
|
|
3885
3944
|
if (residualCount === 0) {
|
|
3886
|
-
await this.
|
|
3887
|
-
|
|
3945
|
+
await this.db.withTransactionAsync(async (tx) => {
|
|
3946
|
+
await this.metadataRepo.setMeta("embedding_dimension", mismatchValue, tx);
|
|
3947
|
+
await this.metadataRepo.clearDimensionMismatch(tx);
|
|
3948
|
+
await this.entryRepo.clearEmbeddingFailureMarkers(tx);
|
|
3949
|
+
});
|
|
3888
3950
|
}
|
|
3889
3951
|
}
|
|
3890
|
-
async
|
|
3952
|
+
async tryEmbedFact(fact) {
|
|
3891
3953
|
const embedFn = this.options.llmProvider.embed;
|
|
3892
|
-
if (
|
|
3954
|
+
if (typeof embedFn !== "function") return { ok: false, kind: "no_provider" };
|
|
3893
3955
|
let tagsStr;
|
|
3894
3956
|
if (Array.isArray(fact.tags)) {
|
|
3895
3957
|
tagsStr = fact.tags.join(" ");
|
|
@@ -3904,13 +3966,15 @@ var EmbeddingService = class {
|
|
|
3904
3966
|
const configuredMaxEmbedChars = this.options.config?.maxEmbedChars;
|
|
3905
3967
|
const maxEmbedChars = Number.isFinite(configuredMaxEmbedChars) ? Math.min(Math.max(0, Math.trunc(configuredMaxEmbedChars)), EMBED_CHARS_CEILING) : DEFAULT_MAX_EMBED_CHARS;
|
|
3906
3968
|
const text = clip(`${fact.title} ${fact.body} ${tagsStr}`.trim(), maxEmbedChars);
|
|
3969
|
+
let float32Vector;
|
|
3907
3970
|
try {
|
|
3908
3971
|
const vector = await embedFn(text);
|
|
3909
3972
|
if (vector.length === 0 || !vector.every((v) => typeof v === "number" && isFinite(v))) {
|
|
3910
3973
|
console.warn(`[WikiMemory] embedFact: embed() returned an invalid vector for ${fact.id}; skipping.`);
|
|
3911
|
-
|
|
3974
|
+
await this.markFailure(fact.id, "invalid_vector");
|
|
3975
|
+
return { ok: false, kind: "invalid_vector" };
|
|
3912
3976
|
}
|
|
3913
|
-
|
|
3977
|
+
float32Vector = new Float32Array(vector);
|
|
3914
3978
|
let hasNonFinite = false;
|
|
3915
3979
|
for (let i = 0; i < float32Vector.length; i++) {
|
|
3916
3980
|
if (!isFinite(float32Vector[i])) {
|
|
@@ -3920,21 +3984,45 @@ var EmbeddingService = class {
|
|
|
3920
3984
|
}
|
|
3921
3985
|
if (hasNonFinite) {
|
|
3922
3986
|
console.warn(`[WikiMemory] embedFact: embed() returned values that overflow float32 for ${fact.id}; skipping.`);
|
|
3923
|
-
|
|
3987
|
+
await this.markFailure(fact.id, "float32_overflow");
|
|
3988
|
+
return { ok: false, kind: "float32_overflow" };
|
|
3924
3989
|
}
|
|
3990
|
+
} catch (err) {
|
|
3991
|
+
console.warn(`[WikiMemory] embedFact failed for ${fact.id}:`, err);
|
|
3992
|
+
await this.markFailure(fact.id, "provider_error");
|
|
3993
|
+
return { ok: false, kind: "provider_error" };
|
|
3994
|
+
}
|
|
3995
|
+
try {
|
|
3925
3996
|
await this.storeEmbeddingDimension(float32Vector.length);
|
|
3926
3997
|
const blob = new Uint8Array(float32Vector.buffer);
|
|
3927
3998
|
await this.entryRepo.updateEmbeddingBlob(fact.id, blob);
|
|
3928
|
-
try {
|
|
3929
|
-
await this.notifyEmbeddingPersisted(fact.entity_id, fact.id, float32Vector);
|
|
3930
|
-
} catch (hookErr) {
|
|
3931
|
-
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed for ${fact.id}:`, hookErr);
|
|
3932
|
-
}
|
|
3933
|
-
return true;
|
|
3934
3999
|
} catch (err) {
|
|
3935
|
-
console.warn(`[WikiMemory] embedFact failed for ${fact.id}:`, err);
|
|
3936
|
-
return false;
|
|
4000
|
+
console.warn(`[WikiMemory] embedFact: persisting embedding failed for ${fact.id}:`, err);
|
|
4001
|
+
return { ok: false, kind: "storage_error" };
|
|
3937
4002
|
}
|
|
4003
|
+
try {
|
|
4004
|
+
await this.notifyEmbeddingPersisted(fact.entity_id, fact.id, float32Vector);
|
|
4005
|
+
} catch (hookErr) {
|
|
4006
|
+
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed for ${fact.id}:`, hookErr);
|
|
4007
|
+
}
|
|
4008
|
+
return { ok: true, dimension: float32Vector.length };
|
|
4009
|
+
}
|
|
4010
|
+
/** Marker writes must never fail the caller. Only marker-eligible kinds reach here. */
|
|
4011
|
+
async markFailure(id, kind) {
|
|
4012
|
+
try {
|
|
4013
|
+
await this.entryRepo.markEmbeddingFailure(id, kind, Date.now());
|
|
4014
|
+
} catch (err) {
|
|
4015
|
+
console.warn(`[WikiMemory] failed to record embedding failure for ${id}:`, err);
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
/**
|
|
4019
|
+
* Back-compatible boolean form. Kept because callers such as
|
|
4020
|
+
* ImportExportService branch on `if (!embedded)`; a discriminated result
|
|
4021
|
+
* object is always truthy and would silently disable that branch.
|
|
4022
|
+
*/
|
|
4023
|
+
async embedFact(fact) {
|
|
4024
|
+
const result = await this.tryEmbedFact(fact);
|
|
4025
|
+
return result.ok;
|
|
3938
4026
|
}
|
|
3939
4027
|
async notifyEmbeddingPersisted(entityId, factId, vector) {
|
|
3940
4028
|
if (!this.options.vectorRanker?.onEmbeddingPersisted) return;
|
|
@@ -4779,5 +4867,5 @@ var WriteService = class {
|
|
|
4779
4867
|
};
|
|
4780
4868
|
|
|
4781
4869
|
export { BaseRepository, DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, DEFAULT_MAX_EMBED_CHARS, EMBED_CHARS_CEILING, EmbeddingService, HEAL_ANCHORS_PER_CANDIDATE, HEAL_BATCH_SIZE, HEAL_MAX_FACT_BODY_CHARS_L3, HEAL_MAX_TASKS, HEAL_RECHECK_MS, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, MetadataRepository, ONTOLOGY_BACKFILL_BATCH_SIZE, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, ONTOLOGY_BACKFILL_RECHECK_MS, ONTOLOGY_BACKFILL_SYSTEM_PROMPT, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WikiDuplicateHashError, WikiIngestEmptyError, WikiInvalidReadOptions, WikiParseError, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, chunkText, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveNodeType, safeSlice, typeSatisfies, validateInlineEdges, validateManifest };
|
|
4782
|
-
//# sourceMappingURL=chunk-
|
|
4783
|
-
//# sourceMappingURL=chunk-
|
|
4870
|
+
//# sourceMappingURL=chunk-YNFYSLR6.mjs.map
|
|
4871
|
+
//# sourceMappingURL=chunk-YNFYSLR6.mjs.map
|