@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/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-
|
|
2
|
-
export { C as ChunkFailure, D as DegradedRecord, E as
|
|
1
|
+
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-BtsN1pTK.mjs';
|
|
2
|
+
export { C as ChunkFailure, D as DegradedRecord, E as EmbedFactResult, d as EmbedFailureKind, e as EmbeddingMarkerKind, f as EntityStatus, g as ExtractedFact, h as ExtractedFactEdge, i as ExtractedFactWithOntology, j as ExtractedTask, k as GraphTraversalOptions, H as HEAL_BATCH_SIZE, l as HEAL_RECHECK_MS, m as HOOK_TIMEOUT_MARKER, n as HealResult, I as IngestDocumentResult, L as LLMProvider, o as ONTOLOGY_BACKFILL_BATCH_SIZE, p as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, q as ONTOLOGY_BACKFILL_RECHECK_MS, r as OntologyBackfillResult, s as OntologyConfig, t as OntologyEdgeType, u as OntologyMode, v as OntologyNodeType, w as OntologyPromptContext, x as OntologyUpdates, P as PromptOverrides, y as PromptService, z as PrunePartialFailureError, A as ReembedResult, V as VectorRanker, B as VectorRankerFallback, J as VectorRankerRankArgs, K as VectorRankerSemanticResult, N as WikiBusyError, Q as WikiBusyOperation, T as WikiCheckpoint, U as WikiConfig, X as WikiDuplicateHashError, Y as WikiEdge, Z as WikiEvent, _ as WikiFact, $ as WikiIngestEmptyError, a0 as WikiInvalidReadOptions, a1 as WikiMemoryTestAccess, a2 as WikiOutboxEvent, a3 as WikiParseError, a4 as WikiSourceRefHashCollision, a5 as WikiStrictOntologyViolation, a6 as WikiTask, a7 as WikiTransactionError } from './testing-BtsN1pTK.mjs';
|
|
3
3
|
import { OkfFile } from '@equationalapplications/core-okf';
|
|
4
4
|
import 'minisearch';
|
|
5
5
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-
|
|
2
|
-
export { C as ChunkFailure, D as DegradedRecord, E as
|
|
1
|
+
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-BtsN1pTK.js';
|
|
2
|
+
export { C as ChunkFailure, D as DegradedRecord, E as EmbedFactResult, d as EmbedFailureKind, e as EmbeddingMarkerKind, f as EntityStatus, g as ExtractedFact, h as ExtractedFactEdge, i as ExtractedFactWithOntology, j as ExtractedTask, k as GraphTraversalOptions, H as HEAL_BATCH_SIZE, l as HEAL_RECHECK_MS, m as HOOK_TIMEOUT_MARKER, n as HealResult, I as IngestDocumentResult, L as LLMProvider, o as ONTOLOGY_BACKFILL_BATCH_SIZE, p as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, q as ONTOLOGY_BACKFILL_RECHECK_MS, r as OntologyBackfillResult, s as OntologyConfig, t as OntologyEdgeType, u as OntologyMode, v as OntologyNodeType, w as OntologyPromptContext, x as OntologyUpdates, P as PromptOverrides, y as PromptService, z as PrunePartialFailureError, A as ReembedResult, V as VectorRanker, B as VectorRankerFallback, J as VectorRankerRankArgs, K as VectorRankerSemanticResult, N as WikiBusyError, Q as WikiBusyOperation, T as WikiCheckpoint, U as WikiConfig, X as WikiDuplicateHashError, Y as WikiEdge, Z as WikiEvent, _ as WikiFact, $ as WikiIngestEmptyError, a0 as WikiInvalidReadOptions, a1 as WikiMemoryTestAccess, a2 as WikiOutboxEvent, a3 as WikiParseError, a4 as WikiSourceRefHashCollision, a5 as WikiStrictOntologyViolation, a6 as WikiTask, a7 as WikiTransactionError } from './testing-BtsN1pTK.js';
|
|
3
3
|
import { OkfFile } from '@equationalapplications/core-okf';
|
|
4
4
|
import 'minisearch';
|
|
5
5
|
|
package/dist/index.js
CHANGED
|
@@ -45,7 +45,10 @@ async function setupDatabase(db, prefix) {
|
|
|
45
45
|
last_verified_by TEXT,
|
|
46
46
|
okf_sources TEXT,
|
|
47
47
|
okf_verified TEXT,
|
|
48
|
-
okf_usage_window TEXT
|
|
48
|
+
okf_usage_window TEXT,
|
|
49
|
+
embedding_failed_at INTEGER,
|
|
50
|
+
embedding_failure_kind TEXT,
|
|
51
|
+
embedding_attempts INTEGER NOT NULL DEFAULT 0
|
|
49
52
|
);
|
|
50
53
|
|
|
51
54
|
CREATE INDEX IF NOT EXISTS ${prefix}entries_entity_idx ON ${prefix}entries(entity_id);
|
|
@@ -576,6 +579,24 @@ var MIGRATIONS = [
|
|
|
576
579
|
CREATE INDEX IF NOT EXISTS ${prefix}tasks_last_verified_at_idx ON ${prefix}tasks(last_verified_at);
|
|
577
580
|
`);
|
|
578
581
|
}
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
version: 11,
|
|
585
|
+
description: "Add embedding_failed_at, embedding_failure_kind, embedding_attempts to entries for embed retry state",
|
|
586
|
+
run: async (db, prefix) => {
|
|
587
|
+
const cols = await db.getAllAsync(
|
|
588
|
+
`PRAGMA table_info(${prefix}entries)`
|
|
589
|
+
);
|
|
590
|
+
const existing = new Set(cols.map((c) => c.name));
|
|
591
|
+
const adds = [
|
|
592
|
+
["embedding_failed_at", `ALTER TABLE ${prefix}entries ADD COLUMN embedding_failed_at INTEGER`],
|
|
593
|
+
["embedding_failure_kind", `ALTER TABLE ${prefix}entries ADD COLUMN embedding_failure_kind TEXT`],
|
|
594
|
+
["embedding_attempts", `ALTER TABLE ${prefix}entries ADD COLUMN embedding_attempts INTEGER NOT NULL DEFAULT 0`]
|
|
595
|
+
];
|
|
596
|
+
for (const [name, sql] of adds) {
|
|
597
|
+
if (!existing.has(name)) await db.execAsync(sql);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
579
600
|
}
|
|
580
601
|
];
|
|
581
602
|
for (let i = 1; i < MIGRATIONS.length; i++) {
|
|
@@ -736,6 +757,13 @@ var EntryRepository = class extends BaseRepository {
|
|
|
736
757
|
* New-row INSERTs still bind a value so the column is populated; if the
|
|
737
758
|
* caller supplied nothing, the schema DEFAULT 'stable' applies when the
|
|
738
759
|
* bind is bound as 'stable' explicitly here.
|
|
760
|
+
*
|
|
761
|
+
* Marker discipline (spec §4.1): a conflict carrying a real
|
|
762
|
+
* `embedding_blob` clears the embedding failure markers, because the row now
|
|
763
|
+
* HAS a valid embedding and a surviving marker would be a self-contradictory
|
|
764
|
+
* diagnostic that inflates runReembed's permanentlyFailed counter. A conflict
|
|
765
|
+
* with no blob leaves marker state alone, matching the "absent means don't
|
|
766
|
+
* touch" semantics used for embedding_blob itself.
|
|
739
767
|
*/
|
|
740
768
|
async upsert(fact, tx) {
|
|
741
769
|
const executor = this.getExecutor(tx);
|
|
@@ -773,6 +801,9 @@ var EntryRepository = class extends BaseRepository {
|
|
|
773
801
|
deleted_at = excluded.deleted_at,
|
|
774
802
|
embedding_blob = CASE WHEN excluded.embedding_blob IS NULL THEN embedding_blob ELSE excluded.embedding_blob END,
|
|
775
803
|
embedding = NULL,
|
|
804
|
+
embedding_failed_at = CASE WHEN excluded.embedding_blob IS NOT NULL THEN NULL ELSE embedding_failed_at END,
|
|
805
|
+
embedding_failure_kind = CASE WHEN excluded.embedding_blob IS NOT NULL THEN NULL ELSE embedding_failure_kind END,
|
|
806
|
+
embedding_attempts = CASE WHEN excluded.embedding_blob IS NOT NULL THEN 0 ELSE embedding_attempts END,
|
|
776
807
|
okf_type = excluded.okf_type`,
|
|
777
808
|
[
|
|
778
809
|
fact.id,
|
|
@@ -901,6 +932,9 @@ var EntryRepository = class extends BaseRepository {
|
|
|
901
932
|
deleted_at = excluded.deleted_at,
|
|
902
933
|
embedding_blob = excluded.embedding_blob,
|
|
903
934
|
embedding = NULL,
|
|
935
|
+
embedding_failed_at = CASE WHEN excluded.embedding_blob IS NOT NULL THEN NULL ELSE embedding_failed_at END,
|
|
936
|
+
embedding_failure_kind = CASE WHEN excluded.embedding_blob IS NOT NULL THEN NULL ELSE embedding_failure_kind END,
|
|
937
|
+
embedding_attempts = CASE WHEN excluded.embedding_blob IS NOT NULL THEN 0 ELSE embedding_attempts END,
|
|
904
938
|
okf_type = excluded.okf_type,
|
|
905
939
|
lifecycle_status = excluded.lifecycle_status,
|
|
906
940
|
stale_after = excluded.stale_after,
|
|
@@ -1337,10 +1371,58 @@ var EntryRepository = class extends BaseRepository {
|
|
|
1337
1371
|
async updateEmbeddingBlob(id, blob, tx) {
|
|
1338
1372
|
const executor = this.getExecutor(tx);
|
|
1339
1373
|
await executor.runAsync(
|
|
1340
|
-
`UPDATE ${this.prefix}entries
|
|
1374
|
+
`UPDATE ${this.prefix}entries
|
|
1375
|
+
SET embedding_blob = ?,
|
|
1376
|
+
embedding = NULL,
|
|
1377
|
+
embedding_failed_at = NULL,
|
|
1378
|
+
embedding_failure_kind = NULL,
|
|
1379
|
+
embedding_attempts = 0
|
|
1380
|
+
WHERE id = ?`,
|
|
1341
1381
|
[blob, id]
|
|
1342
1382
|
);
|
|
1343
1383
|
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Record a failed embedding attempt. Deliberately does NOT touch updated_at
|
|
1386
|
+
* (import merge is last-write-wins on it) and pushes no outbox event —
|
|
1387
|
+
* embedding lifecycle is local state, not replicated. Same discipline as
|
|
1388
|
+
* updateEmbeddingBlob. See spec §3.5.
|
|
1389
|
+
*
|
|
1390
|
+
* Only marker-eligible kinds (spec §3.3) are accepted: `no_provider` never
|
|
1391
|
+
* marks and `storage_error` never marks, so retry/permanent-failure policy
|
|
1392
|
+
* built on these rows only ever sees real embed failures.
|
|
1393
|
+
*/
|
|
1394
|
+
async markEmbeddingFailure(id, kind, now, tx) {
|
|
1395
|
+
const executor = this.getExecutor(tx);
|
|
1396
|
+
await executor.runAsync(
|
|
1397
|
+
`UPDATE ${this.prefix}entries
|
|
1398
|
+
SET embedding_failed_at = ?,
|
|
1399
|
+
embedding_failure_kind = ?,
|
|
1400
|
+
embedding_attempts = embedding_attempts + 1
|
|
1401
|
+
WHERE id = ?`,
|
|
1402
|
+
[now, kind, id]
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Clear marker state for every marked row. Called when the embedding
|
|
1407
|
+
* dimension is promoted: a new model produces different vectors, so past
|
|
1408
|
+
* failures — including `float32_overflow`, which is otherwise terminal —
|
|
1409
|
+
* no longer predict future ones (spec §2.3).
|
|
1410
|
+
*
|
|
1411
|
+
* Same discipline as markEmbeddingFailure: no `updated_at` touch and no
|
|
1412
|
+
* outbox event, because embedding lifecycle is local state, not replicated.
|
|
1413
|
+
* Returns the number of rows cleared.
|
|
1414
|
+
*/
|
|
1415
|
+
async clearEmbeddingFailureMarkers(tx) {
|
|
1416
|
+
const executor = this.getExecutor(tx);
|
|
1417
|
+
const result = await executor.runAsync(
|
|
1418
|
+
`UPDATE ${this.prefix}entries
|
|
1419
|
+
SET embedding_failed_at = NULL,
|
|
1420
|
+
embedding_failure_kind = NULL,
|
|
1421
|
+
embedding_attempts = 0
|
|
1422
|
+
WHERE embedding_failed_at IS NOT NULL`
|
|
1423
|
+
);
|
|
1424
|
+
return result.changes;
|
|
1425
|
+
}
|
|
1344
1426
|
async hasLegacySourceTypes(tx) {
|
|
1345
1427
|
const executor = this.getExecutor(tx);
|
|
1346
1428
|
const row = await executor.getFirstAsync(
|
|
@@ -4814,6 +4896,23 @@ var HEAL_ANCHOR_SEARCH_OVERFETCH = 4;
|
|
|
4814
4896
|
var HEAL_MAX_PROMPT_CHARS = 4e4;
|
|
4815
4897
|
var HEAL_BATCH_SIZE = 25;
|
|
4816
4898
|
var HEAL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
4899
|
+
var MAX_EMBED_ATTEMPTS = 5;
|
|
4900
|
+
var EMBED_RETRY_BASE_MS = 6e4;
|
|
4901
|
+
var EMBED_RETRY_CAP_MS = 24 * 60 * 60 * 1e3;
|
|
4902
|
+
function embedRetryDelayMs(attempts) {
|
|
4903
|
+
const n = Math.max(1, Math.trunc(attempts || 0));
|
|
4904
|
+
const delay = EMBED_RETRY_BASE_MS * Math.pow(2, n - 1);
|
|
4905
|
+
return Math.min(delay, EMBED_RETRY_CAP_MS);
|
|
4906
|
+
}
|
|
4907
|
+
function classifyReembedRow(row, now, force) {
|
|
4908
|
+
if (force) return "attempt";
|
|
4909
|
+
const failedAt = row.embedding_failed_at;
|
|
4910
|
+
if (failedAt === null || failedAt === void 0) return "attempt";
|
|
4911
|
+
if (row.embedding_failure_kind === "float32_overflow") return "permanent";
|
|
4912
|
+
const attempts = row.embedding_attempts ?? 0;
|
|
4913
|
+
if (attempts >= MAX_EMBED_ATTEMPTS) return "permanent";
|
|
4914
|
+
return now - failedAt >= embedRetryDelayMs(attempts) ? "attempt" : "defer";
|
|
4915
|
+
}
|
|
4817
4916
|
var SKIP_ERROR_LOG_CHARS = 4096;
|
|
4818
4917
|
var formatSkipError = (err) => {
|
|
4819
4918
|
let base;
|
|
@@ -4952,9 +5051,24 @@ var MaintenanceService = class {
|
|
|
4952
5051
|
this.jobManager.releaseLock("ontologyBackfill", entityId);
|
|
4953
5052
|
}
|
|
4954
5053
|
}
|
|
5054
|
+
/**
|
|
5055
|
+
* Re-embed facts, honouring per-row failure markers and exponential backoff.
|
|
5056
|
+
*
|
|
5057
|
+
* Marker lifecycle (spec §2): markers are cleared by a successful embed, by
|
|
5058
|
+
* an upsert carrying a valid blob, and by an embedding-dimension promotion.
|
|
5059
|
+
* A promotion revives rows for the NEXT sweep, not this one — reconciliation
|
|
5060
|
+
* runs after candidates were already classified.
|
|
5061
|
+
*
|
|
5062
|
+
* Residual, by design: swapping to a provider with the SAME dimension never
|
|
5063
|
+
* sets embedding_dimension_mismatch, so promotion never fires and markers
|
|
5064
|
+
* survive the swap. Use `runReembed({ force: true })` to clear that state —
|
|
5065
|
+
* `force` bypasses classification entirely and retries every row.
|
|
5066
|
+
*/
|
|
4955
5067
|
async runReembed(entityId, opts) {
|
|
4956
5068
|
const embedFn = this.options.llmProvider.embed;
|
|
4957
|
-
if (
|
|
5069
|
+
if (typeof embedFn !== "function") {
|
|
5070
|
+
return { embedded: 0, skipped: 0, failed: 0, deferred: 0, permanentlyFailed: 0 };
|
|
5071
|
+
}
|
|
4958
5072
|
const op = entityId ? "reembed" : "global_reembed";
|
|
4959
5073
|
this.jobManager.acquireLock(op, entityId ?? "*");
|
|
4960
5074
|
try {
|
|
@@ -4977,6 +5091,10 @@ var MaintenanceService = class {
|
|
|
4977
5091
|
let embedded = 0;
|
|
4978
5092
|
let skipped = 0;
|
|
4979
5093
|
let failed = 0;
|
|
5094
|
+
let deferred = 0;
|
|
5095
|
+
let permanentlyFailed = 0;
|
|
5096
|
+
const force = opts?.force ?? false;
|
|
5097
|
+
const now = Date.now();
|
|
4980
5098
|
try {
|
|
4981
5099
|
for (const row of rows) {
|
|
4982
5100
|
const existingBlob = row.embedding_blob;
|
|
@@ -4988,8 +5106,17 @@ var MaintenanceService = class {
|
|
|
4988
5106
|
continue;
|
|
4989
5107
|
}
|
|
4990
5108
|
}
|
|
4991
|
-
const
|
|
4992
|
-
if (
|
|
5109
|
+
const disposition = classifyReembedRow(row, now, force);
|
|
5110
|
+
if (disposition === "defer") {
|
|
5111
|
+
deferred++;
|
|
5112
|
+
continue;
|
|
5113
|
+
}
|
|
5114
|
+
if (disposition === "permanent") {
|
|
5115
|
+
permanentlyFailed++;
|
|
5116
|
+
continue;
|
|
5117
|
+
}
|
|
5118
|
+
const result = await this.embeddingService.tryEmbedFact(row);
|
|
5119
|
+
if (result.ok) embedded++;
|
|
4993
5120
|
else failed++;
|
|
4994
5121
|
}
|
|
4995
5122
|
if (embedded > 0) {
|
|
@@ -4998,7 +5125,7 @@ var MaintenanceService = class {
|
|
|
4998
5125
|
} finally {
|
|
4999
5126
|
this.searchService.evictCache(entityId);
|
|
5000
5127
|
}
|
|
5001
|
-
return { embedded, skipped, failed };
|
|
5128
|
+
return { embedded, skipped, failed, deferred, permanentlyFailed };
|
|
5002
5129
|
} finally {
|
|
5003
5130
|
this.jobManager.releaseLock(op, entityId ?? "*");
|
|
5004
5131
|
}
|
|
@@ -6176,20 +6303,37 @@ var EmbeddingService = class {
|
|
|
6176
6303
|
await this.metadataRepo.setMeta("embedding_dimension", String(dim), this.db);
|
|
6177
6304
|
}
|
|
6178
6305
|
}
|
|
6179
|
-
/**
|
|
6306
|
+
/**
|
|
6307
|
+
* Promotes embedding_dimension_mismatch to canonical embedding_dimension when
|
|
6308
|
+
* safe, and clears embedding failure markers as part of the same event.
|
|
6309
|
+
*
|
|
6310
|
+
* Marker reset (spec §2.3): a promoted dimension means a different model is
|
|
6311
|
+
* producing the vectors, so prior failures no longer predict future ones.
|
|
6312
|
+
* `float32_overflow` is cleared too — it is terminal only because retrying is
|
|
6313
|
+
* the same arithmetic on the same vector, and after a model change it is not.
|
|
6314
|
+
*
|
|
6315
|
+
* All three writes commit atomically; see the transaction note inline.
|
|
6316
|
+
*
|
|
6317
|
+
* Revived rows are NOT embedded here. They become eligible and are picked up
|
|
6318
|
+
* by the NEXT sweep, because runReembed calls this after its candidates were
|
|
6319
|
+
* already classified; same-sweep revival would be re-entrant.
|
|
6320
|
+
*/
|
|
6180
6321
|
async reconcileEmbeddingDimension() {
|
|
6181
6322
|
const mismatchValue = await this.metadataRepo.getMeta("embedding_dimension_mismatch");
|
|
6182
6323
|
if (!mismatchValue) return;
|
|
6183
6324
|
const newDim = parseInt(mismatchValue, 10);
|
|
6184
6325
|
const residualCount = await this.entryRepo.countStaleEmbeddings(newDim);
|
|
6185
6326
|
if (residualCount === 0) {
|
|
6186
|
-
await this.
|
|
6187
|
-
|
|
6327
|
+
await this.db.withTransactionAsync(async (tx) => {
|
|
6328
|
+
await this.metadataRepo.setMeta("embedding_dimension", mismatchValue, tx);
|
|
6329
|
+
await this.metadataRepo.clearDimensionMismatch(tx);
|
|
6330
|
+
await this.entryRepo.clearEmbeddingFailureMarkers(tx);
|
|
6331
|
+
});
|
|
6188
6332
|
}
|
|
6189
6333
|
}
|
|
6190
|
-
async
|
|
6334
|
+
async tryEmbedFact(fact) {
|
|
6191
6335
|
const embedFn = this.options.llmProvider.embed;
|
|
6192
|
-
if (
|
|
6336
|
+
if (typeof embedFn !== "function") return { ok: false, kind: "no_provider" };
|
|
6193
6337
|
let tagsStr;
|
|
6194
6338
|
if (Array.isArray(fact.tags)) {
|
|
6195
6339
|
tagsStr = fact.tags.join(" ");
|
|
@@ -6204,13 +6348,15 @@ var EmbeddingService = class {
|
|
|
6204
6348
|
const configuredMaxEmbedChars = this.options.config?.maxEmbedChars;
|
|
6205
6349
|
const maxEmbedChars = Number.isFinite(configuredMaxEmbedChars) ? Math.min(Math.max(0, Math.trunc(configuredMaxEmbedChars)), EMBED_CHARS_CEILING) : DEFAULT_MAX_EMBED_CHARS;
|
|
6206
6350
|
const text = clip(`${fact.title} ${fact.body} ${tagsStr}`.trim(), maxEmbedChars);
|
|
6351
|
+
let float32Vector;
|
|
6207
6352
|
try {
|
|
6208
6353
|
const vector = await embedFn(text);
|
|
6209
6354
|
if (vector.length === 0 || !vector.every((v) => typeof v === "number" && isFinite(v))) {
|
|
6210
6355
|
console.warn(`[WikiMemory] embedFact: embed() returned an invalid vector for ${fact.id}; skipping.`);
|
|
6211
|
-
|
|
6356
|
+
await this.markFailure(fact.id, "invalid_vector");
|
|
6357
|
+
return { ok: false, kind: "invalid_vector" };
|
|
6212
6358
|
}
|
|
6213
|
-
|
|
6359
|
+
float32Vector = new Float32Array(vector);
|
|
6214
6360
|
let hasNonFinite = false;
|
|
6215
6361
|
for (let i = 0; i < float32Vector.length; i++) {
|
|
6216
6362
|
if (!isFinite(float32Vector[i])) {
|
|
@@ -6220,21 +6366,45 @@ var EmbeddingService = class {
|
|
|
6220
6366
|
}
|
|
6221
6367
|
if (hasNonFinite) {
|
|
6222
6368
|
console.warn(`[WikiMemory] embedFact: embed() returned values that overflow float32 for ${fact.id}; skipping.`);
|
|
6223
|
-
|
|
6369
|
+
await this.markFailure(fact.id, "float32_overflow");
|
|
6370
|
+
return { ok: false, kind: "float32_overflow" };
|
|
6224
6371
|
}
|
|
6372
|
+
} catch (err) {
|
|
6373
|
+
console.warn(`[WikiMemory] embedFact failed for ${fact.id}:`, err);
|
|
6374
|
+
await this.markFailure(fact.id, "provider_error");
|
|
6375
|
+
return { ok: false, kind: "provider_error" };
|
|
6376
|
+
}
|
|
6377
|
+
try {
|
|
6225
6378
|
await this.storeEmbeddingDimension(float32Vector.length);
|
|
6226
6379
|
const blob = new Uint8Array(float32Vector.buffer);
|
|
6227
6380
|
await this.entryRepo.updateEmbeddingBlob(fact.id, blob);
|
|
6228
|
-
try {
|
|
6229
|
-
await this.notifyEmbeddingPersisted(fact.entity_id, fact.id, float32Vector);
|
|
6230
|
-
} catch (hookErr) {
|
|
6231
|
-
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed for ${fact.id}:`, hookErr);
|
|
6232
|
-
}
|
|
6233
|
-
return true;
|
|
6234
6381
|
} catch (err) {
|
|
6235
|
-
console.warn(`[WikiMemory] embedFact failed for ${fact.id}:`, err);
|
|
6236
|
-
return false;
|
|
6382
|
+
console.warn(`[WikiMemory] embedFact: persisting embedding failed for ${fact.id}:`, err);
|
|
6383
|
+
return { ok: false, kind: "storage_error" };
|
|
6384
|
+
}
|
|
6385
|
+
try {
|
|
6386
|
+
await this.notifyEmbeddingPersisted(fact.entity_id, fact.id, float32Vector);
|
|
6387
|
+
} catch (hookErr) {
|
|
6388
|
+
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed for ${fact.id}:`, hookErr);
|
|
6237
6389
|
}
|
|
6390
|
+
return { ok: true, dimension: float32Vector.length };
|
|
6391
|
+
}
|
|
6392
|
+
/** Marker writes must never fail the caller. Only marker-eligible kinds reach here. */
|
|
6393
|
+
async markFailure(id, kind) {
|
|
6394
|
+
try {
|
|
6395
|
+
await this.entryRepo.markEmbeddingFailure(id, kind, Date.now());
|
|
6396
|
+
} catch (err) {
|
|
6397
|
+
console.warn(`[WikiMemory] failed to record embedding failure for ${id}:`, err);
|
|
6398
|
+
}
|
|
6399
|
+
}
|
|
6400
|
+
/**
|
|
6401
|
+
* Back-compatible boolean form. Kept because callers such as
|
|
6402
|
+
* ImportExportService branch on `if (!embedded)`; a discriminated result
|
|
6403
|
+
* object is always truthy and would silently disable that branch.
|
|
6404
|
+
*/
|
|
6405
|
+
async embedFact(fact) {
|
|
6406
|
+
const result = await this.tryEmbedFact(fact);
|
|
6407
|
+
return result.ok;
|
|
6238
6408
|
}
|
|
6239
6409
|
async notifyEmbeddingPersisted(entityId, factId, vector) {
|
|
6240
6410
|
if (!this.options.vectorRanker?.onEmbeddingPersisted) return;
|