@agenr/openclaw-plugin 3.3.0 → 2026.6.2
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/dist/build-before-turn-artifact-NPUHVWFE.js +71 -0
- package/dist/build-recall-artifact-F3LS3PZX.js +62 -0
- package/dist/chunk-5AXMFBHR.js +14 -0
- package/dist/chunk-5AYIXQRF.js +4452 -0
- package/dist/chunk-5TIP2EPP.js +6944 -0
- package/dist/chunk-GAERET5Q.js +2070 -0
- package/dist/chunk-GF3PX3VM.js +41 -0
- package/dist/chunk-GKZQ5AG5.js +44 -0
- package/dist/chunk-LDJN7CVU.js +3231 -0
- package/dist/chunk-MC3C2XM5.js +148 -0
- package/dist/chunk-NBS62ES5.js +3012 -0
- package/dist/chunk-NSLTJBUC.js +270 -0
- package/dist/chunk-OJSIZDZD.js +9 -0
- package/dist/chunk-OWGQWQUP.js +45 -0
- package/dist/chunk-Q5UTJXHZ.js +1069 -0
- package/dist/chunk-SOQW7356.js +2416 -0
- package/dist/chunk-VBPYU7GO.js +597 -0
- package/dist/chunk-VTHBPXDQ.js +1750 -0
- package/dist/chunk-XFJ4S4G2.js +1679 -0
- package/dist/chunk-Y5NB3FTH.js +106 -0
- package/dist/chunk-ZX55JBV2.js +4451 -0
- package/dist/index.js +1848 -19847
- package/dist/lifecycle-checkpoint-IAC5FCQU.js +154 -0
- package/dist/scan-6JKPOQHD.js +6 -0
- package/dist/service-EKFACEN6.js +15 -0
- package/dist/service-RHNB5AEQ.js +861 -0
- package/dist/sink-AUAAWC5O.js +8 -0
- package/openclaw.plugin.json +2 -11
- package/package.json +1 -1
|
@@ -0,0 +1,2070 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isCurrentlyValidMemory,
|
|
3
|
+
isStaleMemory,
|
|
4
|
+
isWithinValidityWindow,
|
|
5
|
+
normalizeClaimKey,
|
|
6
|
+
resolveKeyedDurableLifecycleStatus
|
|
7
|
+
} from "./chunk-VBPYU7GO.js";
|
|
8
|
+
|
|
9
|
+
// src/core/recall/cross-encoder.ts
|
|
10
|
+
var DEFAULT_CROSS_ENCODER_TOP_K = 10;
|
|
11
|
+
var DEFAULT_CROSS_ENCODER_ALPHA = 0.6;
|
|
12
|
+
async function applyCrossEncoderRerank(options) {
|
|
13
|
+
const candidates = [...options.candidates];
|
|
14
|
+
const k = resolveTopK(options.topK, candidates.length);
|
|
15
|
+
const alpha = resolveAlpha(options.alpha);
|
|
16
|
+
const startedAt = Date.now();
|
|
17
|
+
const passthrough = (degradedReason) => ({
|
|
18
|
+
applied: false,
|
|
19
|
+
k,
|
|
20
|
+
alpha,
|
|
21
|
+
latencyMs: elapsedMs(startedAt),
|
|
22
|
+
...degradedReason ? { degradedReason } : {},
|
|
23
|
+
candidates: candidates.map((candidate) => ({
|
|
24
|
+
candidate: candidate.candidate,
|
|
25
|
+
score: candidate.score
|
|
26
|
+
})),
|
|
27
|
+
rescoredIds: []
|
|
28
|
+
});
|
|
29
|
+
if (options.disabled === true) {
|
|
30
|
+
return passthrough("disabled");
|
|
31
|
+
}
|
|
32
|
+
if (!options.port) {
|
|
33
|
+
return passthrough("not_configured");
|
|
34
|
+
}
|
|
35
|
+
if (candidates.length === 0) {
|
|
36
|
+
return passthrough("no_candidates");
|
|
37
|
+
}
|
|
38
|
+
const shortlist = candidates.slice(0, k);
|
|
39
|
+
const tail = candidates.slice(k);
|
|
40
|
+
const query = options.query.trim();
|
|
41
|
+
if (query.length === 0 || shortlist.length === 0) {
|
|
42
|
+
return passthrough("no_candidates");
|
|
43
|
+
}
|
|
44
|
+
let scores;
|
|
45
|
+
try {
|
|
46
|
+
scores = await options.port.rank(
|
|
47
|
+
query,
|
|
48
|
+
shortlist.map((candidate) => ({ id: candidate.id, text: candidate.text }))
|
|
49
|
+
);
|
|
50
|
+
} catch {
|
|
51
|
+
return passthrough("provider_error");
|
|
52
|
+
}
|
|
53
|
+
if (!Array.isArray(scores)) {
|
|
54
|
+
return passthrough("provider_error");
|
|
55
|
+
}
|
|
56
|
+
const scoreById = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const entry of scores) {
|
|
58
|
+
if (!entry || typeof entry.id !== "string" || typeof entry.score !== "number" || !Number.isFinite(entry.score)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
scoreById.set(entry.id, clampUnit(entry.score));
|
|
62
|
+
}
|
|
63
|
+
if (scoreById.size === 0) {
|
|
64
|
+
return passthrough("provider_error");
|
|
65
|
+
}
|
|
66
|
+
const rescoredIds = [];
|
|
67
|
+
const rescoredShortlist = shortlist.map((candidate) => {
|
|
68
|
+
const crossEncoderScore = scoreById.get(candidate.id);
|
|
69
|
+
if (crossEncoderScore === void 0) {
|
|
70
|
+
return {
|
|
71
|
+
candidate: candidate.candidate,
|
|
72
|
+
score: candidate.score,
|
|
73
|
+
id: candidate.id,
|
|
74
|
+
rescored: false
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const nextScore = clampUnit(alpha * crossEncoderScore + (1 - alpha) * candidate.score);
|
|
78
|
+
if (nextScore !== candidate.score) {
|
|
79
|
+
rescoredIds.push(candidate.id);
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
candidate: candidate.candidate,
|
|
83
|
+
score: nextScore,
|
|
84
|
+
crossEncoderScore,
|
|
85
|
+
id: candidate.id,
|
|
86
|
+
rescored: true
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
const reorderedShortlist = [...rescoredShortlist].sort((left, right) => {
|
|
90
|
+
if (left.score !== right.score) {
|
|
91
|
+
return right.score - left.score;
|
|
92
|
+
}
|
|
93
|
+
return shortlist.findIndex((candidate) => candidate.id === left.id) - shortlist.findIndex((candidate) => candidate.id === right.id);
|
|
94
|
+
});
|
|
95
|
+
const shortlistOutput = reorderedShortlist.map(({ candidate, score, crossEncoderScore }) => ({
|
|
96
|
+
candidate,
|
|
97
|
+
score,
|
|
98
|
+
...typeof crossEncoderScore === "number" ? { crossEncoderScore } : {}
|
|
99
|
+
}));
|
|
100
|
+
const tailOutput = tail.map((candidate) => ({
|
|
101
|
+
candidate: candidate.candidate,
|
|
102
|
+
score: candidate.score
|
|
103
|
+
}));
|
|
104
|
+
return {
|
|
105
|
+
applied: true,
|
|
106
|
+
k,
|
|
107
|
+
alpha,
|
|
108
|
+
latencyMs: elapsedMs(startedAt),
|
|
109
|
+
candidates: [...shortlistOutput, ...tailOutput],
|
|
110
|
+
rescoredIds
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function resolveTopK(value, total) {
|
|
114
|
+
if (total <= 0) {
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
const raw = typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : DEFAULT_CROSS_ENCODER_TOP_K;
|
|
118
|
+
return Math.max(1, Math.min(total, raw));
|
|
119
|
+
}
|
|
120
|
+
function resolveAlpha(value) {
|
|
121
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
122
|
+
return DEFAULT_CROSS_ENCODER_ALPHA;
|
|
123
|
+
}
|
|
124
|
+
return clampUnit(value);
|
|
125
|
+
}
|
|
126
|
+
function clampUnit(value) {
|
|
127
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
return value >= 1 ? 1 : value;
|
|
131
|
+
}
|
|
132
|
+
function elapsedMs(startedAt) {
|
|
133
|
+
return Math.max(0, Date.now() - startedAt);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/core/recall/entry-lineage.ts
|
|
137
|
+
function describeDurableLineageState(entry, nowMs) {
|
|
138
|
+
if (entry.superseded_by) {
|
|
139
|
+
return "superseded";
|
|
140
|
+
}
|
|
141
|
+
if (!isCurrentlyValidMemory(entry, nowMs)) {
|
|
142
|
+
return "historical";
|
|
143
|
+
}
|
|
144
|
+
return "current";
|
|
145
|
+
}
|
|
146
|
+
function formatDurableClaimLifecycle(entry) {
|
|
147
|
+
const status = resolveKeyedDurableLifecycleStatus(entry);
|
|
148
|
+
return status === "no_key" ? "no-key" : status;
|
|
149
|
+
}
|
|
150
|
+
function summarizeClaimFamilyTransition(entries, nowMs) {
|
|
151
|
+
const current = entries.find((entry) => isCurrentlyValidMemory(entry, nowMs));
|
|
152
|
+
const prior = [...entries].reverse().find((entry) => entry.id !== current?.id && isHistoricalFamilyMember(entry, nowMs));
|
|
153
|
+
if (current && prior) {
|
|
154
|
+
return `${prior.id} -> ${current.id}`;
|
|
155
|
+
}
|
|
156
|
+
if (prior) {
|
|
157
|
+
return `${prior.id} is historical with no current sibling in the traced family`;
|
|
158
|
+
}
|
|
159
|
+
if (current) {
|
|
160
|
+
return `${current.id} is the only current sibling in the traced family`;
|
|
161
|
+
}
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
function isHistoricalFamilyMember(entry, nowMs) {
|
|
165
|
+
if (entry.superseded_by) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
return !isCurrentlyValidMemory(entry, nowMs);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/core/recall/fusion.ts
|
|
172
|
+
var DEFAULT_RRF_RANK_CONSTANT = 60;
|
|
173
|
+
var DEFAULT_RRF_SMALL_POOL_RANK_CONSTANT = 8;
|
|
174
|
+
var SMALL_POOL_RRF_POOL_SIZE = 4;
|
|
175
|
+
function rrfFuse(channels, rankConstant = DEFAULT_RRF_RANK_CONSTANT) {
|
|
176
|
+
const k = sanitizeRankConstant(rankConstant);
|
|
177
|
+
const nonEmptyChannels = channels.filter((channel) => channel.length > 0);
|
|
178
|
+
const scores = /* @__PURE__ */ new Map();
|
|
179
|
+
if (nonEmptyChannels.length === 0) {
|
|
180
|
+
return scores;
|
|
181
|
+
}
|
|
182
|
+
for (const channel of nonEmptyChannels) {
|
|
183
|
+
const seenInChannel = /* @__PURE__ */ new Set();
|
|
184
|
+
let compactedRank = 0;
|
|
185
|
+
for (const id of channel) {
|
|
186
|
+
if (id === void 0 || seenInChannel.has(id)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
seenInChannel.add(id);
|
|
190
|
+
const contribution = 1 / (compactedRank + k);
|
|
191
|
+
scores.set(id, (scores.get(id) ?? 0) + contribution);
|
|
192
|
+
compactedRank += 1;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const normalizationDenominator = nonEmptyChannels.length * (1 / k);
|
|
196
|
+
for (const [id, score] of scores) {
|
|
197
|
+
scores.set(id, clampUnit2(score / normalizationDenominator));
|
|
198
|
+
}
|
|
199
|
+
return scores;
|
|
200
|
+
}
|
|
201
|
+
function rrfFuseVectorLexical(vectorRanks, lexicalRanks, rankConstant = DEFAULT_RRF_RANK_CONSTANT) {
|
|
202
|
+
return rrfFuse([vectorRanks, lexicalRanks], rankConstant);
|
|
203
|
+
}
|
|
204
|
+
function sanitizeRankConstant(value) {
|
|
205
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
206
|
+
return DEFAULT_RRF_RANK_CONSTANT;
|
|
207
|
+
}
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
function clampUnit2(value) {
|
|
211
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
return value >= 1 ? 1 : value;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/core/recall/lexical.ts
|
|
218
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
219
|
+
"the",
|
|
220
|
+
"a",
|
|
221
|
+
"an",
|
|
222
|
+
"is",
|
|
223
|
+
"are",
|
|
224
|
+
"was",
|
|
225
|
+
"were",
|
|
226
|
+
"be",
|
|
227
|
+
"been",
|
|
228
|
+
"being",
|
|
229
|
+
"have",
|
|
230
|
+
"has",
|
|
231
|
+
"had",
|
|
232
|
+
"do",
|
|
233
|
+
"does",
|
|
234
|
+
"did",
|
|
235
|
+
"will",
|
|
236
|
+
"would",
|
|
237
|
+
"could",
|
|
238
|
+
"should",
|
|
239
|
+
"may",
|
|
240
|
+
"might",
|
|
241
|
+
"shall",
|
|
242
|
+
"can",
|
|
243
|
+
"need",
|
|
244
|
+
"must",
|
|
245
|
+
"i",
|
|
246
|
+
"me",
|
|
247
|
+
"my",
|
|
248
|
+
"we",
|
|
249
|
+
"our",
|
|
250
|
+
"you",
|
|
251
|
+
"your",
|
|
252
|
+
"he",
|
|
253
|
+
"him",
|
|
254
|
+
"his",
|
|
255
|
+
"she",
|
|
256
|
+
"her",
|
|
257
|
+
"it",
|
|
258
|
+
"its",
|
|
259
|
+
"they",
|
|
260
|
+
"them",
|
|
261
|
+
"their",
|
|
262
|
+
"this",
|
|
263
|
+
"that",
|
|
264
|
+
"these",
|
|
265
|
+
"those",
|
|
266
|
+
"what",
|
|
267
|
+
"which",
|
|
268
|
+
"who",
|
|
269
|
+
"whom",
|
|
270
|
+
"in",
|
|
271
|
+
"on",
|
|
272
|
+
"at",
|
|
273
|
+
"to",
|
|
274
|
+
"for",
|
|
275
|
+
"of",
|
|
276
|
+
"with",
|
|
277
|
+
"by",
|
|
278
|
+
"from",
|
|
279
|
+
"up",
|
|
280
|
+
"about",
|
|
281
|
+
"into",
|
|
282
|
+
"through",
|
|
283
|
+
"during",
|
|
284
|
+
"before",
|
|
285
|
+
"after",
|
|
286
|
+
"above",
|
|
287
|
+
"below",
|
|
288
|
+
"and",
|
|
289
|
+
"or",
|
|
290
|
+
"but",
|
|
291
|
+
"not",
|
|
292
|
+
"no",
|
|
293
|
+
"nor",
|
|
294
|
+
"so",
|
|
295
|
+
"if",
|
|
296
|
+
"then",
|
|
297
|
+
"else",
|
|
298
|
+
"when",
|
|
299
|
+
"where",
|
|
300
|
+
"how",
|
|
301
|
+
"all",
|
|
302
|
+
"each",
|
|
303
|
+
"every",
|
|
304
|
+
"both",
|
|
305
|
+
"few",
|
|
306
|
+
"more",
|
|
307
|
+
"some",
|
|
308
|
+
"any",
|
|
309
|
+
"other",
|
|
310
|
+
"than"
|
|
311
|
+
]);
|
|
312
|
+
var FTS_OPERATOR_TOKENS = /* @__PURE__ */ new Set(["or", "not", "near"]);
|
|
313
|
+
var LEXICAL_TOKEN_PATTERN = /[\p{L}\p{N}][\p{L}\p{N}._-]*/gu;
|
|
314
|
+
var TOKEN_EDGE_PUNCTUATION_PATTERN = /^[._-]+|[._-]+$/gu;
|
|
315
|
+
function tokenize(text) {
|
|
316
|
+
const matches = normalizeLexicalText(text).match(LEXICAL_TOKEN_PATTERN) ?? [];
|
|
317
|
+
return matches.map((token) => token.replace(TOKEN_EDGE_PUNCTUATION_PATTERN, "")).filter((token) => token.length >= 2 && !STOP_WORDS.has(token));
|
|
318
|
+
}
|
|
319
|
+
function buildLexicalPlan(text) {
|
|
320
|
+
const trimmed = text.trim();
|
|
321
|
+
if (trimmed.length === 0) {
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
const tokens = tokenize(trimmed).filter((token) => !FTS_OPERATOR_TOKENS.has(token));
|
|
325
|
+
if (tokens.length === 0) {
|
|
326
|
+
return [
|
|
327
|
+
{
|
|
328
|
+
tier: "exact",
|
|
329
|
+
text: trimmed
|
|
330
|
+
}
|
|
331
|
+
];
|
|
332
|
+
}
|
|
333
|
+
if (tokens.length === 1) {
|
|
334
|
+
return [
|
|
335
|
+
{
|
|
336
|
+
tier: "exact",
|
|
337
|
+
text: trimmed
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
tier: "all_tokens",
|
|
341
|
+
tokens
|
|
342
|
+
}
|
|
343
|
+
];
|
|
344
|
+
}
|
|
345
|
+
return [
|
|
346
|
+
{
|
|
347
|
+
tier: "exact",
|
|
348
|
+
text: trimmed
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
tier: "all_tokens",
|
|
352
|
+
tokens
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
tier: "any_tokens",
|
|
356
|
+
tokens
|
|
357
|
+
}
|
|
358
|
+
];
|
|
359
|
+
}
|
|
360
|
+
function computeLexicalScore(query, subject, content) {
|
|
361
|
+
const queryTokens = tokenize(query);
|
|
362
|
+
const subjectTokens = tokenize(subject);
|
|
363
|
+
const contentTokens = tokenize(content);
|
|
364
|
+
const subjectTokenSet = new Set(subjectTokens);
|
|
365
|
+
const contentTokenSet = new Set(contentTokens);
|
|
366
|
+
const tokenOverlap = queryTokens.length === 0 ? 0 : queryTokens.filter((token) => subjectTokenSet.has(token) || contentTokenSet.has(token)).length / queryTokens.length;
|
|
367
|
+
const phraseMatches = countPhraseMatches(queryTokens, subjectTokens, contentTokens);
|
|
368
|
+
const phraseBonus = Math.min(0.4, phraseMatches * 0.2);
|
|
369
|
+
const subjectBonus = normalizeText(query) === normalizeText(subject) && normalizeText(query).length > 0 ? 0.3 : 0;
|
|
370
|
+
return Math.min(1, tokenOverlap + phraseBonus + subjectBonus);
|
|
371
|
+
}
|
|
372
|
+
var normalizeText = (text) => normalizeLexicalText(text).trim();
|
|
373
|
+
var normalizeLexicalText = (text) => text.normalize("NFKC").toLocaleLowerCase();
|
|
374
|
+
var countPhraseMatches = (queryTokens, subjectTokens, contentTokens) => {
|
|
375
|
+
if (queryTokens.length < 2) {
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
378
|
+
const matchedPhrases = /* @__PURE__ */ new Set();
|
|
379
|
+
for (let size = 2; size <= queryTokens.length; size += 1) {
|
|
380
|
+
for (let index = 0; index + size <= queryTokens.length; index += 1) {
|
|
381
|
+
const phraseTokens = queryTokens.slice(index, index + size);
|
|
382
|
+
if (hasConsecutivePhrase(subjectTokens, phraseTokens) || hasConsecutivePhrase(contentTokens, phraseTokens)) {
|
|
383
|
+
matchedPhrases.add(phraseTokens.join(" "));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return matchedPhrases.size;
|
|
388
|
+
};
|
|
389
|
+
var hasConsecutivePhrase = (haystack, needle) => {
|
|
390
|
+
if (needle.length === 0 || haystack.length < needle.length) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
for (let index = 0; index + needle.length <= haystack.length; index += 1) {
|
|
394
|
+
let matches = true;
|
|
395
|
+
for (let offset = 0; offset < needle.length; offset += 1) {
|
|
396
|
+
if (haystack[index + offset] !== needle[offset]) {
|
|
397
|
+
matches = false;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (matches) {
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// src/core/recall/scoring.ts
|
|
409
|
+
var DAY_IN_MILLISECONDS = 1e3 * 60 * 60 * 24;
|
|
410
|
+
var IMPORTANCE_FLOOR = 0.4;
|
|
411
|
+
var RELEVANCE_WEIGHT = 0.5;
|
|
412
|
+
var RECENCY_WEIGHT = 0.25;
|
|
413
|
+
var IMPORTANCE_WEIGHT = 0.25;
|
|
414
|
+
function recencyScore(createdAt, expiry, now = /* @__PURE__ */ new Date()) {
|
|
415
|
+
if (expiry === "core") {
|
|
416
|
+
return 1;
|
|
417
|
+
}
|
|
418
|
+
const createdDate = asValidDate(createdAt);
|
|
419
|
+
const nowDate = asValidDate(now);
|
|
420
|
+
if (!createdDate || !nowDate) {
|
|
421
|
+
return 0;
|
|
422
|
+
}
|
|
423
|
+
const halfLifeDays = expiry === "permanent" ? 365 : 30;
|
|
424
|
+
const daysOld = Math.max(0, (nowDate.getTime() - createdDate.getTime()) / DAY_IN_MILLISECONDS);
|
|
425
|
+
return clampUnit3(Math.pow(0.5, daysOld / halfLifeDays));
|
|
426
|
+
}
|
|
427
|
+
function gaussianRecency(createdAt, aroundDate, radiusDays) {
|
|
428
|
+
const createdDate = asValidDate(createdAt);
|
|
429
|
+
const anchorDate = asValidDate(aroundDate);
|
|
430
|
+
const normalizedRadius = sanitizeNonNegative(radiusDays);
|
|
431
|
+
if (!createdDate || !anchorDate) {
|
|
432
|
+
return 0;
|
|
433
|
+
}
|
|
434
|
+
if (normalizedRadius <= 0) {
|
|
435
|
+
return createdDate.getTime() === anchorDate.getTime() ? 1 : 0;
|
|
436
|
+
}
|
|
437
|
+
const daysDelta = Math.abs(createdDate.getTime() - anchorDate.getTime()) / DAY_IN_MILLISECONDS;
|
|
438
|
+
return clampUnit3(Math.exp(-0.5 * (daysDelta / normalizedRadius) ** 2));
|
|
439
|
+
}
|
|
440
|
+
function importanceScore(importance) {
|
|
441
|
+
const clampedImportance = clampRange(sanitizeNonNegative(importance), 1, 10);
|
|
442
|
+
return clampUnit3(IMPORTANCE_FLOOR + (clampedImportance - 1) / 9 * (1 - IMPORTANCE_FLOOR));
|
|
443
|
+
}
|
|
444
|
+
function scoreCandidate(params) {
|
|
445
|
+
const vector = clampUnit3(sanitizeNonNegative(params.vectorSim));
|
|
446
|
+
const lexical = clampUnit3(sanitizeNonNegative(params.lexical));
|
|
447
|
+
const recency = clampUnit3(sanitizeNonNegative(params.recency));
|
|
448
|
+
const importance = clampUnit3(sanitizeNonNegative(params.importance));
|
|
449
|
+
const relevance = clampUnit3(sanitizeNonNegative(params.relevance));
|
|
450
|
+
const score = clampUnit3(relevance * RELEVANCE_WEIGHT + recency * RECENCY_WEIGHT + importance * IMPORTANCE_WEIGHT);
|
|
451
|
+
return {
|
|
452
|
+
score,
|
|
453
|
+
scores: {
|
|
454
|
+
relevance,
|
|
455
|
+
vector,
|
|
456
|
+
lexical,
|
|
457
|
+
recency,
|
|
458
|
+
importance
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function cosineSimilarity(left, right) {
|
|
463
|
+
const size = Math.min(left.length, right.length);
|
|
464
|
+
if (size === 0) {
|
|
465
|
+
return 0;
|
|
466
|
+
}
|
|
467
|
+
let dot = 0;
|
|
468
|
+
let leftNorm = 0;
|
|
469
|
+
let rightNorm = 0;
|
|
470
|
+
for (let index = 0; index < size; index += 1) {
|
|
471
|
+
const leftValue = sanitizeFinite(left[index]);
|
|
472
|
+
const rightValue = sanitizeFinite(right[index]);
|
|
473
|
+
dot += leftValue * rightValue;
|
|
474
|
+
leftNorm += leftValue * leftValue;
|
|
475
|
+
rightNorm += rightValue * rightValue;
|
|
476
|
+
}
|
|
477
|
+
if (leftNorm <= 0 || rightNorm <= 0) {
|
|
478
|
+
return 0;
|
|
479
|
+
}
|
|
480
|
+
return clampUnit3(dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm)));
|
|
481
|
+
}
|
|
482
|
+
var asValidDate = (value) => {
|
|
483
|
+
const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
|
|
484
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
485
|
+
};
|
|
486
|
+
var clampUnit3 = (value) => clampRange(sanitizeNonNegative(value), 0, 1);
|
|
487
|
+
var clampRange = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
488
|
+
var sanitizeFinite = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
489
|
+
var sanitizeNonNegative = (value) => Math.max(0, sanitizeFinite(value));
|
|
490
|
+
|
|
491
|
+
// src/core/recall/mmr.ts
|
|
492
|
+
var DEFAULT_MMR_LAMBDA = 0.7;
|
|
493
|
+
var DEFAULT_MMR_MIN_POOL_SIZE = 4;
|
|
494
|
+
var NEAR_DUPLICATE_SIMILARITY = 0.95;
|
|
495
|
+
function maximalMarginalRelevance(options) {
|
|
496
|
+
const lambda = clampUnit4(sanitizeNumber(options.lambda, DEFAULT_MMR_LAMBDA));
|
|
497
|
+
const inputIds = options.candidates.map((candidate) => candidate.id);
|
|
498
|
+
const limit = resolveLimit(options.limit, inputIds.length);
|
|
499
|
+
const minPoolSize = resolveMinPoolSize(options.minPoolSize);
|
|
500
|
+
const embeddedCandidates = options.candidates.filter((candidate) => hasUsableEmbedding(candidate.embedding));
|
|
501
|
+
const unembeddedIds = options.candidates.filter((candidate) => !hasUsableEmbedding(candidate.embedding)).map((candidate) => candidate.id);
|
|
502
|
+
const poolBelowGate = minPoolSize > 0 && options.candidates.length <= minPoolSize;
|
|
503
|
+
const canApplyMmr = !poolBelowGate && options.queryVector.length > 0 && embeddedCandidates.length >= 2;
|
|
504
|
+
if (!canApplyMmr) {
|
|
505
|
+
return {
|
|
506
|
+
applied: false,
|
|
507
|
+
lambda,
|
|
508
|
+
orderedIds: sliceOrDefault(inputIds, limit),
|
|
509
|
+
droppedDuplicateCount: 0,
|
|
510
|
+
reorderedIds: []
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const relevanceById = /* @__PURE__ */ new Map();
|
|
514
|
+
for (const candidate of embeddedCandidates) {
|
|
515
|
+
const overriddenRelevance = sanitizeUnit(candidate.relevance);
|
|
516
|
+
const derivedRelevance = overriddenRelevance ?? cosineSimilarity(options.queryVector, candidate.embedding ?? []);
|
|
517
|
+
relevanceById.set(candidate.id, derivedRelevance);
|
|
518
|
+
}
|
|
519
|
+
const pairwiseMaxById = computePairwiseMaxSimilarity(embeddedCandidates);
|
|
520
|
+
const mmrScoreById = /* @__PURE__ */ new Map();
|
|
521
|
+
for (const candidate of embeddedCandidates) {
|
|
522
|
+
const relevance = relevanceById.get(candidate.id) ?? 0;
|
|
523
|
+
const maxPairwise = pairwiseMaxById.get(candidate.id) ?? 0;
|
|
524
|
+
mmrScoreById.set(candidate.id, lambda * relevance - (1 - lambda) * maxPairwise);
|
|
525
|
+
}
|
|
526
|
+
const rankedEmbeddedIds = [...embeddedCandidates].sort((left, right) => {
|
|
527
|
+
const leftScore = mmrScoreById.get(left.id) ?? 0;
|
|
528
|
+
const rightScore = mmrScoreById.get(right.id) ?? 0;
|
|
529
|
+
if (leftScore !== rightScore) {
|
|
530
|
+
return rightScore - leftScore;
|
|
531
|
+
}
|
|
532
|
+
return inputIds.indexOf(left.id) - inputIds.indexOf(right.id);
|
|
533
|
+
}).map((candidate) => candidate.id);
|
|
534
|
+
const orderedIds = sliceOrDefault([...rankedEmbeddedIds, ...unembeddedIds], limit);
|
|
535
|
+
const reorderedIds = inputIds.filter((id, index) => orderedIds[index] !== id);
|
|
536
|
+
const droppedDuplicateCount = countDroppedDuplicates(rankedEmbeddedIds, inputIds, pairwiseMaxById);
|
|
537
|
+
return {
|
|
538
|
+
applied: true,
|
|
539
|
+
lambda,
|
|
540
|
+
orderedIds,
|
|
541
|
+
droppedDuplicateCount,
|
|
542
|
+
reorderedIds
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function computePairwiseMaxSimilarity(embeddedCandidates) {
|
|
546
|
+
const result = /* @__PURE__ */ new Map();
|
|
547
|
+
for (const candidate of embeddedCandidates) {
|
|
548
|
+
result.set(candidate.id, 0);
|
|
549
|
+
}
|
|
550
|
+
for (let outer = 0; outer < embeddedCandidates.length; outer += 1) {
|
|
551
|
+
for (let inner = outer + 1; inner < embeddedCandidates.length; inner += 1) {
|
|
552
|
+
const left = embeddedCandidates[outer];
|
|
553
|
+
const right = embeddedCandidates[inner];
|
|
554
|
+
const similarity = cosineSimilarity(left.embedding ?? [], right.embedding ?? []);
|
|
555
|
+
if (similarity > (result.get(left.id) ?? 0)) {
|
|
556
|
+
result.set(left.id, similarity);
|
|
557
|
+
}
|
|
558
|
+
if (similarity > (result.get(right.id) ?? 0)) {
|
|
559
|
+
result.set(right.id, similarity);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
function countDroppedDuplicates(rankedEmbeddedIds, inputIds, pairwiseMaxById) {
|
|
566
|
+
const inputRankById = /* @__PURE__ */ new Map();
|
|
567
|
+
inputIds.forEach((id, index) => inputRankById.set(id, index));
|
|
568
|
+
let droppedDuplicates = 0;
|
|
569
|
+
rankedEmbeddedIds.forEach((id, mmrRank) => {
|
|
570
|
+
const inputRank = inputRankById.get(id);
|
|
571
|
+
if (inputRank === void 0 || mmrRank <= inputRank) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const maxSimilarity = pairwiseMaxById.get(id) ?? 0;
|
|
575
|
+
if (maxSimilarity >= NEAR_DUPLICATE_SIMILARITY) {
|
|
576
|
+
droppedDuplicates += 1;
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
return droppedDuplicates;
|
|
580
|
+
}
|
|
581
|
+
function sliceOrDefault(ids, limit) {
|
|
582
|
+
if (limit === null || limit >= ids.length) {
|
|
583
|
+
return [...ids];
|
|
584
|
+
}
|
|
585
|
+
return ids.slice(0, limit);
|
|
586
|
+
}
|
|
587
|
+
function resolveLimit(value, totalCandidates) {
|
|
588
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
return Math.min(totalCandidates, Math.floor(value));
|
|
592
|
+
}
|
|
593
|
+
function hasUsableEmbedding(embedding) {
|
|
594
|
+
return Array.isArray(embedding) && embedding.length > 0;
|
|
595
|
+
}
|
|
596
|
+
function clampUnit4(value) {
|
|
597
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
598
|
+
return 0;
|
|
599
|
+
}
|
|
600
|
+
return value >= 1 ? 1 : value;
|
|
601
|
+
}
|
|
602
|
+
function resolveMinPoolSize(value) {
|
|
603
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
604
|
+
return DEFAULT_MMR_MIN_POOL_SIZE;
|
|
605
|
+
}
|
|
606
|
+
if (value < 0) {
|
|
607
|
+
return DEFAULT_MMR_MIN_POOL_SIZE;
|
|
608
|
+
}
|
|
609
|
+
return Math.floor(value);
|
|
610
|
+
}
|
|
611
|
+
function sanitizeNumber(value, fallback) {
|
|
612
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
613
|
+
return fallback;
|
|
614
|
+
}
|
|
615
|
+
return value;
|
|
616
|
+
}
|
|
617
|
+
function sanitizeUnit(value) {
|
|
618
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
return clampUnit4(value);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/core/recall/neighborhood.ts
|
|
625
|
+
var DEFAULT_NEIGHBORHOOD_BUDGET = 24;
|
|
626
|
+
var DEFAULT_STRONG_SEED_TOP_N = 3;
|
|
627
|
+
var DEFAULT_STRONG_SEED_SCORE_GAP = 0.05;
|
|
628
|
+
var DEFAULT_SEEDED_RERANK_WEIGHT = 0.03;
|
|
629
|
+
function selectStrongSeeds(candidates, options = {}) {
|
|
630
|
+
if (candidates.length === 0) {
|
|
631
|
+
return [];
|
|
632
|
+
}
|
|
633
|
+
const topN = Math.max(1, sanitizeInteger(options.topN, DEFAULT_STRONG_SEED_TOP_N));
|
|
634
|
+
const floor = sanitizeUnit2(options.scoreGapFloor ?? DEFAULT_STRONG_SEED_SCORE_GAP);
|
|
635
|
+
const ordered = [...candidates].sort((left, right) => right.score - left.score);
|
|
636
|
+
const leader = ordered[0];
|
|
637
|
+
if (!leader || leader.score <= 0) {
|
|
638
|
+
return [];
|
|
639
|
+
}
|
|
640
|
+
const followerScore = ordered[topN]?.score ?? 0;
|
|
641
|
+
if (leader.score - followerScore < floor) {
|
|
642
|
+
return [];
|
|
643
|
+
}
|
|
644
|
+
const cutoff = Math.max(leader.score - floor, followerScore);
|
|
645
|
+
return ordered.slice(0, topN).filter((candidate) => candidate.score >= cutoff);
|
|
646
|
+
}
|
|
647
|
+
function seededRerank(candidates, seeds, sharesLineage, options = {}) {
|
|
648
|
+
const boostedIds = [];
|
|
649
|
+
if (candidates.length === 0 || seeds.length === 0) {
|
|
650
|
+
return { candidates: [...candidates], boostedIds };
|
|
651
|
+
}
|
|
652
|
+
const weight = sanitizeUnit2(options.weight ?? DEFAULT_SEEDED_RERANK_WEIGHT);
|
|
653
|
+
if (weight <= 0) {
|
|
654
|
+
return { candidates: [...candidates], boostedIds };
|
|
655
|
+
}
|
|
656
|
+
const seedIds = new Set(seeds.map((seed) => seed.id));
|
|
657
|
+
const reranked = candidates.map((candidate) => {
|
|
658
|
+
if (seedIds.has(candidate.id)) {
|
|
659
|
+
return candidate;
|
|
660
|
+
}
|
|
661
|
+
const match = seeds.find((seed) => seed.id !== candidate.id && sharesLineage(candidate, seed));
|
|
662
|
+
if (!match) {
|
|
663
|
+
return candidate;
|
|
664
|
+
}
|
|
665
|
+
boostedIds.push(candidate.id);
|
|
666
|
+
return {
|
|
667
|
+
...candidate,
|
|
668
|
+
score: clampUnit5(candidate.score + weight)
|
|
669
|
+
};
|
|
670
|
+
});
|
|
671
|
+
return { candidates: reranked, boostedIds };
|
|
672
|
+
}
|
|
673
|
+
function sharesEntryLineage(candidate, seed) {
|
|
674
|
+
if (candidate.id === seed.id) {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
if (candidate.claim_key && seed.claim_key && candidate.claim_key === seed.claim_key) {
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
if (candidate.superseded_by === seed.id || seed.superseded_by === candidate.id) {
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
return sharesTopicPrefix(candidate.subject, seed.subject);
|
|
684
|
+
}
|
|
685
|
+
function sharesEpisodeLineage(candidate, seed) {
|
|
686
|
+
if (candidate.id === seed.id) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
if (candidate.source === seed.source && candidate.sourceId !== void 0 && candidate.sourceId === seed.sourceId) {
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
if (candidate.transcriptHash && seed.transcriptHash && candidate.transcriptHash === seed.transcriptHash) {
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
function sharesProcedureLineage(candidate, seed) {
|
|
698
|
+
if (candidate.id === seed.id) {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
return candidate.procedure_key === seed.procedure_key;
|
|
702
|
+
}
|
|
703
|
+
var TOPIC_PREFIX_SHARED_MIN = 2;
|
|
704
|
+
var TOPIC_PREFIX_COVERAGE_MIN = 0.6;
|
|
705
|
+
function sharesTopicPrefix(leftSubject, rightSubject) {
|
|
706
|
+
const leftTokens = tokenize(leftSubject);
|
|
707
|
+
const rightTokens = tokenize(rightSubject);
|
|
708
|
+
if (leftTokens.length === 0 || rightTokens.length === 0) {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
const length = Math.min(leftTokens.length, rightTokens.length);
|
|
712
|
+
let shared = 0;
|
|
713
|
+
for (let index = 0; index < length; index += 1) {
|
|
714
|
+
if (leftTokens[index] !== rightTokens[index]) {
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
shared += 1;
|
|
718
|
+
}
|
|
719
|
+
if (shared < TOPIC_PREFIX_SHARED_MIN) {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
return shared / length >= TOPIC_PREFIX_COVERAGE_MIN;
|
|
723
|
+
}
|
|
724
|
+
function clampUnit5(value) {
|
|
725
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
726
|
+
return 0;
|
|
727
|
+
}
|
|
728
|
+
return value >= 1 ? 1 : value;
|
|
729
|
+
}
|
|
730
|
+
function sanitizeUnit2(value) {
|
|
731
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
732
|
+
return 0;
|
|
733
|
+
}
|
|
734
|
+
return Math.min(1, value);
|
|
735
|
+
}
|
|
736
|
+
function sanitizeInteger(value, fallback) {
|
|
737
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
738
|
+
return fallback;
|
|
739
|
+
}
|
|
740
|
+
return Math.floor(value);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/core/claim-slot-policy.ts
|
|
744
|
+
var MULTIVALUED_ATTRIBUTE_HEADS = /* @__PURE__ */ new Set(["access", "dependency", "guide", "integration", "preference", "requirement", "support"]);
|
|
745
|
+
function resolveClaimSlotPolicy(claimKey, config) {
|
|
746
|
+
const normalized = normalizeClaimKey(claimKey ?? "");
|
|
747
|
+
if (!normalized.ok) {
|
|
748
|
+
return {
|
|
749
|
+
policy: "exclusive",
|
|
750
|
+
reason: "No canonical claim key was available, so the slot policy defaulted to exclusive."
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
const { claimKey: canonicalClaimKey, entity, attribute } = normalized.value;
|
|
754
|
+
const attributeHead = attribute.split("_")[0] ?? attribute;
|
|
755
|
+
const configuredPolicy = resolveConfiguredAttributeHeadPolicy(attributeHead, config);
|
|
756
|
+
if (configuredPolicy) {
|
|
757
|
+
return {
|
|
758
|
+
claimKey: canonicalClaimKey,
|
|
759
|
+
entity,
|
|
760
|
+
attribute,
|
|
761
|
+
attributeHead,
|
|
762
|
+
policy: configuredPolicy,
|
|
763
|
+
reason: `Attribute head "${attributeHead}" is configured as ${configuredPolicy} by runtime policy.`
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
if (MULTIVALUED_ATTRIBUTE_HEADS.has(attributeHead)) {
|
|
767
|
+
return {
|
|
768
|
+
claimKey: canonicalClaimKey,
|
|
769
|
+
entity,
|
|
770
|
+
attribute,
|
|
771
|
+
attributeHead,
|
|
772
|
+
policy: "multivalued",
|
|
773
|
+
reason: `Attribute head "${attributeHead}" is registered as multivalued.`
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
claimKey: canonicalClaimKey,
|
|
778
|
+
entity,
|
|
779
|
+
attribute,
|
|
780
|
+
attributeHead,
|
|
781
|
+
policy: "exclusive",
|
|
782
|
+
reason: `Attribute head "${attributeHead}" defaults to exclusive current-state shaping.`
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
function resolveConfiguredAttributeHeadPolicy(attributeHead, config) {
|
|
786
|
+
const configuredPolicy = config?.attributeHeads?.[attributeHead];
|
|
787
|
+
if (configuredPolicy) {
|
|
788
|
+
return configuredPolicy;
|
|
789
|
+
}
|
|
790
|
+
const loweredAttributeHead = attributeHead.toLowerCase();
|
|
791
|
+
if (loweredAttributeHead === attributeHead) {
|
|
792
|
+
return void 0;
|
|
793
|
+
}
|
|
794
|
+
return config?.attributeHeads?.[loweredAttributeHead];
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/core/recall/temporal.ts
|
|
798
|
+
var DAY_IN_MILLISECONDS2 = 1e3 * 60 * 60 * 24;
|
|
799
|
+
var MONTH_INDEX = /* @__PURE__ */ new Map([
|
|
800
|
+
["january", 0],
|
|
801
|
+
["february", 1],
|
|
802
|
+
["march", 2],
|
|
803
|
+
["april", 3],
|
|
804
|
+
["may", 4],
|
|
805
|
+
["june", 5],
|
|
806
|
+
["july", 6],
|
|
807
|
+
["august", 7],
|
|
808
|
+
["september", 8],
|
|
809
|
+
["october", 9],
|
|
810
|
+
["november", 10],
|
|
811
|
+
["december", 11]
|
|
812
|
+
]);
|
|
813
|
+
function inferAroundDate(text, now = /* @__PURE__ */ new Date()) {
|
|
814
|
+
const normalized = text.trim().toLowerCase();
|
|
815
|
+
const referenceNow = asValidDate2(now);
|
|
816
|
+
if (normalized.length === 0 || !referenceNow) {
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
let inferred = null;
|
|
820
|
+
if (/\byesterday\b/.test(normalized)) {
|
|
821
|
+
inferred = offsetDays(referenceNow, 1);
|
|
822
|
+
} else if (/\blast week\b/.test(normalized)) {
|
|
823
|
+
inferred = offsetDays(referenceNow, 7);
|
|
824
|
+
} else if (/\blast month\b/.test(normalized)) {
|
|
825
|
+
inferred = offsetDays(referenceNow, 30);
|
|
826
|
+
} else if (/\blast year\b/.test(normalized)) {
|
|
827
|
+
inferred = offsetDays(referenceNow, 365);
|
|
828
|
+
} else if (/\bthis week\b/.test(normalized)) {
|
|
829
|
+
inferred = offsetDays(referenceNow, 3);
|
|
830
|
+
} else if (/\bthis month\b/.test(normalized)) {
|
|
831
|
+
inferred = offsetDays(referenceNow, 15);
|
|
832
|
+
} else {
|
|
833
|
+
const relativeMatch = normalized.match(/\b(\d+)\s+(day|days|week|weeks|month|months)\s+ago\b/);
|
|
834
|
+
if (relativeMatch) {
|
|
835
|
+
const amount = Number(relativeMatch[1]);
|
|
836
|
+
const unit = relativeMatch[2];
|
|
837
|
+
const multiplier = unit?.startsWith("week") ? 7 : unit?.startsWith("month") ? 30 : 1;
|
|
838
|
+
inferred = Number.isFinite(amount) ? offsetDays(referenceNow, amount * multiplier) : null;
|
|
839
|
+
} else {
|
|
840
|
+
const monthMatch = normalized.match(/\bin\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b/);
|
|
841
|
+
if (monthMatch?.[1]) {
|
|
842
|
+
inferred = inferMonthAnchor(monthMatch[1], referenceNow);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (!inferred) {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
return inferred.getTime() > referenceNow.getTime() ? new Date(referenceNow.getTime()) : inferred;
|
|
850
|
+
}
|
|
851
|
+
function parseRelativeDate(input, now = /* @__PURE__ */ new Date()) {
|
|
852
|
+
const trimmed = input.trim();
|
|
853
|
+
const referenceNow = asValidDate2(now);
|
|
854
|
+
if (trimmed.length === 0 || !referenceNow) {
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
const durationMatch = trimmed.match(/^(\d+)d$/i);
|
|
858
|
+
if (durationMatch?.[1]) {
|
|
859
|
+
const days = Number(durationMatch[1]);
|
|
860
|
+
return Number.isFinite(days) ? offsetDays(referenceNow, days) : null;
|
|
861
|
+
}
|
|
862
|
+
const parsed = new Date(trimmed);
|
|
863
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
864
|
+
}
|
|
865
|
+
var asValidDate2 = (value) => {
|
|
866
|
+
const date = new Date(value.getTime());
|
|
867
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
868
|
+
};
|
|
869
|
+
var offsetDays = (date, days) => new Date(date.getTime() - days * DAY_IN_MILLISECONDS2);
|
|
870
|
+
var inferMonthAnchor = (monthName, now) => {
|
|
871
|
+
const monthIndex = MONTH_INDEX.get(monthName);
|
|
872
|
+
if (monthIndex === void 0) {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
const year = monthIndex <= now.getUTCMonth() ? now.getUTCFullYear() : now.getUTCFullYear() - 1;
|
|
876
|
+
return new Date(Date.UTC(year, monthIndex, 15));
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
// src/core/recall/as-of-validity.ts
|
|
880
|
+
function resolveRecallValidAsOf(params) {
|
|
881
|
+
if (params.rankingProfile === "historical_state") {
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
return new Date(params.asOfDate ? params.asOfDate.getTime() : params.nowMs);
|
|
885
|
+
}
|
|
886
|
+
function applyAsOfValidityFilter(mergeOutcome, summary, params) {
|
|
887
|
+
const asOf = resolveRecallValidAsOf(params);
|
|
888
|
+
if (asOf === null) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const asOfMs = asOf.getTime();
|
|
892
|
+
const removed = /* @__PURE__ */ new Set();
|
|
893
|
+
for (const [id, candidate] of mergeOutcome.merged) {
|
|
894
|
+
if (!isWithinValidityWindow(candidate.entry.valid_from, candidate.entry.valid_to, asOfMs)) {
|
|
895
|
+
mergeOutcome.merged.delete(id);
|
|
896
|
+
removed.add(id);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
if (removed.size > 0) {
|
|
900
|
+
mergeOutcome.vectorRanks = mergeOutcome.vectorRanks.filter((id) => !removed.has(id));
|
|
901
|
+
mergeOutcome.ftsRanks = mergeOutcome.ftsRanks.filter((id) => !removed.has(id));
|
|
902
|
+
}
|
|
903
|
+
summary.filtering.asOfValidity = {
|
|
904
|
+
applied: true,
|
|
905
|
+
anchor: new Date(asOfMs).toISOString(),
|
|
906
|
+
source: params.asOfDate ? "explicit_as_of" : "now",
|
|
907
|
+
excludedCount: removed.size
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// src/core/recall/trace.ts
|
|
912
|
+
var NOOP_RECALL_TRACE_SINK = {
|
|
913
|
+
reportSummary() {
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
function createNoopRecallTraceSink() {
|
|
917
|
+
return NOOP_RECALL_TRACE_SINK;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/core/recall/search.ts
|
|
921
|
+
var HISTORICAL_NEIGHBORHOOD_FAMILIES = ["supersession_chain", "claim_key_sibling", "topic_family"];
|
|
922
|
+
var MIN_VECTOR_ONLY_EVIDENCE = 0.3;
|
|
923
|
+
var HISTORICAL_STATE_FLAT_RECENCY = 0.5;
|
|
924
|
+
var HISTORICAL_PREDECESSOR_BOOST = 0.08;
|
|
925
|
+
var HISTORICAL_STALE_PREDECESSOR_BOOST = 0.06;
|
|
926
|
+
var HISTORICAL_OLDER_STATE_BOOST = 0.08;
|
|
927
|
+
var HISTORICAL_LINEAGE_GAP_MARGIN = 0.02;
|
|
928
|
+
var HISTORICAL_LINEAGE_MAX_BONUS = 0.45;
|
|
929
|
+
var HISTORICAL_TOPIC_SHARED_PREFIX_MIN = 2;
|
|
930
|
+
var HISTORICAL_TOPIC_PREFIX_OF_CANDIDATE_MIN = 0.6;
|
|
931
|
+
var CLAIM_KEY_TENTATIVE_CURRENT_PENALTY = 0.08;
|
|
932
|
+
var CLAIM_KEY_REDUNDANT_TRUSTED_SLOT_PENALTY = 0.05;
|
|
933
|
+
var CLAIM_KEY_REDUNDANT_TRUSTED_SLOT_MAX_PENALTY = 0.15;
|
|
934
|
+
var QUERY_EMBEDDING_FAILURE_NOTICE = "Embeddings failed during recall, so Agenr fell back to lexical-only entry ranking.";
|
|
935
|
+
var VECTOR_SEARCH_FAILURE_NOTICE = "Vector search failed during recall, so Agenr continued with lexical entry candidates only.";
|
|
936
|
+
var ENTITY_ATTRIBUTE_IDENTITY_WRAPPERS = /* @__PURE__ */ new Set(["identity", "profile", "bio", "biography", "summary"]);
|
|
937
|
+
var WEAK_QUERY_GROUNDING_TOKENS = /* @__PURE__ */ new Set([
|
|
938
|
+
"earlier",
|
|
939
|
+
"last",
|
|
940
|
+
"mention",
|
|
941
|
+
"mentioned",
|
|
942
|
+
"number",
|
|
943
|
+
"order",
|
|
944
|
+
"remember",
|
|
945
|
+
"remind",
|
|
946
|
+
"reminder",
|
|
947
|
+
"run",
|
|
948
|
+
"runs",
|
|
949
|
+
"thing",
|
|
950
|
+
"time",
|
|
951
|
+
"use",
|
|
952
|
+
"uses",
|
|
953
|
+
"using"
|
|
954
|
+
]);
|
|
955
|
+
var WEAKLY_GROUNDED_REMINDER_PATTERN = /\b(earlier|last time|mention(?:ed)?|remember|remind(?:er)?)\b/iu;
|
|
956
|
+
var MIN_VECTOR_WITHOUT_GROUNDED_LEXICAL_SUPPORT = 0.45;
|
|
957
|
+
var GROUNDING_SORT_MAX_SCORE_GAP = 0.03;
|
|
958
|
+
async function recall(query, ports, options = {}) {
|
|
959
|
+
const text = query.text.trim();
|
|
960
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
961
|
+
const nowMs = now.getTime();
|
|
962
|
+
const limit = normalizeLimit(query.limit);
|
|
963
|
+
const threshold = normalizeThreshold(query.threshold);
|
|
964
|
+
const budget = normalizeBudget(query.budget);
|
|
965
|
+
const asOfDate = query.asOf ? parseAroundDate(query.asOf, now) : null;
|
|
966
|
+
const aroundDate = query.around !== void 0 ? parseAroundDate(query.around, now) : inferAroundDate(text, now);
|
|
967
|
+
const since = query.since ? parseRelativeDate(query.since, now) : null;
|
|
968
|
+
const until = query.until ? parseRelativeDate(query.until, now) : null;
|
|
969
|
+
const filters = buildDurableFilters(query.types, query.tags, since, until, {
|
|
970
|
+
asOfDate,
|
|
971
|
+
nowMs,
|
|
972
|
+
rankingProfile: query.rankingProfile
|
|
973
|
+
});
|
|
974
|
+
const trace = options.trace ?? createNoopRecallTraceSink();
|
|
975
|
+
const slotPolicyConfig = options.slotPolicyConfig;
|
|
976
|
+
const summary = buildRecallTraceSummary({
|
|
977
|
+
filters,
|
|
978
|
+
limit,
|
|
979
|
+
threshold,
|
|
980
|
+
budget,
|
|
981
|
+
asOfDate,
|
|
982
|
+
aroundDate,
|
|
983
|
+
aroundSource: query.around !== void 0 ? "explicit" : "inferred",
|
|
984
|
+
aroundRadius: aroundDate ? normalizeAroundRadius(query.aroundRadius) : void 0
|
|
985
|
+
});
|
|
986
|
+
let traceReported = false;
|
|
987
|
+
const reportTrace = (noResultReason) => {
|
|
988
|
+
if (traceReported) {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
traceReported = true;
|
|
992
|
+
finishRecallTrace(summary, trace, noResultReason);
|
|
993
|
+
};
|
|
994
|
+
if (text.length === 0) {
|
|
995
|
+
reportTrace("empty_query");
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
if (limit === 0) {
|
|
999
|
+
reportTrace("limit_zero");
|
|
1000
|
+
return [];
|
|
1001
|
+
}
|
|
1002
|
+
try {
|
|
1003
|
+
let queryEmbedding = [];
|
|
1004
|
+
try {
|
|
1005
|
+
queryEmbedding = await ports.embed(text);
|
|
1006
|
+
} catch {
|
|
1007
|
+
markRecallDegraded(summary, "query_embedding_failed", QUERY_EMBEDDING_FAILURE_NOTICE);
|
|
1008
|
+
}
|
|
1009
|
+
const vectorSearchLimit = limit * 4;
|
|
1010
|
+
const lexicalSearchLimit = limit * 2;
|
|
1011
|
+
const [vectorCandidates, ftsCandidates] = queryEmbedding.length > 0 ? await Promise.all([
|
|
1012
|
+
ports.vectorSearch({
|
|
1013
|
+
embedding: queryEmbedding,
|
|
1014
|
+
limit: vectorSearchLimit,
|
|
1015
|
+
filters
|
|
1016
|
+
}).catch(() => {
|
|
1017
|
+
markRecallDegraded(summary, "vector_search_failed", VECTOR_SEARCH_FAILURE_NOTICE);
|
|
1018
|
+
return [];
|
|
1019
|
+
}),
|
|
1020
|
+
ports.ftsSearch({
|
|
1021
|
+
text,
|
|
1022
|
+
limit: lexicalSearchLimit,
|
|
1023
|
+
filters
|
|
1024
|
+
})
|
|
1025
|
+
]) : [
|
|
1026
|
+
[],
|
|
1027
|
+
await ports.ftsSearch({
|
|
1028
|
+
text,
|
|
1029
|
+
limit: lexicalSearchLimit,
|
|
1030
|
+
filters
|
|
1031
|
+
})
|
|
1032
|
+
];
|
|
1033
|
+
summary.degraded.lexicalOnly = summary.degraded.active && queryEmbedding.length === 0;
|
|
1034
|
+
const mergeStartedAt = Date.now();
|
|
1035
|
+
const mergeOutcome = mergeCandidates(vectorCandidates, ftsCandidates);
|
|
1036
|
+
applyAsOfValidityFilter(mergeOutcome, summary, {
|
|
1037
|
+
rankingProfile: query.rankingProfile,
|
|
1038
|
+
asOfDate,
|
|
1039
|
+
nowMs
|
|
1040
|
+
});
|
|
1041
|
+
const neighborhoodEnabled = options.rankingPolicy?.neighborhood !== "disabled";
|
|
1042
|
+
const expansionRanks = neighborhoodEnabled ? await expandEntryNeighborhood(mergeOutcome.merged, queryEmbedding, ports, {
|
|
1043
|
+
rankingProfile: query.rankingProfile,
|
|
1044
|
+
neighborhoodTrace: summary.neighborhood
|
|
1045
|
+
}) : [];
|
|
1046
|
+
const relevanceByEntryId = resolveEntryRelevance({
|
|
1047
|
+
vectorRanks: mergeOutcome.vectorRanks,
|
|
1048
|
+
ftsRanks: mergeOutcome.ftsRanks,
|
|
1049
|
+
expansionRanks,
|
|
1050
|
+
policy: options.rankingPolicy,
|
|
1051
|
+
trace: summary.rrf
|
|
1052
|
+
});
|
|
1053
|
+
summary.candidateCounts.merged = mergeOutcome.merged.size;
|
|
1054
|
+
summary.timings.mergeCandidatesMs = elapsedMs2(mergeStartedAt);
|
|
1055
|
+
const scoreStartedAt = Date.now();
|
|
1056
|
+
const historicallyBoosted = applyHistoricalLineageBoosts(
|
|
1057
|
+
Array.from(mergeOutcome.merged.values()).map(
|
|
1058
|
+
(candidate) => scoreMergedCandidate(candidate, text, queryEmbedding, relevanceByEntryId.get(candidate.entry.id) ?? 0, {
|
|
1059
|
+
asOfDate,
|
|
1060
|
+
aroundDate,
|
|
1061
|
+
aroundRadius: query.aroundRadius,
|
|
1062
|
+
rankingProfile: query.rankingProfile,
|
|
1063
|
+
now
|
|
1064
|
+
})
|
|
1065
|
+
),
|
|
1066
|
+
{
|
|
1067
|
+
aroundDate,
|
|
1068
|
+
rankingProfile: query.rankingProfile,
|
|
1069
|
+
nowMs
|
|
1070
|
+
},
|
|
1071
|
+
summary.claimKey,
|
|
1072
|
+
slotPolicyConfig
|
|
1073
|
+
);
|
|
1074
|
+
const rerankedCandidates = neighborhoodEnabled ? applySeededEntryRerank(historicallyBoosted, summary.neighborhood) : historicallyBoosted;
|
|
1075
|
+
const shaped = applyClaimKeyResultShaping(rerankedCandidates, summary.claimKey, nowMs, slotPolicyConfig).sort((left, right) => right.score - left.score);
|
|
1076
|
+
const diversified = applyMmrDiversification(shaped, queryEmbedding, options.rankingPolicy, summary.mmr);
|
|
1077
|
+
const scored = await applyEntryCrossEncoderRerank(diversified, text, ports.crossEncoder, options.rankingPolicy, summary.crossEncoder);
|
|
1078
|
+
summary.timings.scoreCandidatesMs = elapsedMs2(scoreStartedAt);
|
|
1079
|
+
const thresholdStartedAt = Date.now();
|
|
1080
|
+
const thresholded = scored.filter((result) => hasSufficientReturnEvidence(result, query) && result.score >= threshold);
|
|
1081
|
+
summary.candidateCounts.thresholdQualified = thresholded.length;
|
|
1082
|
+
summary.timings.thresholdMs = elapsedMs2(thresholdStartedAt);
|
|
1083
|
+
if (thresholded.length === 0) {
|
|
1084
|
+
reportTrace(resolveNoResultReason(summary, scored.length === 0 ? "no_candidates" : "below_threshold"));
|
|
1085
|
+
return [];
|
|
1086
|
+
}
|
|
1087
|
+
const budgetStartedAt = Date.now();
|
|
1088
|
+
const budgeted = budget === null ? thresholded : applyBudget(thresholded, budget);
|
|
1089
|
+
summary.candidateCounts.budgetAccepted = budgeted.length;
|
|
1090
|
+
summary.timings.budgetMs = budget === null ? 0 : elapsedMs2(budgetStartedAt);
|
|
1091
|
+
const ranked = sortAcceptedCandidates(budgeted.slice(0, limit), text, query.rankingProfile);
|
|
1092
|
+
summary.candidateCounts.finalRanked = ranked.length;
|
|
1093
|
+
if (ranked.length === 0) {
|
|
1094
|
+
reportTrace("limit_zero");
|
|
1095
|
+
return [];
|
|
1096
|
+
}
|
|
1097
|
+
const hydratedEntries = await ports.hydrateEntries(ranked.map((result) => result.entry.id));
|
|
1098
|
+
const shapeStartedAt = Date.now();
|
|
1099
|
+
const hydratedById = new Map(hydratedEntries.map((entry) => [entry.id, entry]));
|
|
1100
|
+
const results = ranked.flatMap((result) => {
|
|
1101
|
+
const entry = hydratedById.get(result.entry.id);
|
|
1102
|
+
if (!entry) {
|
|
1103
|
+
return [];
|
|
1104
|
+
}
|
|
1105
|
+
return [
|
|
1106
|
+
{
|
|
1107
|
+
entry,
|
|
1108
|
+
score: result.score,
|
|
1109
|
+
scores: result.scores
|
|
1110
|
+
}
|
|
1111
|
+
];
|
|
1112
|
+
});
|
|
1113
|
+
summary.candidateCounts.returned = results.length;
|
|
1114
|
+
summary.timings.shapeResultsMs = elapsedMs2(shapeStartedAt);
|
|
1115
|
+
if (results.length === 0) {
|
|
1116
|
+
reportTrace(resolveNoResultReason(summary, "hydrate_missing"));
|
|
1117
|
+
return [];
|
|
1118
|
+
}
|
|
1119
|
+
if (results.length > 0) {
|
|
1120
|
+
await ports.recordRecallEvents({
|
|
1121
|
+
entryIds: results.map((result) => result.entry.id),
|
|
1122
|
+
query: text,
|
|
1123
|
+
sessionKey: query.sessionKey
|
|
1124
|
+
}).catch(() => void 0);
|
|
1125
|
+
}
|
|
1126
|
+
reportTrace();
|
|
1127
|
+
return results;
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
reportTrace();
|
|
1130
|
+
throw error;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
function buildRecallTraceSummary(params) {
|
|
1134
|
+
return {
|
|
1135
|
+
filtering: {
|
|
1136
|
+
types: params.filters?.types ?? [],
|
|
1137
|
+
tags: params.filters?.tags ?? [],
|
|
1138
|
+
since: params.filters?.since?.toISOString(),
|
|
1139
|
+
until: params.filters?.until?.toISOString(),
|
|
1140
|
+
around: params.aroundDate ? {
|
|
1141
|
+
source: params.aroundSource,
|
|
1142
|
+
anchor: params.aroundDate.toISOString(),
|
|
1143
|
+
radiusDays: params.aroundRadius ?? 14
|
|
1144
|
+
} : void 0,
|
|
1145
|
+
...params.asOfDate ? {
|
|
1146
|
+
asOf: {
|
|
1147
|
+
anchor: params.asOfDate.toISOString()
|
|
1148
|
+
}
|
|
1149
|
+
} : {}
|
|
1150
|
+
},
|
|
1151
|
+
ranking: {
|
|
1152
|
+
limit: params.limit,
|
|
1153
|
+
threshold: params.threshold,
|
|
1154
|
+
budget: params.budget
|
|
1155
|
+
},
|
|
1156
|
+
candidateCounts: {
|
|
1157
|
+
merged: 0,
|
|
1158
|
+
thresholdQualified: 0,
|
|
1159
|
+
budgetAccepted: 0,
|
|
1160
|
+
finalRanked: 0,
|
|
1161
|
+
returned: 0
|
|
1162
|
+
},
|
|
1163
|
+
claimKey: {
|
|
1164
|
+
historicalBoosted: 0,
|
|
1165
|
+
tentativeLineageSuppressed: 0,
|
|
1166
|
+
trustPenalized: 0,
|
|
1167
|
+
redundancyPenalized: 0
|
|
1168
|
+
},
|
|
1169
|
+
rrf: {
|
|
1170
|
+
applied: false,
|
|
1171
|
+
channelCount: 0,
|
|
1172
|
+
rankConstant: DEFAULT_RRF_RANK_CONSTANT,
|
|
1173
|
+
fusedCandidateCount: 0,
|
|
1174
|
+
maxFusedScore: 0
|
|
1175
|
+
},
|
|
1176
|
+
neighborhood: {
|
|
1177
|
+
expansionRequested: false,
|
|
1178
|
+
expansionAvailable: false,
|
|
1179
|
+
familiesRequested: [],
|
|
1180
|
+
includeHistorical: false,
|
|
1181
|
+
seedIds: [],
|
|
1182
|
+
expansionCandidates: 0,
|
|
1183
|
+
strongSeedIds: [],
|
|
1184
|
+
rerankBoostedIds: []
|
|
1185
|
+
},
|
|
1186
|
+
mmr: {
|
|
1187
|
+
applied: false,
|
|
1188
|
+
lambda: DEFAULT_MMR_LAMBDA,
|
|
1189
|
+
droppedDuplicateCount: 0,
|
|
1190
|
+
reorderedIds: []
|
|
1191
|
+
},
|
|
1192
|
+
crossEncoder: {
|
|
1193
|
+
applied: false,
|
|
1194
|
+
k: 0,
|
|
1195
|
+
alpha: DEFAULT_CROSS_ENCODER_ALPHA,
|
|
1196
|
+
latencyMs: 0,
|
|
1197
|
+
rescoredIds: []
|
|
1198
|
+
},
|
|
1199
|
+
timings: {
|
|
1200
|
+
mergeCandidatesMs: 0,
|
|
1201
|
+
scoreCandidatesMs: 0,
|
|
1202
|
+
thresholdMs: 0,
|
|
1203
|
+
budgetMs: 0,
|
|
1204
|
+
shapeResultsMs: 0
|
|
1205
|
+
},
|
|
1206
|
+
degraded: {
|
|
1207
|
+
active: false,
|
|
1208
|
+
reasons: [],
|
|
1209
|
+
lexicalOnly: false,
|
|
1210
|
+
notices: []
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
function finishRecallTrace(summary, trace, noResultReason) {
|
|
1215
|
+
if (noResultReason) {
|
|
1216
|
+
summary.ranking.noResultReason = noResultReason;
|
|
1217
|
+
}
|
|
1218
|
+
trace.reportSummary(summary);
|
|
1219
|
+
}
|
|
1220
|
+
function markRecallDegraded(summary, reason, notice) {
|
|
1221
|
+
summary.degraded.active = true;
|
|
1222
|
+
if (!summary.degraded.reasons.includes(reason)) {
|
|
1223
|
+
summary.degraded.reasons.push(reason);
|
|
1224
|
+
}
|
|
1225
|
+
if (!summary.degraded.notices.includes(notice)) {
|
|
1226
|
+
summary.degraded.notices.push(notice);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
function resolveNoResultReason(summary, reason) {
|
|
1230
|
+
if (!summary.degraded.active) {
|
|
1231
|
+
return reason;
|
|
1232
|
+
}
|
|
1233
|
+
if (reason === "no_candidates") {
|
|
1234
|
+
return "degraded_no_candidates";
|
|
1235
|
+
}
|
|
1236
|
+
if (reason === "below_threshold") {
|
|
1237
|
+
return "degraded_below_threshold";
|
|
1238
|
+
}
|
|
1239
|
+
return reason;
|
|
1240
|
+
}
|
|
1241
|
+
function scoreMergedCandidate(candidate, queryText, queryEmbedding, rrfScore, params) {
|
|
1242
|
+
const vector = candidate.vectorSim ?? cosineSimilarity(candidate.entry.embedding ?? [], queryEmbedding);
|
|
1243
|
+
const lexical = computeLexicalScore(queryText, candidate.entry.subject, candidate.entry.content);
|
|
1244
|
+
const recency = resolveRecencyScore(candidate.entry, params);
|
|
1245
|
+
const importance = importanceScore(candidate.entry.importance);
|
|
1246
|
+
const scored = scoreCandidate({
|
|
1247
|
+
relevance: rrfScore,
|
|
1248
|
+
vectorSim: vector,
|
|
1249
|
+
lexical,
|
|
1250
|
+
recency,
|
|
1251
|
+
importance
|
|
1252
|
+
});
|
|
1253
|
+
return {
|
|
1254
|
+
entry: candidate.entry,
|
|
1255
|
+
score: scored.score,
|
|
1256
|
+
scores: {
|
|
1257
|
+
...scored.scores,
|
|
1258
|
+
// `rrf` mirrors `relevance` and makes the reciprocal rank fusion source
|
|
1259
|
+
// explicit for trace summaries and cross-stage reasoning in later phases.
|
|
1260
|
+
rrf: scored.scores.relevance,
|
|
1261
|
+
historicalLineage: 0,
|
|
1262
|
+
neighborhoodBoost: 0,
|
|
1263
|
+
claimKeyTrustPenalty: 0,
|
|
1264
|
+
claimKeyRedundancyPenalty: 0
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
function resolveEntryRelevance(params) {
|
|
1269
|
+
const { vectorRanks, ftsRanks, expansionRanks, policy, trace } = params;
|
|
1270
|
+
if (policy?.rrf === "disabled") {
|
|
1271
|
+
const fallbackChannel = vectorRanks.length > 0 ? vectorRanks : ftsRanks;
|
|
1272
|
+
const fallback = /* @__PURE__ */ new Map();
|
|
1273
|
+
fallbackChannel.forEach((id, index) => {
|
|
1274
|
+
if (!fallback.has(id)) {
|
|
1275
|
+
fallback.set(id, 1 / (index + 1));
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
trace.applied = false;
|
|
1279
|
+
trace.channelCount = fallbackChannel.length > 0 ? 1 : 0;
|
|
1280
|
+
trace.rankConstant = resolveRrfRankConstant(policy, fallback.size);
|
|
1281
|
+
trace.fusedCandidateCount = fallback.size;
|
|
1282
|
+
trace.maxFusedScore = fallback.size > 0 ? Math.max(...fallback.values()) : 0;
|
|
1283
|
+
return fallback;
|
|
1284
|
+
}
|
|
1285
|
+
const channels = [Array.from(vectorRanks), Array.from(ftsRanks), Array.from(expansionRanks)];
|
|
1286
|
+
const activeChannels = channels.filter((channel) => channel.length > 0);
|
|
1287
|
+
const uniqueFusedIds = /* @__PURE__ */ new Set();
|
|
1288
|
+
for (const channel of channels) {
|
|
1289
|
+
for (const id of channel) {
|
|
1290
|
+
uniqueFusedIds.add(id);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
const rankConstant = resolveRrfRankConstant(policy, uniqueFusedIds.size);
|
|
1294
|
+
trace.rankConstant = rankConstant;
|
|
1295
|
+
const fused = rrfFuse(channels, rankConstant);
|
|
1296
|
+
trace.applied = fused.size > 0;
|
|
1297
|
+
trace.channelCount = activeChannels.length;
|
|
1298
|
+
trace.fusedCandidateCount = fused.size;
|
|
1299
|
+
trace.maxFusedScore = fused.size > 0 ? Math.max(...fused.values()) : 0;
|
|
1300
|
+
return fused;
|
|
1301
|
+
}
|
|
1302
|
+
function resolveRrfRankConstant(policy, fusedPoolSize) {
|
|
1303
|
+
const rawGeneral = policy?.rrfRankConstant;
|
|
1304
|
+
const hasExplicitGeneral = typeof rawGeneral === "number" && Number.isFinite(rawGeneral) && rawGeneral > 0;
|
|
1305
|
+
const generalConstant = hasExplicitGeneral ? rawGeneral : DEFAULT_RRF_RANK_CONSTANT;
|
|
1306
|
+
const isSmallPool = Number.isFinite(fusedPoolSize) && fusedPoolSize > 0 && fusedPoolSize <= SMALL_POOL_RRF_POOL_SIZE;
|
|
1307
|
+
if (!isSmallPool) {
|
|
1308
|
+
return generalConstant;
|
|
1309
|
+
}
|
|
1310
|
+
const rawSmall = policy?.rrfSmallPoolRankConstant;
|
|
1311
|
+
if (typeof rawSmall === "number" && Number.isFinite(rawSmall) && rawSmall > 0) {
|
|
1312
|
+
return rawSmall;
|
|
1313
|
+
}
|
|
1314
|
+
if (hasExplicitGeneral) {
|
|
1315
|
+
return generalConstant;
|
|
1316
|
+
}
|
|
1317
|
+
return DEFAULT_RRF_SMALL_POOL_RANK_CONSTANT;
|
|
1318
|
+
}
|
|
1319
|
+
async function expandEntryNeighborhood(mergedCandidates, queryEmbedding, ports, params) {
|
|
1320
|
+
const trace = params.neighborhoodTrace;
|
|
1321
|
+
trace.expansionAvailable = Boolean(ports.expandNeighborhood);
|
|
1322
|
+
if (mergedCandidates.size === 0 || !ports.expandNeighborhood || params.rankingProfile !== "historical_state") {
|
|
1323
|
+
return [];
|
|
1324
|
+
}
|
|
1325
|
+
const families = HISTORICAL_NEIGHBORHOOD_FAMILIES;
|
|
1326
|
+
const includeHistorical = true;
|
|
1327
|
+
const seedIds = Array.from(mergedCandidates.keys());
|
|
1328
|
+
trace.expansionRequested = true;
|
|
1329
|
+
trace.familiesRequested = [...families];
|
|
1330
|
+
trace.includeHistorical = includeHistorical;
|
|
1331
|
+
trace.seedIds = seedIds;
|
|
1332
|
+
const expanded = await ports.expandNeighborhood({
|
|
1333
|
+
seedIds,
|
|
1334
|
+
budget: DEFAULT_NEIGHBORHOOD_BUDGET,
|
|
1335
|
+
families,
|
|
1336
|
+
includeHistorical
|
|
1337
|
+
});
|
|
1338
|
+
const ranked = expanded.filter((entry) => !mergedCandidates.has(entry.id)).map((entry) => ({
|
|
1339
|
+
entry,
|
|
1340
|
+
vectorSim: cosineSimilarity(entry.embedding ?? [], queryEmbedding)
|
|
1341
|
+
})).sort((left, right) => right.vectorSim - left.vectorSim || left.entry.id.localeCompare(right.entry.id));
|
|
1342
|
+
for (const candidate of ranked) {
|
|
1343
|
+
mergedCandidates.set(candidate.entry.id, {
|
|
1344
|
+
entry: candidate.entry,
|
|
1345
|
+
vectorSim: candidate.vectorSim
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
trace.expansionCandidates = ranked.length;
|
|
1349
|
+
return ranked.map((candidate) => candidate.entry.id);
|
|
1350
|
+
}
|
|
1351
|
+
function applySeededEntryRerank(candidates, trace) {
|
|
1352
|
+
if (candidates.length === 0) {
|
|
1353
|
+
return candidates;
|
|
1354
|
+
}
|
|
1355
|
+
const seeds = selectStrongSeeds(
|
|
1356
|
+
candidates.map((candidate) => ({ id: candidate.entry.id, score: candidate.score, entry: candidate.entry })),
|
|
1357
|
+
{
|
|
1358
|
+
topN: DEFAULT_STRONG_SEED_TOP_N,
|
|
1359
|
+
scoreGapFloor: DEFAULT_STRONG_SEED_SCORE_GAP
|
|
1360
|
+
}
|
|
1361
|
+
);
|
|
1362
|
+
if (seeds.length === 0) {
|
|
1363
|
+
return candidates;
|
|
1364
|
+
}
|
|
1365
|
+
trace.strongSeedIds = seeds.map((seed) => seed.id);
|
|
1366
|
+
const payloads = candidates.map((candidate) => ({
|
|
1367
|
+
id: candidate.entry.id,
|
|
1368
|
+
score: candidate.score,
|
|
1369
|
+
entry: candidate.entry
|
|
1370
|
+
}));
|
|
1371
|
+
const reranked = seededRerank(payloads, seeds, (candidate, seed) => sharesEntryLineage(candidate.entry, seed.entry), {
|
|
1372
|
+
weight: DEFAULT_SEEDED_RERANK_WEIGHT
|
|
1373
|
+
});
|
|
1374
|
+
trace.rerankBoostedIds = reranked.boostedIds;
|
|
1375
|
+
const scoreById = new Map(reranked.candidates.map((candidate) => [candidate.id, candidate.score]));
|
|
1376
|
+
return candidates.map((candidate) => {
|
|
1377
|
+
const nextScore = scoreById.get(candidate.entry.id) ?? candidate.score;
|
|
1378
|
+
const delta = nextScore - candidate.score;
|
|
1379
|
+
if (delta <= 0) {
|
|
1380
|
+
return candidate;
|
|
1381
|
+
}
|
|
1382
|
+
return {
|
|
1383
|
+
...candidate,
|
|
1384
|
+
score: nextScore,
|
|
1385
|
+
scores: {
|
|
1386
|
+
...candidate.scores,
|
|
1387
|
+
neighborhoodBoost: candidate.scores.neighborhoodBoost + delta
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
function resolveRecencyScore(entry, params) {
|
|
1393
|
+
if (params.asOfDate) {
|
|
1394
|
+
return resolveAsOfScore(entry, params.asOfDate);
|
|
1395
|
+
}
|
|
1396
|
+
if (params.aroundDate) {
|
|
1397
|
+
return gaussianRecency(entry.created_at, params.aroundDate, normalizeAroundRadius(params.aroundRadius));
|
|
1398
|
+
}
|
|
1399
|
+
if (params.rankingProfile === "historical_state") {
|
|
1400
|
+
return HISTORICAL_STATE_FLAT_RECENCY;
|
|
1401
|
+
}
|
|
1402
|
+
return recencyScore(entry.created_at, entry.expiry, params.now);
|
|
1403
|
+
}
|
|
1404
|
+
function resolveAsOfScore(entry, asOfDate) {
|
|
1405
|
+
const validFrom = parseTimestamp(entry.valid_from);
|
|
1406
|
+
const validTo = parseTimestamp(entry.valid_to);
|
|
1407
|
+
if (validFrom || validTo) {
|
|
1408
|
+
const startMs = validFrom?.getTime() ?? Number.NEGATIVE_INFINITY;
|
|
1409
|
+
const endMs = validTo?.getTime() ?? Number.POSITIVE_INFINITY;
|
|
1410
|
+
const asOfMs = asOfDate.getTime();
|
|
1411
|
+
if (asOfMs >= startMs && asOfMs <= endMs) {
|
|
1412
|
+
return 1;
|
|
1413
|
+
}
|
|
1414
|
+
const nearestBoundaryMs = asOfMs < startMs ? startMs : endMs;
|
|
1415
|
+
return Math.max(0.1, gaussianRecency(new Date(nearestBoundaryMs).toISOString(), asOfDate, 21) * 0.65);
|
|
1416
|
+
}
|
|
1417
|
+
const observedAt = parseTimestamp(entry.claim_support_observed_at);
|
|
1418
|
+
if (observedAt) {
|
|
1419
|
+
const observedBeforeAsOf = observedAt.getTime() <= asOfDate.getTime();
|
|
1420
|
+
const proximity = gaussianRecency(observedAt.toISOString(), asOfDate, 30);
|
|
1421
|
+
return observedBeforeAsOf ? Math.max(0.45, proximity * 0.8) : Math.max(0.05, proximity * 0.2);
|
|
1422
|
+
}
|
|
1423
|
+
const createdAt = parseTimestamp(entry.created_at);
|
|
1424
|
+
if (createdAt) {
|
|
1425
|
+
const createdBeforeAsOf = createdAt.getTime() <= asOfDate.getTime();
|
|
1426
|
+
const proximity = gaussianRecency(createdAt.toISOString(), asOfDate, 45);
|
|
1427
|
+
return createdBeforeAsOf ? Math.max(0.35, proximity * 0.7) : Math.max(0.05, proximity * 0.15);
|
|
1428
|
+
}
|
|
1429
|
+
return HISTORICAL_STATE_FLAT_RECENCY;
|
|
1430
|
+
}
|
|
1431
|
+
function applyHistoricalLineageBoosts(candidates, params, claimKeyTrace, slotPolicyConfig) {
|
|
1432
|
+
if (params.rankingProfile !== "historical_state") {
|
|
1433
|
+
return candidates;
|
|
1434
|
+
}
|
|
1435
|
+
const entries = candidates.map((candidate) => candidate.entry);
|
|
1436
|
+
const scoresById = new Map(candidates.map((candidate) => [candidate.entry.id, candidate.score]));
|
|
1437
|
+
return candidates.map((candidate) => {
|
|
1438
|
+
const decision = resolveHistoricalLineageBonus(candidate.entry, entries, scoresById, candidate.score, params.aroundDate, params.nowMs, slotPolicyConfig);
|
|
1439
|
+
if (decision.tentativeLineageSuppressed) {
|
|
1440
|
+
claimKeyTrace.tentativeLineageSuppressed += 1;
|
|
1441
|
+
}
|
|
1442
|
+
const bonus = decision.bonus;
|
|
1443
|
+
if (bonus <= 0) {
|
|
1444
|
+
return candidate;
|
|
1445
|
+
}
|
|
1446
|
+
claimKeyTrace.historicalBoosted += 1;
|
|
1447
|
+
return {
|
|
1448
|
+
...candidate,
|
|
1449
|
+
score: clampRecallScore(candidate.score + bonus),
|
|
1450
|
+
scores: {
|
|
1451
|
+
...candidate.scores,
|
|
1452
|
+
historicalLineage: candidate.scores.historicalLineage + bonus
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
function resolveHistoricalLineageBonus(entry, entries, scoresById, candidateScore, aroundDate, nowMs, slotPolicyConfig) {
|
|
1458
|
+
const directSuccessor = entries.find((peer) => peer.id !== entry.id && entry.superseded_by === peer.id);
|
|
1459
|
+
if (directSuccessor) {
|
|
1460
|
+
const successorScore = scoresById.get(directSuccessor.id) ?? 0;
|
|
1461
|
+
return {
|
|
1462
|
+
bonus: shapeHistoricalLineageBonus(HISTORICAL_PREDECESSOR_BOOST, candidateScore, successorScore),
|
|
1463
|
+
tentativeLineageSuppressed: false
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
if (aroundDate) {
|
|
1467
|
+
return {
|
|
1468
|
+
bonus: 0,
|
|
1469
|
+
tentativeLineageSuppressed: false
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
let tentativeLineageSuppressed = false;
|
|
1473
|
+
let bestPeerScore = 0;
|
|
1474
|
+
let peerMatched = false;
|
|
1475
|
+
for (const peer of entries) {
|
|
1476
|
+
if (peer.id === entry.id || !isPotentialCurrentPeer(peer, nowMs) || createdAtMs(entry.created_at) >= createdAtMs(peer.created_at)) {
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1479
|
+
const relation = resolveHistoricalPeerRelation(entry, peer, entries, slotPolicyConfig);
|
|
1480
|
+
if (relation === "tentative_claim_key_suppressed") {
|
|
1481
|
+
tentativeLineageSuppressed = true;
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
if (relation === null) {
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
peerMatched = true;
|
|
1488
|
+
const peerScore = scoresById.get(peer.id) ?? 0;
|
|
1489
|
+
if (peerScore > bestPeerScore) {
|
|
1490
|
+
bestPeerScore = peerScore;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
if (!peerMatched) {
|
|
1494
|
+
return {
|
|
1495
|
+
bonus: 0,
|
|
1496
|
+
tentativeLineageSuppressed
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
const base = isStalePredecessor(entry, nowMs) ? HISTORICAL_STALE_PREDECESSOR_BOOST : HISTORICAL_OLDER_STATE_BOOST;
|
|
1500
|
+
return {
|
|
1501
|
+
bonus: shapeHistoricalLineageBonus(base, candidateScore, bestPeerScore),
|
|
1502
|
+
tentativeLineageSuppressed
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
function shapeHistoricalLineageBonus(base, candidateScore, successorScore) {
|
|
1506
|
+
const gap = successorScore - candidateScore;
|
|
1507
|
+
const needed = gap > 0 ? gap + HISTORICAL_LINEAGE_GAP_MARGIN : 0;
|
|
1508
|
+
return Math.min(HISTORICAL_LINEAGE_MAX_BONUS, Math.max(base, needed));
|
|
1509
|
+
}
|
|
1510
|
+
function isPotentialCurrentPeer(entry, nowMs) {
|
|
1511
|
+
return isCurrentlyValidMemory(entry, nowMs);
|
|
1512
|
+
}
|
|
1513
|
+
function isStalePredecessor(entry, nowMs) {
|
|
1514
|
+
return isStaleMemory(entry, nowMs);
|
|
1515
|
+
}
|
|
1516
|
+
function resolveHistoricalPeerRelation(left, right, entries, slotPolicyConfig) {
|
|
1517
|
+
if (left.claim_key && right.claim_key && left.claim_key === right.claim_key) {
|
|
1518
|
+
if (resolveClaimSlotPolicy(left.claim_key, slotPolicyConfig).policy === "multivalued") {
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
return canUseClaimKeyLineage(left, entries, slotPolicyConfig) ? "claim_key" : "tentative_claim_key_suppressed";
|
|
1522
|
+
}
|
|
1523
|
+
return sharesHistoricalTopic(left, right) ? "topic" : null;
|
|
1524
|
+
}
|
|
1525
|
+
function canUseClaimKeyLineage(entry, entries, slotPolicyConfig) {
|
|
1526
|
+
if (!entry.claim_key) {
|
|
1527
|
+
return false;
|
|
1528
|
+
}
|
|
1529
|
+
if (resolveClaimSlotPolicy(entry.claim_key, slotPolicyConfig).policy === "multivalued") {
|
|
1530
|
+
return false;
|
|
1531
|
+
}
|
|
1532
|
+
if (!hasTrustedClaimKeyEvidence(entries, entry.claim_key)) {
|
|
1533
|
+
return true;
|
|
1534
|
+
}
|
|
1535
|
+
return entry.claim_key_status === "trusted";
|
|
1536
|
+
}
|
|
1537
|
+
function hasTrustedClaimKeyEvidence(entries, claimKey) {
|
|
1538
|
+
return entries.some((entry) => entry.claim_key === claimKey && entry.claim_key_status === "trusted");
|
|
1539
|
+
}
|
|
1540
|
+
function sharesHistoricalTopic(left, right) {
|
|
1541
|
+
const leftTokens = tokenize(left.subject);
|
|
1542
|
+
const rightTokens = tokenize(right.subject);
|
|
1543
|
+
if (leftTokens.length === 0 || rightTokens.length === 0) {
|
|
1544
|
+
return false;
|
|
1545
|
+
}
|
|
1546
|
+
const sharedPrefixCount = countSharedPrefixTokens(leftTokens, rightTokens);
|
|
1547
|
+
return sharedPrefixCount >= HISTORICAL_TOPIC_SHARED_PREFIX_MIN && sharedPrefixCount / leftTokens.length >= HISTORICAL_TOPIC_PREFIX_OF_CANDIDATE_MIN;
|
|
1548
|
+
}
|
|
1549
|
+
function createdAtMs(value) {
|
|
1550
|
+
return parseTimestamp(value)?.getTime() ?? 0;
|
|
1551
|
+
}
|
|
1552
|
+
function parseTimestamp(value) {
|
|
1553
|
+
const normalized = value?.trim();
|
|
1554
|
+
if (!normalized) {
|
|
1555
|
+
return null;
|
|
1556
|
+
}
|
|
1557
|
+
const timestamp = new Date(normalized);
|
|
1558
|
+
return Number.isFinite(timestamp.getTime()) ? timestamp : null;
|
|
1559
|
+
}
|
|
1560
|
+
function countSharedPrefixTokens(leftTokens, rightTokens) {
|
|
1561
|
+
const length = Math.min(leftTokens.length, rightTokens.length);
|
|
1562
|
+
let sharedPrefixCount = 0;
|
|
1563
|
+
for (let index = 0; index < length; index += 1) {
|
|
1564
|
+
if (leftTokens[index] !== rightTokens[index]) {
|
|
1565
|
+
break;
|
|
1566
|
+
}
|
|
1567
|
+
sharedPrefixCount += 1;
|
|
1568
|
+
}
|
|
1569
|
+
return sharedPrefixCount;
|
|
1570
|
+
}
|
|
1571
|
+
function applyMmrDiversification(candidates, queryEmbedding, policy, trace) {
|
|
1572
|
+
if (candidates.length < 2 || policy?.mmr === "disabled") {
|
|
1573
|
+
trace.applied = false;
|
|
1574
|
+
trace.lambda = resolveMmrLambda(policy);
|
|
1575
|
+
return candidates;
|
|
1576
|
+
}
|
|
1577
|
+
const reorder = maximalMarginalRelevance({
|
|
1578
|
+
queryVector: queryEmbedding,
|
|
1579
|
+
candidates: candidates.map((candidate) => ({
|
|
1580
|
+
id: candidate.entry.id,
|
|
1581
|
+
relevance: candidate.score,
|
|
1582
|
+
...candidate.entry.embedding ? { embedding: candidate.entry.embedding } : {}
|
|
1583
|
+
})),
|
|
1584
|
+
lambda: resolveMmrLambda(policy),
|
|
1585
|
+
minPoolSize: resolveMmrMinPoolSize(policy)
|
|
1586
|
+
});
|
|
1587
|
+
trace.applied = reorder.applied;
|
|
1588
|
+
trace.lambda = reorder.lambda;
|
|
1589
|
+
trace.droppedDuplicateCount = reorder.droppedDuplicateCount;
|
|
1590
|
+
trace.reorderedIds = reorder.reorderedIds;
|
|
1591
|
+
if (!reorder.applied) {
|
|
1592
|
+
return candidates;
|
|
1593
|
+
}
|
|
1594
|
+
const candidatesById = new Map(candidates.map((candidate) => [candidate.entry.id, candidate]));
|
|
1595
|
+
return reorder.orderedIds.flatMap((id) => {
|
|
1596
|
+
const candidate = candidatesById.get(id);
|
|
1597
|
+
return candidate ? [candidate] : [];
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
async function applyEntryCrossEncoderRerank(candidates, query, crossEncoder, policy, trace) {
|
|
1601
|
+
const result = await applyCrossEncoderRerank({
|
|
1602
|
+
query,
|
|
1603
|
+
candidates: candidates.map((candidate) => ({
|
|
1604
|
+
id: candidate.entry.id,
|
|
1605
|
+
text: buildCrossEncoderPassageText(candidate.entry),
|
|
1606
|
+
score: candidate.score,
|
|
1607
|
+
candidate
|
|
1608
|
+
})),
|
|
1609
|
+
port: crossEncoder,
|
|
1610
|
+
disabled: policy?.crossEncoder === "disabled",
|
|
1611
|
+
topK: policy?.crossEncoderTopK ?? DEFAULT_CROSS_ENCODER_TOP_K,
|
|
1612
|
+
alpha: policy?.crossEncoderAlpha ?? DEFAULT_CROSS_ENCODER_ALPHA
|
|
1613
|
+
});
|
|
1614
|
+
trace.applied = result.applied;
|
|
1615
|
+
trace.k = result.k;
|
|
1616
|
+
trace.alpha = result.alpha;
|
|
1617
|
+
trace.latencyMs = result.latencyMs;
|
|
1618
|
+
trace.rescoredIds = [...result.rescoredIds];
|
|
1619
|
+
if (result.degradedReason) {
|
|
1620
|
+
trace.degradedReason = result.degradedReason;
|
|
1621
|
+
} else {
|
|
1622
|
+
delete trace.degradedReason;
|
|
1623
|
+
}
|
|
1624
|
+
return result.candidates.map((entry) => {
|
|
1625
|
+
const scoredCandidate = entry.candidate;
|
|
1626
|
+
const nextScore = entry.score;
|
|
1627
|
+
if (typeof entry.crossEncoderScore !== "number" && nextScore === scoredCandidate.score) {
|
|
1628
|
+
return scoredCandidate;
|
|
1629
|
+
}
|
|
1630
|
+
return {
|
|
1631
|
+
...scoredCandidate,
|
|
1632
|
+
score: nextScore,
|
|
1633
|
+
scores: {
|
|
1634
|
+
...scoredCandidate.scores,
|
|
1635
|
+
...typeof entry.crossEncoderScore === "number" ? { crossEncoder: entry.crossEncoderScore } : {}
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
function buildCrossEncoderPassageText(entry) {
|
|
1641
|
+
const subject = entry.subject.trim();
|
|
1642
|
+
const content = entry.content.trim();
|
|
1643
|
+
if (subject.length === 0) {
|
|
1644
|
+
return content;
|
|
1645
|
+
}
|
|
1646
|
+
if (content.length === 0) {
|
|
1647
|
+
return subject;
|
|
1648
|
+
}
|
|
1649
|
+
return `${subject}
|
|
1650
|
+
|
|
1651
|
+
${content}`;
|
|
1652
|
+
}
|
|
1653
|
+
function resolveMmrLambda(policy) {
|
|
1654
|
+
const rawLambda = policy?.mmrLambda;
|
|
1655
|
+
if (typeof rawLambda !== "number" || !Number.isFinite(rawLambda)) {
|
|
1656
|
+
return DEFAULT_MMR_LAMBDA;
|
|
1657
|
+
}
|
|
1658
|
+
return Math.max(0, Math.min(1, rawLambda));
|
|
1659
|
+
}
|
|
1660
|
+
function resolveMmrMinPoolSize(policy) {
|
|
1661
|
+
const raw = policy?.mmrMinPoolSize;
|
|
1662
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0) {
|
|
1663
|
+
return DEFAULT_MMR_MIN_POOL_SIZE;
|
|
1664
|
+
}
|
|
1665
|
+
return Math.floor(raw);
|
|
1666
|
+
}
|
|
1667
|
+
function applyClaimKeyResultShaping(candidates, claimKeyTrace, nowMs, slotPolicyConfig) {
|
|
1668
|
+
if (candidates.length === 0) {
|
|
1669
|
+
return candidates;
|
|
1670
|
+
}
|
|
1671
|
+
const trustedActiveClaimKeys = new Set(
|
|
1672
|
+
candidates.map((candidate) => candidate.entry).filter(
|
|
1673
|
+
(entry) => isPotentialCurrentPeer(entry, nowMs) && entry.claim_key && entry.claim_key_status === "trusted" && resolveClaimSlotPolicy(entry.claim_key, slotPolicyConfig).policy === "exclusive"
|
|
1674
|
+
).map((entry) => entry.claim_key)
|
|
1675
|
+
);
|
|
1676
|
+
const trustedSlotRankById = rankTrustedSlotSiblings(candidates, nowMs, slotPolicyConfig);
|
|
1677
|
+
return candidates.map((candidate) => {
|
|
1678
|
+
const trustPenalty = shouldPenalizeTentativeCurrentSibling(candidate.entry, trustedActiveClaimKeys, nowMs) ? CLAIM_KEY_TENTATIVE_CURRENT_PENALTY : 0;
|
|
1679
|
+
const redundancyPenalty = resolveTrustedSlotRedundancyPenalty(candidate.entry.id, trustedSlotRankById);
|
|
1680
|
+
if (trustPenalty <= 0 && redundancyPenalty <= 0) {
|
|
1681
|
+
return candidate;
|
|
1682
|
+
}
|
|
1683
|
+
if (trustPenalty > 0) {
|
|
1684
|
+
claimKeyTrace.trustPenalized += 1;
|
|
1685
|
+
}
|
|
1686
|
+
if (redundancyPenalty > 0) {
|
|
1687
|
+
claimKeyTrace.redundancyPenalized += 1;
|
|
1688
|
+
}
|
|
1689
|
+
return {
|
|
1690
|
+
...candidate,
|
|
1691
|
+
score: clampRecallScore(candidate.score - trustPenalty - redundancyPenalty),
|
|
1692
|
+
scores: {
|
|
1693
|
+
...candidate.scores,
|
|
1694
|
+
claimKeyTrustPenalty: trustPenalty,
|
|
1695
|
+
claimKeyRedundancyPenalty: redundancyPenalty
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
function rankTrustedSlotSiblings(candidates, nowMs, slotPolicyConfig) {
|
|
1701
|
+
const candidatesById = new Map(candidates.map((candidate) => [candidate.entry.id, candidate]));
|
|
1702
|
+
const trustedByClaimKey = /* @__PURE__ */ new Map();
|
|
1703
|
+
for (const candidate of candidates) {
|
|
1704
|
+
const claimKey = candidate.entry.claim_key;
|
|
1705
|
+
if (!claimKey || candidate.entry.claim_key_status !== "trusted" || !isPotentialCurrentPeer(candidate.entry, nowMs) || resolveClaimSlotPolicy(claimKey, slotPolicyConfig).policy !== "exclusive") {
|
|
1706
|
+
continue;
|
|
1707
|
+
}
|
|
1708
|
+
const siblings = trustedByClaimKey.get(claimKey) ?? [];
|
|
1709
|
+
siblings.push(candidate);
|
|
1710
|
+
trustedByClaimKey.set(claimKey, siblings);
|
|
1711
|
+
}
|
|
1712
|
+
const ranks = /* @__PURE__ */ new Map();
|
|
1713
|
+
for (const siblings of trustedByClaimKey.values()) {
|
|
1714
|
+
siblings.slice().sort(compareCandidatesForTrustedSlotRank).forEach((candidate, index) => {
|
|
1715
|
+
if (candidatesById.has(candidate.entry.id)) {
|
|
1716
|
+
ranks.set(candidate.entry.id, index);
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
return ranks;
|
|
1721
|
+
}
|
|
1722
|
+
function compareCandidatesForTrustedSlotRank(left, right) {
|
|
1723
|
+
return right.score - left.score || createdAtMs(right.entry.created_at) - createdAtMs(left.entry.created_at) || left.entry.id.localeCompare(right.entry.id);
|
|
1724
|
+
}
|
|
1725
|
+
function shouldPenalizeTentativeCurrentSibling(entry, trustedActiveClaimKeys, nowMs) {
|
|
1726
|
+
return isPotentialCurrentPeer(entry, nowMs) && entry.claim_key !== void 0 && entry.claim_key_status !== "trusted" && trustedActiveClaimKeys.has(entry.claim_key);
|
|
1727
|
+
}
|
|
1728
|
+
function resolveTrustedSlotRedundancyPenalty(entryId, trustedSlotRankById) {
|
|
1729
|
+
const rank = trustedSlotRankById.get(entryId) ?? 0;
|
|
1730
|
+
if (rank <= 0) {
|
|
1731
|
+
return 0;
|
|
1732
|
+
}
|
|
1733
|
+
return Math.min(CLAIM_KEY_REDUNDANT_TRUSTED_SLOT_MAX_PENALTY, rank * CLAIM_KEY_REDUNDANT_TRUSTED_SLOT_PENALTY);
|
|
1734
|
+
}
|
|
1735
|
+
function clampRecallScore(value) {
|
|
1736
|
+
return Math.max(0, Math.min(1, value));
|
|
1737
|
+
}
|
|
1738
|
+
function hasSufficientReturnEvidence(candidate, query) {
|
|
1739
|
+
if (query.rankingProfile === "entity_attribute") {
|
|
1740
|
+
return hasEntityAttributeEvidence(candidate.entry, query.queryShape);
|
|
1741
|
+
}
|
|
1742
|
+
const groundedLexicalSupport = hasGroundedLexicalSupport(candidate.entry, query.text);
|
|
1743
|
+
if (candidate.scores.lexical > 0) {
|
|
1744
|
+
if (groundedLexicalSupport) {
|
|
1745
|
+
return true;
|
|
1746
|
+
}
|
|
1747
|
+
return candidate.scores.vector >= MIN_VECTOR_WITHOUT_GROUNDED_LEXICAL_SUPPORT;
|
|
1748
|
+
}
|
|
1749
|
+
if (isWeaklyGroundedReminderQuery(query.text) && !groundedLexicalSupport) {
|
|
1750
|
+
return false;
|
|
1751
|
+
}
|
|
1752
|
+
return candidate.scores.vector >= MIN_VECTOR_ONLY_EVIDENCE;
|
|
1753
|
+
}
|
|
1754
|
+
function hasGroundedLexicalSupport(entry, queryText) {
|
|
1755
|
+
const groundingTokens = getGroundingTokens(queryText);
|
|
1756
|
+
if (groundingTokens.length === 0) {
|
|
1757
|
+
return false;
|
|
1758
|
+
}
|
|
1759
|
+
const candidateTokens = new Set(tokenize(`${entry.subject} ${entry.content}`).map(canonicalizeRecallToken));
|
|
1760
|
+
return groundingTokens.some((token) => candidateTokens.has(token));
|
|
1761
|
+
}
|
|
1762
|
+
function isWeaklyGroundedReminderQuery(queryText) {
|
|
1763
|
+
return WEAKLY_GROUNDED_REMINDER_PATTERN.test(queryText);
|
|
1764
|
+
}
|
|
1765
|
+
function hasEntityAttributeEvidence(entry, queryShape) {
|
|
1766
|
+
if (queryShape?.kind !== "entity_attribute") {
|
|
1767
|
+
return false;
|
|
1768
|
+
}
|
|
1769
|
+
const normalizedSubject = normalizeEntityAttributeText(entry.subject);
|
|
1770
|
+
const normalizedContent = normalizeEntityAttributeText(entry.content);
|
|
1771
|
+
const combinedTokens = new Set(tokenize(`${entry.subject} ${entry.content}`));
|
|
1772
|
+
const entityTokenMatches = countTokenMatches(queryShape.entityTokens, combinedTokens);
|
|
1773
|
+
const attributeTokenMatches = countTokenMatches(queryShape.attributeTokens, combinedTokens);
|
|
1774
|
+
if (queryShape.attributeKind === "identity") {
|
|
1775
|
+
if (normalizedSubject === queryShape.normalizedEntity || isIdentityWrapperSubject(normalizedSubject, queryShape.normalizedEntity)) {
|
|
1776
|
+
return true;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
if ((containsNormalizedPhrase(normalizedSubject, queryShape.normalizedEntity) || containsNormalizedPhrase(normalizedContent, queryShape.normalizedEntity)) && (queryShape.entityTokens.length >= 2 || attributeTokenMatches >= 1)) {
|
|
1780
|
+
return true;
|
|
1781
|
+
}
|
|
1782
|
+
return entityTokenMatches >= 2 && attributeTokenMatches >= 1;
|
|
1783
|
+
}
|
|
1784
|
+
function countTokenMatches(expectedTokens, availableTokens) {
|
|
1785
|
+
let matches = 0;
|
|
1786
|
+
for (const token of expectedTokens) {
|
|
1787
|
+
if (availableTokens.has(token)) {
|
|
1788
|
+
matches += 1;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
return matches;
|
|
1792
|
+
}
|
|
1793
|
+
function isIdentityWrapperSubject(normalizedSubject, normalizedEntity) {
|
|
1794
|
+
return Array.from(ENTITY_ATTRIBUTE_IDENTITY_WRAPPERS).some((wrapper) => normalizedSubject === `${normalizedEntity} ${wrapper}`);
|
|
1795
|
+
}
|
|
1796
|
+
function containsNormalizedPhrase(normalizedText, normalizedPhrase) {
|
|
1797
|
+
return normalizedPhrase.length > 0 && normalizedText.includes(normalizedPhrase);
|
|
1798
|
+
}
|
|
1799
|
+
function normalizeEntityAttributeText(text) {
|
|
1800
|
+
return text.replace(/\s+/gu, " ").trim().normalize("NFKC").toLocaleLowerCase();
|
|
1801
|
+
}
|
|
1802
|
+
function mergeCandidates(vectorCandidates, ftsCandidates) {
|
|
1803
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1804
|
+
const vectorRanks = [];
|
|
1805
|
+
const ftsRanks = [];
|
|
1806
|
+
for (const candidate of vectorCandidates) {
|
|
1807
|
+
if (!merged.has(candidate.entry.id)) {
|
|
1808
|
+
vectorRanks.push(candidate.entry.id);
|
|
1809
|
+
}
|
|
1810
|
+
merged.set(candidate.entry.id, {
|
|
1811
|
+
entry: candidate.entry,
|
|
1812
|
+
vectorSim: candidate.vectorSim
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
for (const candidate of ftsCandidates) {
|
|
1816
|
+
ftsRanks.push(candidate.entry.id);
|
|
1817
|
+
const existing = merged.get(candidate.entry.id);
|
|
1818
|
+
if (existing) {
|
|
1819
|
+
existing.entry = existing.entry.embedding ? existing.entry : candidate.entry;
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
merged.set(candidate.entry.id, {
|
|
1823
|
+
entry: candidate.entry
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
return {
|
|
1827
|
+
merged,
|
|
1828
|
+
vectorRanks,
|
|
1829
|
+
ftsRanks
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
function buildDurableFilters(types, tags, since, until, options) {
|
|
1833
|
+
const filters = {};
|
|
1834
|
+
if (types && types.length > 0) {
|
|
1835
|
+
filters.types = types;
|
|
1836
|
+
}
|
|
1837
|
+
if (tags && tags.length > 0) {
|
|
1838
|
+
filters.tags = tags;
|
|
1839
|
+
}
|
|
1840
|
+
if (since) {
|
|
1841
|
+
filters.since = since;
|
|
1842
|
+
}
|
|
1843
|
+
if (until) {
|
|
1844
|
+
filters.until = until;
|
|
1845
|
+
}
|
|
1846
|
+
if (options) {
|
|
1847
|
+
const validAsOf = resolveRecallValidAsOf({
|
|
1848
|
+
rankingProfile: options.rankingProfile,
|
|
1849
|
+
asOfDate: options.asOfDate,
|
|
1850
|
+
nowMs: options.nowMs
|
|
1851
|
+
});
|
|
1852
|
+
if (validAsOf) {
|
|
1853
|
+
filters.validAsOf = validAsOf;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return Object.keys(filters).length > 0 ? filters : void 0;
|
|
1857
|
+
}
|
|
1858
|
+
function applyBudget(results, budget) {
|
|
1859
|
+
if (results.length === 0) {
|
|
1860
|
+
return [];
|
|
1861
|
+
}
|
|
1862
|
+
const accepted = [results[0]];
|
|
1863
|
+
let consumed = estimateTokens(results[0].entry);
|
|
1864
|
+
for (const result of results.slice(1)) {
|
|
1865
|
+
const estimate = estimateTokens(result.entry);
|
|
1866
|
+
if (consumed + estimate > budget) {
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
accepted.push(result);
|
|
1870
|
+
consumed += estimate;
|
|
1871
|
+
}
|
|
1872
|
+
return accepted;
|
|
1873
|
+
}
|
|
1874
|
+
function sortAcceptedCandidates(candidates, queryText, rankingProfile) {
|
|
1875
|
+
if (rankingProfile === "historical_state" || rankingProfile === "entity_attribute") {
|
|
1876
|
+
return candidates.map((candidate, index) => ({ candidate, index })).sort((left, right) => right.candidate.score - left.candidate.score || left.index - right.index).map(({ candidate }) => candidate);
|
|
1877
|
+
}
|
|
1878
|
+
const groundingTokens = getGroundingTokens(queryText);
|
|
1879
|
+
return candidates.map((candidate, index) => ({
|
|
1880
|
+
candidate,
|
|
1881
|
+
index,
|
|
1882
|
+
grounding: computeGroundingSupport(candidate.entry, groundingTokens)
|
|
1883
|
+
})).sort((left, right) => {
|
|
1884
|
+
const scoreGap = Math.abs(left.candidate.score - right.candidate.score);
|
|
1885
|
+
if (scoreGap > GROUNDING_SORT_MAX_SCORE_GAP || hasStructuralScoreShaping(left.candidate) || hasStructuralScoreShaping(right.candidate)) {
|
|
1886
|
+
if (left.candidate.score !== right.candidate.score) {
|
|
1887
|
+
return right.candidate.score - left.candidate.score;
|
|
1888
|
+
}
|
|
1889
|
+
return left.index - right.index;
|
|
1890
|
+
}
|
|
1891
|
+
if (left.grounding.phraseMatches !== right.grounding.phraseMatches) {
|
|
1892
|
+
return right.grounding.phraseMatches - left.grounding.phraseMatches;
|
|
1893
|
+
}
|
|
1894
|
+
if (left.grounding.coverage !== right.grounding.coverage) {
|
|
1895
|
+
return right.grounding.coverage - left.grounding.coverage;
|
|
1896
|
+
}
|
|
1897
|
+
if (left.candidate.scores.lexical !== right.candidate.scores.lexical) {
|
|
1898
|
+
return right.candidate.scores.lexical - left.candidate.scores.lexical;
|
|
1899
|
+
}
|
|
1900
|
+
if (left.candidate.score !== right.candidate.score) {
|
|
1901
|
+
return right.candidate.score - left.candidate.score;
|
|
1902
|
+
}
|
|
1903
|
+
if (left.candidate.scores.vector !== right.candidate.scores.vector) {
|
|
1904
|
+
return right.candidate.scores.vector - left.candidate.scores.vector;
|
|
1905
|
+
}
|
|
1906
|
+
return left.index - right.index;
|
|
1907
|
+
}).map(({ candidate }) => candidate);
|
|
1908
|
+
}
|
|
1909
|
+
function hasStructuralScoreShaping(candidate) {
|
|
1910
|
+
return candidate.scores.historicalLineage > 0 || candidate.scores.neighborhoodBoost > 0 || candidate.scores.claimKeyTrustPenalty > 0 || candidate.scores.claimKeyRedundancyPenalty > 0;
|
|
1911
|
+
}
|
|
1912
|
+
function getGroundingTokens(queryText) {
|
|
1913
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1914
|
+
const groundingTokens = [];
|
|
1915
|
+
for (const token of tokenize(queryText)) {
|
|
1916
|
+
if (WEAK_QUERY_GROUNDING_TOKENS.has(token)) {
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1919
|
+
const canonical = canonicalizeRecallToken(token);
|
|
1920
|
+
if (seen.has(canonical)) {
|
|
1921
|
+
continue;
|
|
1922
|
+
}
|
|
1923
|
+
seen.add(canonical);
|
|
1924
|
+
groundingTokens.push(canonical);
|
|
1925
|
+
}
|
|
1926
|
+
return groundingTokens;
|
|
1927
|
+
}
|
|
1928
|
+
function canonicalizeRecallToken(token) {
|
|
1929
|
+
const normalized = token.normalize("NFKC").toLocaleLowerCase();
|
|
1930
|
+
if (normalized === "db" || normalized === "database" || normalized === "databases") {
|
|
1931
|
+
return "db";
|
|
1932
|
+
}
|
|
1933
|
+
if (normalized === "resolve" || normalized === "resolves" || normalized === "resolved" || normalized === "resolving" || normalized === "resolution") {
|
|
1934
|
+
return "resolve";
|
|
1935
|
+
}
|
|
1936
|
+
if (normalized === "branches") {
|
|
1937
|
+
return "branch";
|
|
1938
|
+
}
|
|
1939
|
+
if (normalized === "prefix" || normalized === "prefixes") {
|
|
1940
|
+
return "prefix";
|
|
1941
|
+
}
|
|
1942
|
+
if (normalized.endsWith("ies") && normalized.length > 4) {
|
|
1943
|
+
return `${normalized.slice(0, -3)}y`;
|
|
1944
|
+
}
|
|
1945
|
+
if (normalized.endsWith("es") && normalized.length > 4) {
|
|
1946
|
+
return normalized.slice(0, -2);
|
|
1947
|
+
}
|
|
1948
|
+
if (normalized.endsWith("s") && normalized.length > 3) {
|
|
1949
|
+
return normalized.slice(0, -1);
|
|
1950
|
+
}
|
|
1951
|
+
return normalized;
|
|
1952
|
+
}
|
|
1953
|
+
function computeGroundingSupport(entry, groundingTokens) {
|
|
1954
|
+
if (groundingTokens.length === 0) {
|
|
1955
|
+
return {
|
|
1956
|
+
phraseMatches: 0,
|
|
1957
|
+
coverage: 0
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
const subjectTokens = tokenize(entry.subject).map(canonicalizeRecallToken);
|
|
1961
|
+
const contentTokens = tokenize(entry.content).map(canonicalizeRecallToken);
|
|
1962
|
+
const candidateTokens = /* @__PURE__ */ new Set([...subjectTokens, ...contentTokens]);
|
|
1963
|
+
const matchedTokens = groundingTokens.filter((token) => candidateTokens.has(token));
|
|
1964
|
+
return {
|
|
1965
|
+
phraseMatches: countCanonicalPhraseMatches(groundingTokens, subjectTokens, contentTokens),
|
|
1966
|
+
coverage: matchedTokens.length / groundingTokens.length
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
function countCanonicalPhraseMatches(queryTokens, subjectTokens, contentTokens) {
|
|
1970
|
+
if (queryTokens.length < 2) {
|
|
1971
|
+
return 0;
|
|
1972
|
+
}
|
|
1973
|
+
const matchedPhrases = /* @__PURE__ */ new Set();
|
|
1974
|
+
for (let size = 2; size <= queryTokens.length; size += 1) {
|
|
1975
|
+
for (let index = 0; index + size <= queryTokens.length; index += 1) {
|
|
1976
|
+
const phraseTokens = queryTokens.slice(index, index + size);
|
|
1977
|
+
if (hasCanonicalConsecutivePhrase(subjectTokens, phraseTokens) || hasCanonicalConsecutivePhrase(contentTokens, phraseTokens)) {
|
|
1978
|
+
matchedPhrases.add(phraseTokens.join(" "));
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
return matchedPhrases.size;
|
|
1983
|
+
}
|
|
1984
|
+
function hasCanonicalConsecutivePhrase(haystack, needle) {
|
|
1985
|
+
if (needle.length === 0 || haystack.length < needle.length) {
|
|
1986
|
+
return false;
|
|
1987
|
+
}
|
|
1988
|
+
for (let index = 0; index + needle.length <= haystack.length; index += 1) {
|
|
1989
|
+
let matches = true;
|
|
1990
|
+
for (let offset = 0; offset < needle.length; offset += 1) {
|
|
1991
|
+
if (haystack[index + offset] !== needle[offset]) {
|
|
1992
|
+
matches = false;
|
|
1993
|
+
break;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
if (matches) {
|
|
1997
|
+
return true;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
return false;
|
|
2001
|
+
}
|
|
2002
|
+
function estimateTokens(entry) {
|
|
2003
|
+
return (entry.subject.length + entry.content.length) / 4;
|
|
2004
|
+
}
|
|
2005
|
+
function parseAroundDate(value, now) {
|
|
2006
|
+
return parseRelativeDate(value, now) ?? inferAroundDate(value, now);
|
|
2007
|
+
}
|
|
2008
|
+
function normalizeLimit(value) {
|
|
2009
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2010
|
+
return 10;
|
|
2011
|
+
}
|
|
2012
|
+
return Math.max(0, Math.floor(value));
|
|
2013
|
+
}
|
|
2014
|
+
function normalizeThreshold(value) {
|
|
2015
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2016
|
+
return 0;
|
|
2017
|
+
}
|
|
2018
|
+
return Math.min(1, Math.max(0, value));
|
|
2019
|
+
}
|
|
2020
|
+
function normalizeBudget(value) {
|
|
2021
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2022
|
+
return null;
|
|
2023
|
+
}
|
|
2024
|
+
return Math.max(0, value);
|
|
2025
|
+
}
|
|
2026
|
+
function normalizeAroundRadius(value) {
|
|
2027
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
2028
|
+
return 14;
|
|
2029
|
+
}
|
|
2030
|
+
return value;
|
|
2031
|
+
}
|
|
2032
|
+
function elapsedMs2(startedAt) {
|
|
2033
|
+
return Math.max(0, Date.now() - startedAt);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
export {
|
|
2037
|
+
recencyScore,
|
|
2038
|
+
gaussianRecency,
|
|
2039
|
+
importanceScore,
|
|
2040
|
+
scoreCandidate,
|
|
2041
|
+
cosineSimilarity,
|
|
2042
|
+
tokenize,
|
|
2043
|
+
buildLexicalPlan,
|
|
2044
|
+
computeLexicalScore,
|
|
2045
|
+
inferAroundDate,
|
|
2046
|
+
parseRelativeDate,
|
|
2047
|
+
resolveClaimSlotPolicy,
|
|
2048
|
+
DEFAULT_CROSS_ENCODER_TOP_K,
|
|
2049
|
+
DEFAULT_CROSS_ENCODER_ALPHA,
|
|
2050
|
+
applyCrossEncoderRerank,
|
|
2051
|
+
DEFAULT_RRF_RANK_CONSTANT,
|
|
2052
|
+
rrfFuse,
|
|
2053
|
+
rrfFuseVectorLexical,
|
|
2054
|
+
DEFAULT_MMR_LAMBDA,
|
|
2055
|
+
NEAR_DUPLICATE_SIMILARITY,
|
|
2056
|
+
maximalMarginalRelevance,
|
|
2057
|
+
DEFAULT_NEIGHBORHOOD_BUDGET,
|
|
2058
|
+
DEFAULT_STRONG_SEED_TOP_N,
|
|
2059
|
+
DEFAULT_STRONG_SEED_SCORE_GAP,
|
|
2060
|
+
DEFAULT_SEEDED_RERANK_WEIGHT,
|
|
2061
|
+
selectStrongSeeds,
|
|
2062
|
+
seededRerank,
|
|
2063
|
+
sharesEntryLineage,
|
|
2064
|
+
sharesEpisodeLineage,
|
|
2065
|
+
sharesProcedureLineage,
|
|
2066
|
+
recall,
|
|
2067
|
+
describeDurableLineageState,
|
|
2068
|
+
formatDurableClaimLifecycle,
|
|
2069
|
+
summarizeClaimFamilyTransition
|
|
2070
|
+
};
|