@equationalapplications/core-llm-wiki 5.4.0 → 5.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 +162 -0
- package/dist/{chunk-N2NXN6PO.mjs → chunk-JQRA6BYQ.mjs} +610 -113
- package/dist/chunk-JQRA6BYQ.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +609 -110
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-C71mnMxq.d.mts → testing-D0RnZjyW.d.mts} +108 -11
- package/dist/{testing-C71mnMxq.d.ts → testing-D0RnZjyW.d.ts} +108 -11
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +607 -110
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-N2NXN6PO.mjs.map +0 -1
package/dist/testing.js
CHANGED
|
@@ -110,26 +110,88 @@ var PrunePartialFailureError = class extends Error {
|
|
|
110
110
|
}
|
|
111
111
|
};
|
|
112
112
|
var HOOK_TIMEOUT_MARKER = /* @__PURE__ */ Symbol("WikiMemoryHookTimeout");
|
|
113
|
+
var WikiParseError = class extends Error {
|
|
114
|
+
constructor(message, opts) {
|
|
115
|
+
super(message);
|
|
116
|
+
this.name = "WikiParseError";
|
|
117
|
+
this.tier = opts.tier;
|
|
118
|
+
this.position = opts.position ?? null;
|
|
119
|
+
this.slice = opts.slice ?? "";
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var WikiIngestEmptyError = class extends Error {
|
|
123
|
+
constructor(params) {
|
|
124
|
+
const summary = `All ${params.chunks} chunks failed for sourceRef "${params.sourceRef}"; see parseFailures for per-chunk detail`;
|
|
125
|
+
super(summary);
|
|
126
|
+
this.name = "WikiIngestEmptyError";
|
|
127
|
+
this.parseFailures = params.parseFailures;
|
|
128
|
+
this.sourceRef = params.sourceRef;
|
|
129
|
+
this.chunks = params.chunks;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
113
132
|
|
|
114
133
|
// src/utils/pure.ts
|
|
134
|
+
var MAX_REPAIR_CANDIDATES = 5;
|
|
135
|
+
function throwNoJsonFound(text, start) {
|
|
136
|
+
throw new WikiParseError(
|
|
137
|
+
"No JSON object/array found in LLM response",
|
|
138
|
+
{ tier: "strict", position: start, slice: text }
|
|
139
|
+
);
|
|
140
|
+
}
|
|
115
141
|
function parseJsonResponse(text) {
|
|
116
142
|
const firstBrace = text.indexOf("{");
|
|
117
143
|
const firstBracket = text.indexOf("[");
|
|
118
144
|
let start;
|
|
119
145
|
let openChar;
|
|
120
|
-
let closeChar;
|
|
121
146
|
const useBrace = firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket);
|
|
122
147
|
if (useBrace) {
|
|
123
148
|
start = firstBrace;
|
|
124
149
|
openChar = "{";
|
|
125
|
-
closeChar = "}";
|
|
126
150
|
} else if (firstBracket !== -1) {
|
|
127
151
|
start = firstBracket;
|
|
128
152
|
openChar = "[";
|
|
129
|
-
closeChar = "]";
|
|
130
153
|
} else {
|
|
131
|
-
|
|
154
|
+
throwNoJsonFound(text, null);
|
|
155
|
+
}
|
|
156
|
+
const slice = scanJsonSlice(text, start, openChar);
|
|
157
|
+
if (slice !== null) {
|
|
158
|
+
try {
|
|
159
|
+
return JSON.parse(slice);
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const repairOdd = containerAwareRepair(text, start, false);
|
|
164
|
+
if (repairOdd.success !== null && !repairOdd.ambiguous) {
|
|
165
|
+
return repairOdd.success;
|
|
132
166
|
}
|
|
167
|
+
const repairEven = containerAwareRepair(text, start, true);
|
|
168
|
+
if (repairOdd.success !== null && repairEven.success !== null) {
|
|
169
|
+
const pick = repairEven.escapes < repairOdd.escapes ? repairEven : repairOdd;
|
|
170
|
+
return pick.success;
|
|
171
|
+
}
|
|
172
|
+
if (repairOdd.success !== null) {
|
|
173
|
+
return repairOdd.success;
|
|
174
|
+
}
|
|
175
|
+
if (repairEven.success !== null) {
|
|
176
|
+
return repairEven.success;
|
|
177
|
+
}
|
|
178
|
+
const repair = repairEven.failed !== null ? repairEven : repairOdd;
|
|
179
|
+
if (repair.failed !== null) {
|
|
180
|
+
throw new WikiParseError(
|
|
181
|
+
`Repair produced a candidate but JSON.parse rejected it: ${repair.failed.message}`,
|
|
182
|
+
{ tier: "repair", position: repair.failed.position, slice: repair.failed.candidate }
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (slice === null) {
|
|
186
|
+
throwNoJsonFound(text, start);
|
|
187
|
+
}
|
|
188
|
+
throw new WikiParseError(
|
|
189
|
+
"No parsable JSON candidate found",
|
|
190
|
+
{ tier: "all", position: start, slice: text }
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
function scanJsonSlice(text, start, openChar) {
|
|
194
|
+
const closeChar = openChar === "{" ? "}" : "]";
|
|
133
195
|
let depth = 0;
|
|
134
196
|
let inString = false;
|
|
135
197
|
let escape = false;
|
|
@@ -161,20 +223,248 @@ function parseJsonResponse(text) {
|
|
|
161
223
|
}
|
|
162
224
|
}
|
|
163
225
|
}
|
|
164
|
-
if (end === -1)
|
|
165
|
-
return
|
|
226
|
+
if (end === -1) return null;
|
|
227
|
+
return safeSlice(text, start, end + 1);
|
|
228
|
+
}
|
|
229
|
+
function containerAwareRepair(text, start, closeOnEvenParity) {
|
|
230
|
+
const stack = [];
|
|
231
|
+
let inString = false;
|
|
232
|
+
let stringRole = null;
|
|
233
|
+
let escape = false;
|
|
234
|
+
let i = start;
|
|
235
|
+
let out = "";
|
|
236
|
+
let bareQuoteCount = 0;
|
|
237
|
+
let bestFailed = null;
|
|
238
|
+
let escapes = 0;
|
|
239
|
+
let ambiguous = false;
|
|
240
|
+
let bestSuccess = null;
|
|
241
|
+
let attempts = 0;
|
|
242
|
+
function tryEmitCandidate(candidate) {
|
|
243
|
+
attempts++;
|
|
244
|
+
try {
|
|
245
|
+
bestSuccess = JSON.parse(candidate);
|
|
246
|
+
return true;
|
|
247
|
+
} catch (err) {
|
|
248
|
+
if (bestFailed === null) {
|
|
249
|
+
bestFailed = {
|
|
250
|
+
candidate,
|
|
251
|
+
position: extractParsePosition(err),
|
|
252
|
+
message: safeErrorToString(err)
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
while (i < text.length) {
|
|
259
|
+
const ch = text[i];
|
|
260
|
+
if (stack.length === 0 && !inString) {
|
|
261
|
+
if (ch === "{" || ch === "[") {
|
|
262
|
+
stack.push({ container: ch === "{" ? "object" : "array", expectKey: true });
|
|
263
|
+
out += ch;
|
|
264
|
+
}
|
|
265
|
+
i++;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (escape) {
|
|
269
|
+
out += ch;
|
|
270
|
+
escape = false;
|
|
271
|
+
i++;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (inString && ch === "\\") {
|
|
275
|
+
out += ch;
|
|
276
|
+
escape = true;
|
|
277
|
+
i++;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (inString) {
|
|
281
|
+
const code = ch.charCodeAt(0);
|
|
282
|
+
if (code < 32) {
|
|
283
|
+
out += controlCharEscape(code);
|
|
284
|
+
i++;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (ch === '"') {
|
|
289
|
+
let j = i + 1;
|
|
290
|
+
while (j < text.length && (text[j] === " " || text[j] === " " || text[j] === "\n" || text[j] === "\r")) {
|
|
291
|
+
j++;
|
|
292
|
+
}
|
|
293
|
+
const next = j < text.length ? text[j] : "";
|
|
294
|
+
if (!inString) {
|
|
295
|
+
if (next === "}" || next === "]") {
|
|
296
|
+
if (j > i + 1) out += text.slice(i + 1, j);
|
|
297
|
+
out += next;
|
|
298
|
+
i = j + 1;
|
|
299
|
+
stack.pop();
|
|
300
|
+
if (stack.length === 0) {
|
|
301
|
+
if (tryEmitCandidate(out)) return { success: bestSuccess, failed: bestFailed, escapes, ambiguous };
|
|
302
|
+
if (attempts >= MAX_REPAIR_CANDIDATES) break;
|
|
303
|
+
out = "";
|
|
304
|
+
}
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const top = stack[stack.length - 1];
|
|
308
|
+
stringRole = top?.container === "object" && top.expectKey ? "key" : "value";
|
|
309
|
+
out += ch;
|
|
310
|
+
inString = true;
|
|
311
|
+
i++;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const commaOnly = next === ",";
|
|
315
|
+
const isKeyClose = stringRole === "key" && (next === ":" || next === '"' && j + 1 < text.length && text[j + 1] === ":");
|
|
316
|
+
const isValueClose = !commaOnly && (next === "," || next === "}" || next === "]");
|
|
317
|
+
if (commaOnly && bareQuoteCount > 0) ambiguous = true;
|
|
318
|
+
const shouldCommaClose = commaOnly && (bareQuoteCount === 0 || (closeOnEvenParity ? bareQuoteCount % 2 === 0 : bareQuoteCount % 2 === 1));
|
|
319
|
+
const isClosing = isKeyClose || isValueClose || shouldCommaClose;
|
|
320
|
+
if (isClosing) {
|
|
321
|
+
out += ch;
|
|
322
|
+
inString = false;
|
|
323
|
+
const topFrame = stack[stack.length - 1];
|
|
324
|
+
if (topFrame && topFrame.container === "object") {
|
|
325
|
+
topFrame.expectKey = stringRole === "value";
|
|
326
|
+
}
|
|
327
|
+
stringRole = null;
|
|
328
|
+
bareQuoteCount = 0;
|
|
329
|
+
i++;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
out += "\\" + ch;
|
|
333
|
+
bareQuoteCount++;
|
|
334
|
+
escapes++;
|
|
335
|
+
i++;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (!inString && (ch === "{" || ch === "[")) {
|
|
339
|
+
stack.push({ container: ch === "{" ? "object" : "array", expectKey: true });
|
|
340
|
+
out += ch;
|
|
341
|
+
i++;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (!inString && ch === ",") {
|
|
345
|
+
const topFrame = stack[stack.length - 1];
|
|
346
|
+
if (topFrame && topFrame.container === "object") {
|
|
347
|
+
topFrame.expectKey = true;
|
|
348
|
+
}
|
|
349
|
+
out += ch;
|
|
350
|
+
i++;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (!inString && (ch === "}" || ch === "]")) {
|
|
354
|
+
out += ch;
|
|
355
|
+
stack.pop();
|
|
356
|
+
if (stack.length === 0) {
|
|
357
|
+
if (tryEmitCandidate(out)) return { success: bestSuccess, failed: bestFailed, escapes, ambiguous };
|
|
358
|
+
if (attempts >= MAX_REPAIR_CANDIDATES) break;
|
|
359
|
+
out = "";
|
|
360
|
+
}
|
|
361
|
+
i++;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
out += ch;
|
|
365
|
+
i++;
|
|
366
|
+
}
|
|
367
|
+
return { success: null, failed: bestFailed, escapes, ambiguous };
|
|
368
|
+
}
|
|
369
|
+
function controlCharEscape(code) {
|
|
370
|
+
switch (code) {
|
|
371
|
+
case 10:
|
|
372
|
+
return "\\n";
|
|
373
|
+
case 9:
|
|
374
|
+
return "\\t";
|
|
375
|
+
case 13:
|
|
376
|
+
return "\\r";
|
|
377
|
+
case 8:
|
|
378
|
+
return "\\b";
|
|
379
|
+
case 12:
|
|
380
|
+
return "\\f";
|
|
381
|
+
default:
|
|
382
|
+
return "\\u" + code.toString(16).padStart(4, "0");
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function extractParsePosition(err) {
|
|
386
|
+
const match = /position\s+(\d+)/.exec(safeErrorToString(err));
|
|
387
|
+
return match ? Number(match[1]) : null;
|
|
388
|
+
}
|
|
389
|
+
function safeErrorToString(e) {
|
|
390
|
+
let isErrorLike = false;
|
|
391
|
+
try {
|
|
392
|
+
isErrorLike = e instanceof Error;
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
395
|
+
if (isErrorLike) {
|
|
396
|
+
const msg = readErrorField(e, "message");
|
|
397
|
+
if (typeof msg === "string" && msg.length > 0) return msg;
|
|
398
|
+
const name = readErrorField(e, "name");
|
|
399
|
+
if (typeof name === "string" && name.length > 0) return name;
|
|
400
|
+
return "[Error]";
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
return String(e);
|
|
404
|
+
} catch {
|
|
405
|
+
try {
|
|
406
|
+
return Object.prototype.toString.call(e);
|
|
407
|
+
} catch {
|
|
408
|
+
return "[unstringifiable error]";
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function readErrorField(e, key) {
|
|
413
|
+
try {
|
|
414
|
+
return e[key];
|
|
415
|
+
} catch {
|
|
416
|
+
return void 0;
|
|
417
|
+
}
|
|
166
418
|
}
|
|
167
419
|
function sanitizeRankerError(err, sanitizeRankerErrors) {
|
|
420
|
+
let isErrorLike = false;
|
|
421
|
+
try {
|
|
422
|
+
isErrorLike = err instanceof Error;
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
168
425
|
if (sanitizeRankerErrors === false) {
|
|
169
|
-
return
|
|
426
|
+
return isErrorLike ? err : new Error(safeErrorToString(err));
|
|
427
|
+
}
|
|
428
|
+
let errLike = null;
|
|
429
|
+
if (isErrorLike) errLike = err;
|
|
430
|
+
let typeName;
|
|
431
|
+
try {
|
|
432
|
+
if (errLike) {
|
|
433
|
+
const rawName = errLike.constructor?.name;
|
|
434
|
+
typeName = typeof rawName === "string" ? rawName : "Error";
|
|
435
|
+
} else {
|
|
436
|
+
typeName = typeof err;
|
|
437
|
+
}
|
|
438
|
+
} catch {
|
|
439
|
+
typeName = isErrorLike ? "Error" : typeof err;
|
|
440
|
+
}
|
|
441
|
+
let innerCause;
|
|
442
|
+
if (errLike) {
|
|
443
|
+
let cause;
|
|
444
|
+
try {
|
|
445
|
+
cause = errLike.cause;
|
|
446
|
+
} catch {
|
|
447
|
+
cause = void 0;
|
|
448
|
+
}
|
|
449
|
+
if (cause !== void 0) {
|
|
450
|
+
let causeName;
|
|
451
|
+
try {
|
|
452
|
+
const rawCauseName = cause?.constructor?.name;
|
|
453
|
+
causeName = typeof rawCauseName === "string" ? rawCauseName : typeof cause;
|
|
454
|
+
} catch {
|
|
455
|
+
causeName = typeof cause;
|
|
456
|
+
}
|
|
457
|
+
innerCause = new Error(`Caused by: ${causeName}`);
|
|
458
|
+
}
|
|
170
459
|
}
|
|
171
|
-
const typeName = err instanceof Error ? err.constructor?.name ?? "Error" : typeof err;
|
|
172
|
-
const innerCause = err instanceof Error && err.cause !== void 0 ? new Error(`Caused by: ${err.cause?.constructor?.name ?? typeof err.cause}`) : void 0;
|
|
173
460
|
const sanitized = new Error(
|
|
174
461
|
`VectorRanker ${typeName} (message scrubbed for security)`,
|
|
175
462
|
innerCause ? { cause: innerCause } : void 0
|
|
176
463
|
);
|
|
177
|
-
|
|
464
|
+
try {
|
|
465
|
+
sanitized.name = typeName;
|
|
466
|
+
} catch {
|
|
467
|
+
}
|
|
178
468
|
return sanitized;
|
|
179
469
|
}
|
|
180
470
|
function safeSlice(value, start, end) {
|
|
@@ -969,7 +1259,7 @@ Return ONLY a valid JSON object matching this schema:
|
|
|
969
1259
|
{
|
|
970
1260
|
"facts": [{ "title": "string (max 80 chars)", "body": "string (max 800 chars)", "tags": ["string"], "confidence": "certain|inferred|tentative" }]
|
|
971
1261
|
}
|
|
972
|
-
Extract verbatim factual content. Do not return markdown, just raw JSON.`;
|
|
1262
|
+
Extract verbatim factual content. JSON escaping rules: every literal " character in the source must be escaped as \\" inside any JSON string body, and every literal newline as \\n. Source prose containing quotes (e.g. a worked example with "...") must still be reproduced exactly \u2014 re-escape, do not omit. Do not return markdown, just raw JSON.`;
|
|
973
1263
|
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.
|
|
974
1264
|
Return ONLY a valid JSON object matching this schema:
|
|
975
1265
|
{
|
|
@@ -978,7 +1268,7 @@ Return ONLY a valid JSON object matching this schema:
|
|
|
978
1268
|
]
|
|
979
1269
|
}
|
|
980
1270
|
If no manifest type fits a fact, omit that fact from "classifications" entirely \u2014 do not guess.
|
|
981
|
-
Do not return markdown, just raw JSON.`;
|
|
1271
|
+
When echoing an existing fact's title verbatim into "target_title", preserve every JSON escape sequence (\\", \\n, \\\\, \\/) exactly as it appeared in the input body \u2014 do not strip backslashes, do not add unescaped quotes. Do not return markdown, just raw JSON.`;
|
|
982
1272
|
|
|
983
1273
|
// src/services/PromptService.ts
|
|
984
1274
|
var PromptService = class {
|
|
@@ -1091,6 +1381,16 @@ var DEFAULT_MAX_CHUNK_LENGTH = 12e3;
|
|
|
1091
1381
|
var DEFAULT_CHUNK_OVERLAP = 400;
|
|
1092
1382
|
|
|
1093
1383
|
// src/services/IngestionService.ts
|
|
1384
|
+
function zeroChunkResult(duplicateOf) {
|
|
1385
|
+
const result = {
|
|
1386
|
+
truncated: false,
|
|
1387
|
+
chunks: 0,
|
|
1388
|
+
ingestedChunks: 0,
|
|
1389
|
+
failedChunks: 0
|
|
1390
|
+
};
|
|
1391
|
+
if (duplicateOf !== void 0) result.duplicateOf = duplicateOf;
|
|
1392
|
+
return result;
|
|
1393
|
+
}
|
|
1094
1394
|
var IngestionService = class {
|
|
1095
1395
|
constructor(db, prefix, options, entryRepo, sourceRefIndexRepo, metadataRepo, edgeRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
|
|
1096
1396
|
this.db = db;
|
|
@@ -1131,113 +1431,110 @@ var IngestionService = class {
|
|
|
1131
1431
|
if (onDuplicateHash === "throw") {
|
|
1132
1432
|
throw new WikiDuplicateHashError({ canonical, sourceHash, entityId });
|
|
1133
1433
|
}
|
|
1134
|
-
return
|
|
1434
|
+
return zeroChunkResult(canonical);
|
|
1135
1435
|
}
|
|
1136
1436
|
}
|
|
1137
1437
|
const { chunks, truncated } = chunkText(params.documentChunk, maxChunkLength, chunkOverlap);
|
|
1138
|
-
if (chunks.length === 0) return
|
|
1438
|
+
if (chunks.length === 0) return zeroChunkResult();
|
|
1439
|
+
const ontologyContext = await this.ontologyService?.buildPromptContext(entityId) ?? null;
|
|
1139
1440
|
const chunkResults = await withConcurrency(
|
|
1140
|
-
chunks.map((chunk) => async () => {
|
|
1141
|
-
const ontologyContext = await this.ontologyService?.buildPromptContext(entityId) ?? null;
|
|
1441
|
+
chunks.map((chunk, chunkIndex) => async () => {
|
|
1142
1442
|
const { systemPrompt, userPrompt } = this.promptService.buildIngestPrompt(
|
|
1143
1443
|
chunk,
|
|
1144
1444
|
params.promptOverride,
|
|
1145
1445
|
ontologyContext
|
|
1146
1446
|
);
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1447
|
+
try {
|
|
1448
|
+
const responseText = await this.options.llmProvider.generateText({ systemPrompt, userPrompt });
|
|
1449
|
+
const result2 = parseJsonResponse(responseText);
|
|
1450
|
+
return {
|
|
1451
|
+
status: "ok",
|
|
1452
|
+
facts: (Array.isArray(result2.facts) ? result2.facts : []).map(validateFact).filter((f) => f !== null),
|
|
1453
|
+
ontology_updates: result2.ontology_updates
|
|
1454
|
+
};
|
|
1455
|
+
} catch (e) {
|
|
1456
|
+
const failure = e instanceof WikiParseError ? {
|
|
1457
|
+
chunkIndex,
|
|
1458
|
+
sourceRef,
|
|
1459
|
+
source: "parse",
|
|
1460
|
+
tier: e.tier,
|
|
1461
|
+
position: e.position,
|
|
1462
|
+
message: e.message
|
|
1463
|
+
} : {
|
|
1464
|
+
chunkIndex,
|
|
1465
|
+
sourceRef,
|
|
1466
|
+
source: "llm",
|
|
1467
|
+
// `safeErrorToString` is a non-throwing coercion: an LLM
|
|
1468
|
+
// provider may reject with a non-Error value, and even
|
|
1469
|
+
// `String(e)` itself can throw when `e.toString()` throws.
|
|
1470
|
+
// Letting such an exception escape this catch would cause
|
|
1471
|
+
// `withConcurrency` to reject, discarding every sibling
|
|
1472
|
+
// chunk's results — the exact failure mode this per-chunk
|
|
1473
|
+
// try/catch exists to prevent. Keeping the value in the
|
|
1474
|
+
// `ChunkFailure` preserves the typed diagnostic for
|
|
1475
|
+
// callers (see parseFailures) without unwinding the loop.
|
|
1476
|
+
position: null,
|
|
1477
|
+
message: safeErrorToString(e)
|
|
1478
|
+
};
|
|
1479
|
+
const total = chunks.length;
|
|
1480
|
+
const tags = [];
|
|
1481
|
+
if (failure.tier) tags.push(`tier=${failure.tier}`);
|
|
1482
|
+
if (failure.position !== null) tags.push(`position=${failure.position}`);
|
|
1483
|
+
const tagsSuffix = tags.length > 0 ? `; ${tags.join(" ")}` : "";
|
|
1484
|
+
console.warn(
|
|
1485
|
+
`[WikiMemory] ingest chunk ${chunkIndex + 1}/${total} ${failure.source} failed (sourceRef=${sourceRef}${tagsSuffix})`
|
|
1486
|
+
);
|
|
1487
|
+
return { status: "failed", error: failure };
|
|
1488
|
+
}
|
|
1153
1489
|
}),
|
|
1154
1490
|
chunkConcurrency
|
|
1155
1491
|
);
|
|
1492
|
+
let ingestedChunks = 0;
|
|
1493
|
+
let failedChunks = 0;
|
|
1494
|
+
const failures = [];
|
|
1156
1495
|
const seen = /* @__PURE__ */ new Set();
|
|
1157
1496
|
const orderedChunkFacts = [];
|
|
1158
|
-
for (const
|
|
1497
|
+
for (const slot of chunkResults) {
|
|
1498
|
+
if (slot.status === "failed") {
|
|
1499
|
+
failedChunks++;
|
|
1500
|
+
failures.push(slot.error);
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
ingestedChunks++;
|
|
1159
1504
|
const dedupedFacts = [];
|
|
1160
|
-
for (const fact of
|
|
1505
|
+
for (const fact of slot.facts) {
|
|
1161
1506
|
const normalizedTitle = normalizeTitleKey(fact.title);
|
|
1162
1507
|
if (!seen.has(normalizedTitle)) {
|
|
1163
1508
|
seen.add(normalizedTitle);
|
|
1164
1509
|
dedupedFacts.push(fact);
|
|
1165
1510
|
}
|
|
1166
1511
|
}
|
|
1167
|
-
orderedChunkFacts.push({ facts: dedupedFacts, ontology_updates:
|
|
1512
|
+
orderedChunkFacts.push({ facts: dedupedFacts, ontology_updates: slot.ontology_updates });
|
|
1513
|
+
}
|
|
1514
|
+
if (failedChunks === chunks.length) {
|
|
1515
|
+
throw new WikiIngestEmptyError({
|
|
1516
|
+
parseFailures: failures,
|
|
1517
|
+
sourceRef,
|
|
1518
|
+
chunks: chunks.length
|
|
1519
|
+
});
|
|
1168
1520
|
}
|
|
1169
|
-
const now = Date.now();
|
|
1170
1521
|
const insertedFacts = [];
|
|
1171
1522
|
const deletedSourceFactIds = [];
|
|
1172
1523
|
try {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
for (const { facts, ontology_updates } of orderedChunkFacts) {
|
|
1188
|
-
if (mode === "emergent" && ontology_updates && this.ontologyService) {
|
|
1189
|
-
manifest = await this.ontologyService.mergeEmergentUpdates(entityId, ontology_updates, tx);
|
|
1190
|
-
ontologyState = await this.ontologyService.getEffectiveState(entityId, tx);
|
|
1191
|
-
mode = ontologyState.mode;
|
|
1192
|
-
}
|
|
1193
|
-
for (const fact of facts) {
|
|
1194
|
-
const ontologyFact = fact;
|
|
1195
|
-
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
|
|
1196
|
-
const id = generateId("fact_");
|
|
1197
|
-
hostNodes.push({
|
|
1198
|
-
id,
|
|
1199
|
-
type: ontologyFact.okf_type ?? "",
|
|
1200
|
-
title: fact.title,
|
|
1201
|
-
body: fact.body,
|
|
1202
|
-
// Forward the LLM-extracted tags/confidence through the
|
|
1203
|
-
// extracted-shape upsertGraphCore path so the entries row
|
|
1204
|
-
// stores them (search filterability, heal-candidate
|
|
1205
|
-
// selection, runReembed embedding-text signal). The public
|
|
1206
|
-
// WikiMemory.upsertGraph API doesn't accept tags/confidence
|
|
1207
|
-
// — host-supplied deterministic nodes default to [] and
|
|
1208
|
-
// 'certain' as documented.
|
|
1209
|
-
tags: fact.tags,
|
|
1210
|
-
confidence: fact.confidence
|
|
1211
|
-
});
|
|
1212
|
-
insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
|
|
1213
|
-
titleIndex.set(normalizeTitleKey(fact.title), { id, okf_type: normalized.okf_type });
|
|
1214
|
-
if (normalized.edges.length > 0) {
|
|
1215
|
-
rawEdgeRequests.push({ sourceId: id, sourceType: normalized.okf_type, edges: normalized.edges });
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
const hostEdges = [];
|
|
1220
|
-
for (const req of rawEdgeRequests) {
|
|
1221
|
-
const resolved = this.ontologyService?.resolveEdges(
|
|
1222
|
-
entityId,
|
|
1223
|
-
req.sourceId,
|
|
1224
|
-
req.sourceType,
|
|
1225
|
-
req.edges,
|
|
1226
|
-
manifest,
|
|
1227
|
-
titleIndex,
|
|
1228
|
-
now
|
|
1229
|
-
) ?? [];
|
|
1230
|
-
for (const e of resolved) {
|
|
1231
|
-
hostEdges.push({ type: e.edge_type, sourceId: e.source_id, targetId: e.target_id });
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
await this.upsertGraphCore(
|
|
1235
|
-
entityId,
|
|
1236
|
-
{ sourceRef, sourceHash, nodes: hostNodes, edges: hostEdges },
|
|
1237
|
-
tx,
|
|
1238
|
-
{ strict: false }
|
|
1239
|
-
);
|
|
1240
|
-
});
|
|
1524
|
+
if (failedChunks === 0) {
|
|
1525
|
+
const fullResult = await this.db.withTransactionAsync(async (tx) => {
|
|
1526
|
+
return await this.runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx);
|
|
1527
|
+
});
|
|
1528
|
+
deletedSourceFactIds.push(...fullResult.deletedSourceFactIds);
|
|
1529
|
+
insertedFacts.push(...fullResult.insertedFacts);
|
|
1530
|
+
} else {
|
|
1531
|
+
const partialResult = await this.db.withTransactionAsync(async (tx) => {
|
|
1532
|
+
const flat = [];
|
|
1533
|
+
for (const slot of orderedChunkFacts) flat.push(...slot.facts);
|
|
1534
|
+
return await this.appendPartialFacts(entityId, sourceRef, flat, tx);
|
|
1535
|
+
});
|
|
1536
|
+
insertedFacts.push(...partialResult.insertedDescriptors);
|
|
1537
|
+
}
|
|
1241
1538
|
} catch (err) {
|
|
1242
1539
|
const sqliteCode = err instanceof WikiTransactionError ? err.sqliteErrorCode : extractSqliteCode(err);
|
|
1243
1540
|
if (sqliteCode !== "SQLITE_CONSTRAINT_UNIQUE") throw err;
|
|
@@ -1246,22 +1543,31 @@ var IngestionService = class {
|
|
|
1246
1543
|
if (onDuplicateHash === "throw" || onDuplicateHash === "ingest") {
|
|
1247
1544
|
throw new WikiDuplicateHashError({ canonical, sourceHash, entityId });
|
|
1248
1545
|
}
|
|
1249
|
-
return
|
|
1546
|
+
return zeroChunkResult(canonical);
|
|
1250
1547
|
}
|
|
1251
1548
|
await this.searchService.sync(entityId);
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1549
|
+
if (failedChunks === 0) {
|
|
1550
|
+
const uniqueDeletedSourceFactIds = Array.from(new Set(deletedSourceFactIds));
|
|
1551
|
+
for (const factId of uniqueDeletedSourceFactIds) {
|
|
1552
|
+
try {
|
|
1553
|
+
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
1554
|
+
} catch (hookErr) {
|
|
1555
|
+
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during ingest for ${factId}:`, hookErr);
|
|
1556
|
+
}
|
|
1258
1557
|
}
|
|
1259
1558
|
}
|
|
1260
1559
|
for (const fact of insertedFacts) {
|
|
1261
1560
|
await this.embeddingService.embedFact(fact);
|
|
1262
1561
|
}
|
|
1263
1562
|
this.searchService.evictCache(entityId);
|
|
1264
|
-
|
|
1563
|
+
const result = {
|
|
1564
|
+
truncated,
|
|
1565
|
+
chunks: chunks.length,
|
|
1566
|
+
ingestedChunks,
|
|
1567
|
+
failedChunks
|
|
1568
|
+
};
|
|
1569
|
+
if (failedChunks > 0) result.parseFailures = failures;
|
|
1570
|
+
return result;
|
|
1265
1571
|
} finally {
|
|
1266
1572
|
releaseIngestLocks();
|
|
1267
1573
|
}
|
|
@@ -1386,6 +1692,156 @@ var IngestionService = class {
|
|
|
1386
1692
|
superseded: deletedFactIds.length + deletedEdgeCount
|
|
1387
1693
|
};
|
|
1388
1694
|
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Full supersession + ownership path: identical to the pre-issue-#92 behavior
|
|
1697
|
+
* for the happy path. Called from the `failedChunks === 0` branch of
|
|
1698
|
+
* `ingestDocument`. Runs INSIDE the caller's tx.
|
|
1699
|
+
*
|
|
1700
|
+
* Returns `{ deletedSourceFactIds, insertedFacts }` so the caller can fire
|
|
1701
|
+
* post-commit hooks (embedding lifecycle) AFTER the transaction commits.
|
|
1702
|
+
* On the partial path the caller never invokes this method; the empty
|
|
1703
|
+
* arrays stay empty.
|
|
1704
|
+
*/
|
|
1705
|
+
async runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx) {
|
|
1706
|
+
const deletedSourceFactIds = [];
|
|
1707
|
+
const insertedFacts = [];
|
|
1708
|
+
deletedSourceFactIds.push(...await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false));
|
|
1709
|
+
const titleIndex = /* @__PURE__ */ new Map();
|
|
1710
|
+
const existingFacts = await this.entryRepo.findRecentByEntityId(entityId, 500, tx, sourceRef);
|
|
1711
|
+
for (const existing of existingFacts) {
|
|
1712
|
+
titleIndex.set(normalizeTitleKey(existing.title), {
|
|
1713
|
+
id: existing.id,
|
|
1714
|
+
okf_type: existing.okf_type ?? null
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
let ontologyState = await this.ontologyService?.getEffectiveState(entityId, tx) ?? { mode: "off", manifest: { node_types: [], edge_types: [] } };
|
|
1718
|
+
let { mode, manifest } = ontologyState;
|
|
1719
|
+
const hostNodes = [];
|
|
1720
|
+
const rawEdgeRequests = [];
|
|
1721
|
+
const now = Date.now();
|
|
1722
|
+
for (const { facts, ontology_updates } of orderedChunkFacts) {
|
|
1723
|
+
if (mode === "emergent" && ontology_updates && this.ontologyService) {
|
|
1724
|
+
manifest = await this.ontologyService.mergeEmergentUpdates(entityId, ontology_updates, tx);
|
|
1725
|
+
ontologyState = await this.ontologyService.getEffectiveState(entityId, tx);
|
|
1726
|
+
mode = ontologyState.mode;
|
|
1727
|
+
}
|
|
1728
|
+
for (const fact of facts) {
|
|
1729
|
+
const ontologyFact = fact;
|
|
1730
|
+
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
|
|
1731
|
+
const id = generateId("fact_");
|
|
1732
|
+
hostNodes.push({
|
|
1733
|
+
id,
|
|
1734
|
+
type: ontologyFact.okf_type ?? "",
|
|
1735
|
+
title: fact.title,
|
|
1736
|
+
body: fact.body,
|
|
1737
|
+
// Forward the LLM-extracted tags/confidence through the
|
|
1738
|
+
// extracted-shape upsertGraphCore path so the entries row
|
|
1739
|
+
// stores them (search filterability, heal-candidate
|
|
1740
|
+
// selection, runReembed embedding-text signal). The public
|
|
1741
|
+
// WikiMemory.upsertGraph API doesn't accept tags/confidence
|
|
1742
|
+
// — host-supplied deterministic nodes default to [] and
|
|
1743
|
+
// 'certain' as documented.
|
|
1744
|
+
tags: fact.tags,
|
|
1745
|
+
confidence: fact.confidence
|
|
1746
|
+
});
|
|
1747
|
+
insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
|
|
1748
|
+
titleIndex.set(normalizeTitleKey(fact.title), { id, okf_type: normalized.okf_type });
|
|
1749
|
+
if (normalized.edges.length > 0) {
|
|
1750
|
+
rawEdgeRequests.push({ sourceId: id, sourceType: normalized.okf_type, edges: normalized.edges });
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
const hostEdges = [];
|
|
1755
|
+
for (const req of rawEdgeRequests) {
|
|
1756
|
+
const resolved = this.ontologyService?.resolveEdges(
|
|
1757
|
+
entityId,
|
|
1758
|
+
req.sourceId,
|
|
1759
|
+
req.sourceType,
|
|
1760
|
+
req.edges,
|
|
1761
|
+
manifest,
|
|
1762
|
+
titleIndex,
|
|
1763
|
+
now
|
|
1764
|
+
) ?? [];
|
|
1765
|
+
for (const e of resolved) {
|
|
1766
|
+
hostEdges.push({ type: e.edge_type, sourceId: e.source_id, targetId: e.target_id });
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
await this.upsertGraphCore(
|
|
1770
|
+
entityId,
|
|
1771
|
+
{ sourceRef, sourceHash, nodes: hostNodes, edges: hostEdges },
|
|
1772
|
+
tx,
|
|
1773
|
+
{ strict: false }
|
|
1774
|
+
);
|
|
1775
|
+
return { deletedSourceFactIds, insertedFacts };
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Partial-commit path for `ingestDocument`. Inserts ONLY facts whose
|
|
1779
|
+
* normalized title is not already in the live set for this (entityId,
|
|
1780
|
+
* sourceRef). Does NOT call `entryRepo.softDeleteBySource` (no
|
|
1781
|
+
* supersession) and does NOT call `sourceRefIndexRepo.upsert` (no
|
|
1782
|
+
* ownership). Edges are NOT resolved on this path (no ontology-context
|
|
1783
|
+
* build, no `resolveEdges`, no `mergeEmergentUpdates`). Stale edges from
|
|
1784
|
+
* prior attempts are recovered on the next full run's supersession.
|
|
1785
|
+
*
|
|
1786
|
+
* Source-hash is stored as NULL on partial rows. `findLatestSourceHash`
|
|
1787
|
+
* reads from the most recently updated live row for the sourceRef, so
|
|
1788
|
+
* storing the incoming hash here would cause `hasChanged` to return
|
|
1789
|
+
* `false` on a same-hash retry — the failed chunks would never get a
|
|
1790
|
+
* second chance. With NULL, a retry sees `storedHash === null`, `hasChanged`
|
|
1791
|
+
* returns `true`, and the partial commit's sibling rows remain live
|
|
1792
|
+
* (deduped, not superseded) for the next attempt to extend.
|
|
1793
|
+
*
|
|
1794
|
+
* Runs INSIDE the caller's `tx`. Does not open a nested transaction.
|
|
1795
|
+
* Returns `{ inserted, skippedDuplicate }` for observability.
|
|
1796
|
+
*/
|
|
1797
|
+
async appendPartialFacts(entityId, sourceRef, dedupedFacts, tx) {
|
|
1798
|
+
const liveIds = await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false);
|
|
1799
|
+
const liveFacts = liveIds.length === 0 ? [] : await this.entryRepo.findByIds(liveIds, void 0, tx);
|
|
1800
|
+
const liveTitles = new Set(liveFacts.map((f) => normalizeTitleKey(f.title)));
|
|
1801
|
+
let inserted = 0;
|
|
1802
|
+
let skippedDuplicate = 0;
|
|
1803
|
+
const now = Date.now();
|
|
1804
|
+
const insertedDescriptors = [];
|
|
1805
|
+
for (const fact of dedupedFacts) {
|
|
1806
|
+
const normalizedTitle = normalizeTitleKey(fact.title);
|
|
1807
|
+
if (liveTitles.has(normalizedTitle)) {
|
|
1808
|
+
skippedDuplicate++;
|
|
1809
|
+
continue;
|
|
1810
|
+
}
|
|
1811
|
+
liveTitles.add(normalizedTitle);
|
|
1812
|
+
const id = generateId("fact_");
|
|
1813
|
+
const wikiFact = {
|
|
1814
|
+
id,
|
|
1815
|
+
entity_id: entityId,
|
|
1816
|
+
title: fact.title,
|
|
1817
|
+
body: fact.body,
|
|
1818
|
+
tags: fact.tags,
|
|
1819
|
+
confidence: fact.confidence,
|
|
1820
|
+
source_type: "immutable_document",
|
|
1821
|
+
// source_hash: null on partial rows — see method docstring. The
|
|
1822
|
+
// full path stamps the actual hash; partial rows stay hash-less so
|
|
1823
|
+
// `hasChanged` keeps returning true for retries.
|
|
1824
|
+
source_hash: null,
|
|
1825
|
+
source_ref: sourceRef,
|
|
1826
|
+
created_at: now,
|
|
1827
|
+
updated_at: now,
|
|
1828
|
+
last_accessed_at: null,
|
|
1829
|
+
access_count: 0,
|
|
1830
|
+
deleted_at: null,
|
|
1831
|
+
okf_type: null
|
|
1832
|
+
};
|
|
1833
|
+
await this.entryRepo.upsert(wikiFact, tx);
|
|
1834
|
+
insertedDescriptors.push({
|
|
1835
|
+
id,
|
|
1836
|
+
entity_id: entityId,
|
|
1837
|
+
title: fact.title,
|
|
1838
|
+
body: fact.body,
|
|
1839
|
+
tags: JSON.stringify(fact.tags)
|
|
1840
|
+
});
|
|
1841
|
+
inserted++;
|
|
1842
|
+
}
|
|
1843
|
+
return { inserted, skippedDuplicate, insertedDescriptors };
|
|
1844
|
+
}
|
|
1389
1845
|
};
|
|
1390
1846
|
|
|
1391
1847
|
// src/utils/embedding.ts
|
|
@@ -1430,7 +1886,12 @@ var TRUNCATION_PATTERNS = [
|
|
|
1430
1886
|
];
|
|
1431
1887
|
var EXCEEDS_LIMIT_PATTERN = /exceed[a-z]*[^.]{0,40}\b(model|context)?[ _-]?limit/i;
|
|
1432
1888
|
function isTruncationError(err) {
|
|
1433
|
-
|
|
1889
|
+
let message;
|
|
1890
|
+
try {
|
|
1891
|
+
message = err instanceof Error ? err.message : String(err ?? "");
|
|
1892
|
+
} catch {
|
|
1893
|
+
return false;
|
|
1894
|
+
}
|
|
1434
1895
|
if (EXCEEDS_LIMIT_PATTERN.test(message)) return false;
|
|
1435
1896
|
return TRUNCATION_PATTERNS.some((pattern) => pattern.test(message));
|
|
1436
1897
|
}
|
|
@@ -1534,6 +1995,30 @@ var HEAL_ANCHOR_SEARCH_OVERFETCH = 4;
|
|
|
1534
1995
|
var HEAL_MAX_PROMPT_CHARS = 4e4;
|
|
1535
1996
|
var HEAL_BATCH_SIZE = 25;
|
|
1536
1997
|
var HEAL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1998
|
+
var SKIP_ERROR_LOG_CHARS = 4096;
|
|
1999
|
+
var formatSkipError = (err) => {
|
|
2000
|
+
let base;
|
|
2001
|
+
let isErrorLike = false;
|
|
2002
|
+
try {
|
|
2003
|
+
isErrorLike = err instanceof Error;
|
|
2004
|
+
} catch {
|
|
2005
|
+
}
|
|
2006
|
+
if (isErrorLike || typeof err !== "object" && typeof err !== "function") {
|
|
2007
|
+
base = safeErrorToString(err);
|
|
2008
|
+
} else {
|
|
2009
|
+
try {
|
|
2010
|
+
const json = JSON.stringify(err);
|
|
2011
|
+
base = typeof json === "string" ? json : safeErrorToString(err);
|
|
2012
|
+
} catch {
|
|
2013
|
+
base = safeErrorToString(err);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
if (base.length > SKIP_ERROR_LOG_CHARS) {
|
|
2017
|
+
const truncated = safeSlice(base, 0, SKIP_ERROR_LOG_CHARS);
|
|
2018
|
+
return `${truncated}\u2026[+${base.length - SKIP_ERROR_LOG_CHARS} chars truncated]`;
|
|
2019
|
+
}
|
|
2020
|
+
return base;
|
|
2021
|
+
};
|
|
1537
2022
|
var MaintenanceService = class {
|
|
1538
2023
|
constructor(db, prefix, options, entryRepo, sourceRefIndexRepo, taskRepo, eventRepo, metadataRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
|
|
1539
2024
|
this.db = db;
|
|
@@ -2014,8 +2499,7 @@ var MaintenanceService = class {
|
|
|
2014
2499
|
maxPromptChars: HEAL_MAX_PROMPT_CHARS,
|
|
2015
2500
|
onSkip: (fact, err) => {
|
|
2016
2501
|
console.warn(
|
|
2017
|
-
`[WikiMemory] heal skipped ${entityId}/${fact.id}: response could not be bounded
|
|
2018
|
-
err
|
|
2502
|
+
`[WikiMemory] heal skipped ${entityId}/${fact.id}: response could not be bounded: ${formatSkipError(err)}`
|
|
2019
2503
|
);
|
|
2020
2504
|
}
|
|
2021
2505
|
});
|
|
@@ -2153,8 +2637,7 @@ var MaintenanceService = class {
|
|
|
2153
2637
|
maxPromptChars: ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS,
|
|
2154
2638
|
onSkip: (fact, err) => {
|
|
2155
2639
|
console.warn(
|
|
2156
|
-
`[WikiMemory] ontology backfill skipped ${entityId}/${fact.id}: response could not be bounded
|
|
2157
|
-
err
|
|
2640
|
+
`[WikiMemory] ontology backfill skipped ${entityId}/${fact.id}: response could not be bounded: ${formatSkipError(err)}`
|
|
2158
2641
|
);
|
|
2159
2642
|
}
|
|
2160
2643
|
});
|
|
@@ -2613,7 +3096,12 @@ var RetrievalService = class {
|
|
|
2613
3096
|
}
|
|
2614
3097
|
}
|
|
2615
3098
|
} catch (rankerErr) {
|
|
2616
|
-
|
|
3099
|
+
let isErrorLike = false;
|
|
3100
|
+
try {
|
|
3101
|
+
isErrorLike = rankerErr instanceof Error;
|
|
3102
|
+
} catch {
|
|
3103
|
+
}
|
|
3104
|
+
const rankerError = isErrorLike ? rankerErr : new Error(safeErrorToString(rankerErr));
|
|
2617
3105
|
const policy = this.options.vectorRankerFallback ?? "js-cosine";
|
|
2618
3106
|
this.options.onVectorRankerFallback?.({
|
|
2619
3107
|
error: this._sanitizeRankerError(rankerError),
|
|
@@ -2725,12 +3213,21 @@ var RetrievalService = class {
|
|
|
2725
3213
|
}
|
|
2726
3214
|
}
|
|
2727
3215
|
} catch (err) {
|
|
2728
|
-
|
|
3216
|
+
let isErrorLike = false;
|
|
3217
|
+
try {
|
|
3218
|
+
isErrorLike = err instanceof Error;
|
|
3219
|
+
} catch {
|
|
3220
|
+
}
|
|
3221
|
+
const error = isErrorLike ? err : new Error(safeErrorToString(err));
|
|
2729
3222
|
if (rankerShouldRethrow) {
|
|
2730
3223
|
throw error;
|
|
2731
3224
|
}
|
|
2732
3225
|
if (pendingRankerFallbackError) {
|
|
2733
|
-
|
|
3226
|
+
try {
|
|
3227
|
+
error.cause = pendingRankerFallbackError;
|
|
3228
|
+
} catch {
|
|
3229
|
+
this.options.onRetrievalFallback?.(pendingRankerFallbackError);
|
|
3230
|
+
}
|
|
2734
3231
|
pendingRankerFallbackError = void 0;
|
|
2735
3232
|
}
|
|
2736
3233
|
this.options.onRetrievalFallback?.(error);
|