@xdarkicex/openclaw-memory-libravdb 1.3.11 → 1.3.12
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 +18 -0
- package/docs/README.md +9 -1
- package/docs/ast-v2.md +125 -0
- package/docs/ast.md +70 -0
- package/docs/compaction-evaluation.md +182 -0
- package/docs/continuity.md +488 -0
- package/docs/contributing.md +1 -1
- package/docs/gating.md +53 -255
- package/docs/installation.md +45 -9
- package/docs/mathematics-v2.md +1228 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/context-engine.ts +306 -35
- package/src/continuity.ts +93 -0
- package/src/index.ts +1 -1
- package/src/openclaw-plugin-sdk.d.ts +2 -2
- package/src/recall-utils.ts +100 -8
- package/src/scoring.ts +263 -9
- package/src/tokens.ts +1 -1
- package/src/types.ts +33 -2
package/src/scoring.ts
CHANGED
|
@@ -12,16 +12,52 @@ interface HybridOptions {
|
|
|
12
12
|
userId: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
interface Section7Options {
|
|
16
|
+
queryText: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
userId: string;
|
|
19
|
+
k1?: number;
|
|
20
|
+
k2?: number;
|
|
21
|
+
theta1?: number;
|
|
22
|
+
kappa?: number;
|
|
23
|
+
authorityRecencyLambda?: number;
|
|
24
|
+
authorityRecencyWeight?: number;
|
|
25
|
+
authorityFrequencyWeight?: number;
|
|
26
|
+
authorityAuthoredWeight?: number;
|
|
27
|
+
nowMs?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface HopOptions {
|
|
31
|
+
etaHop?: number;
|
|
32
|
+
thetaHop?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function mergeSection7VariantCandidates(
|
|
36
|
+
ranked: SearchResult[],
|
|
37
|
+
hopExpanded: SearchResult[],
|
|
38
|
+
): SearchResult[] {
|
|
39
|
+
const byID = new Map<string, SearchResult>();
|
|
40
|
+
for (const item of [...ranked, ...hopExpanded]) {
|
|
41
|
+
const existing = byID.get(item.id);
|
|
42
|
+
if (!existing || (item.finalScore ?? 0) > (existing.finalScore ?? 0)) {
|
|
43
|
+
byID.set(item.id, item);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return [...byID.values()].sort((left, right) => (right.finalScore ?? 0) - (left.finalScore ?? 0));
|
|
47
|
+
}
|
|
48
|
+
|
|
15
49
|
export function scoreCandidates(items: SearchResult[], opts: HybridOptions): SearchResult[] {
|
|
16
50
|
const now = Date.now();
|
|
17
|
-
const alpha
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
51
|
+
const { alpha, beta, gamma } = normalizeWeights(
|
|
52
|
+
opts.alpha ?? 0.7,
|
|
53
|
+
opts.beta ?? 0.2,
|
|
54
|
+
opts.gamma ?? 0.1,
|
|
55
|
+
);
|
|
56
|
+
const delta = clamp01(opts.delta ?? 0.5);
|
|
21
57
|
// Lambda units are per-second decay constants.
|
|
22
|
-
const recencyLambdaSession = opts.recencyLambdaSession ?? 0.0001;
|
|
23
|
-
const recencyLambdaUser = opts.recencyLambdaUser ?? 0.00001;
|
|
24
|
-
const recencyLambdaGlobal = opts.recencyLambdaGlobal ?? 0.000002;
|
|
58
|
+
const recencyLambdaSession = Math.max(0, opts.recencyLambdaSession ?? 0.0001);
|
|
59
|
+
const recencyLambdaUser = Math.max(0, opts.recencyLambdaUser ?? 0.00001);
|
|
60
|
+
const recencyLambdaGlobal = Math.max(0, opts.recencyLambdaGlobal ?? 0.000002);
|
|
25
61
|
|
|
26
62
|
return items
|
|
27
63
|
.map((item) => {
|
|
@@ -36,8 +72,9 @@ export function scoreCandidates(items: SearchResult[], opts: HybridOptions): Sea
|
|
|
36
72
|
item.metadata.sessionId === opts.sessionId ? 1.0
|
|
37
73
|
: item.metadata.userId === opts.userId ? 0.6
|
|
38
74
|
: 0.3;
|
|
75
|
+
const similarity = clamp01(item.score);
|
|
39
76
|
const baseScore =
|
|
40
|
-
alpha *
|
|
77
|
+
alpha * similarity +
|
|
41
78
|
beta * recency +
|
|
42
79
|
gamma * scopeBoost;
|
|
43
80
|
const rawDecayRate =
|
|
@@ -47,7 +84,7 @@ export function scoreCandidates(items: SearchResult[], opts: HybridOptions): Sea
|
|
|
47
84
|
item.metadata.type === "summary"
|
|
48
85
|
? 1.0 - delta * decayRate
|
|
49
86
|
: 1.0;
|
|
50
|
-
const finalScore = baseScore * quality;
|
|
87
|
+
const finalScore = clamp01(baseScore * quality);
|
|
51
88
|
|
|
52
89
|
return {
|
|
53
90
|
...item,
|
|
@@ -56,3 +93,220 @@ export function scoreCandidates(items: SearchResult[], opts: HybridOptions): Sea
|
|
|
56
93
|
})
|
|
57
94
|
.sort((a, b) => (b.finalScore ?? 0) - (a.finalScore ?? 0));
|
|
58
95
|
}
|
|
96
|
+
|
|
97
|
+
export function rankSection7VariantCandidates(items: SearchResult[], opts: Section7Options): SearchResult[] {
|
|
98
|
+
const now = opts.nowMs ?? Date.now();
|
|
99
|
+
const k1 = Math.max(1, Math.floor(opts.k1 ?? 16));
|
|
100
|
+
const k2 = Math.max(1, Math.floor(opts.k2 ?? 8));
|
|
101
|
+
const theta1 = clampSimilarity(opts.theta1 ?? 0.2);
|
|
102
|
+
const kappa = Math.max(0, opts.kappa ?? 0.3);
|
|
103
|
+
const { alpha: alphaR, beta: alphaF, gamma: alphaA } = normalizeWeights(
|
|
104
|
+
opts.authorityRecencyWeight ?? 0.5,
|
|
105
|
+
opts.authorityFrequencyWeight ?? 0.2,
|
|
106
|
+
opts.authorityAuthoredWeight ?? 0.3,
|
|
107
|
+
);
|
|
108
|
+
const authorityRecencyLambda = Math.max(0, opts.authorityRecencyLambda ?? 0.00001);
|
|
109
|
+
|
|
110
|
+
const deduped = dedupeCandidates(items);
|
|
111
|
+
const coarseRaw = [...deduped]
|
|
112
|
+
.sort((left, right) => similarity(right) - similarity(left))
|
|
113
|
+
.slice(0, k1);
|
|
114
|
+
const coarseFiltered = coarseRaw.filter((item) => similarity(item) >= theta1);
|
|
115
|
+
const maxAccessCount = coarseFiltered.reduce((max, item) => Math.max(max, accessCount(item)), 0);
|
|
116
|
+
const keywords = extractKeywords(opts.queryText);
|
|
117
|
+
|
|
118
|
+
return coarseFiltered
|
|
119
|
+
.map((item) => {
|
|
120
|
+
const omega = authorityWeight(item, {
|
|
121
|
+
now,
|
|
122
|
+
authorityRecencyLambda,
|
|
123
|
+
alphaR,
|
|
124
|
+
alphaF,
|
|
125
|
+
alphaA,
|
|
126
|
+
maxAccessCount,
|
|
127
|
+
});
|
|
128
|
+
const sim = Math.max(similarity(item), 0);
|
|
129
|
+
const keywordCoverage = normalizedKeywordCoverage(keywords, item.text);
|
|
130
|
+
const finalScore = omega * sim * ((1 + kappa * keywordCoverage) / (1 + kappa));
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
...item,
|
|
134
|
+
finalScore: clamp01(finalScore),
|
|
135
|
+
};
|
|
136
|
+
})
|
|
137
|
+
.sort((left, right) => (right.finalScore ?? 0) - (left.finalScore ?? 0))
|
|
138
|
+
.slice(0, Math.min(k2, coarseFiltered.length));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function expandSection7HopCandidates(
|
|
142
|
+
ranked: SearchResult[],
|
|
143
|
+
authoredVariantRecords: SearchResult[],
|
|
144
|
+
opts: HopOptions,
|
|
145
|
+
): SearchResult[] {
|
|
146
|
+
const etaHop = clampOpenUnit(opts.etaHop ?? 0.5);
|
|
147
|
+
const thetaHop = clamp01(opts.thetaHop ?? 0.15);
|
|
148
|
+
const rankedIDs = new Set(ranked.map((item) => item.id));
|
|
149
|
+
const authoredByID = new Map(authoredVariantRecords.map((item) => [item.id, item] as const));
|
|
150
|
+
const bestScores = new Map<string, number>();
|
|
151
|
+
|
|
152
|
+
for (const parent of ranked) {
|
|
153
|
+
const parentScore = clamp01(parent.finalScore ?? 0);
|
|
154
|
+
for (const targetID of hopTargets(parent)) {
|
|
155
|
+
if (rankedIDs.has(targetID)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!authoredByID.has(targetID)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const candidateScore = etaHop * parentScore;
|
|
162
|
+
if (candidateScore > (bestScores.get(targetID) ?? -1)) {
|
|
163
|
+
bestScores.set(targetID, candidateScore);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return [...bestScores.entries()]
|
|
169
|
+
.filter(([, score]) => score >= thetaHop)
|
|
170
|
+
.map(([id, score]) => ({
|
|
171
|
+
...authoredByID.get(id)!,
|
|
172
|
+
finalScore: score,
|
|
173
|
+
}))
|
|
174
|
+
.sort((left, right) => (right.finalScore ?? 0) - (left.finalScore ?? 0));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function clamp01(value: number): number {
|
|
178
|
+
return Math.min(1, Math.max(0, value));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function clampSimilarity(value: number): number {
|
|
182
|
+
return Math.min(1, Math.max(-1, value));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function clampOpenUnit(value: number): number {
|
|
186
|
+
return Math.min(0.999999, Math.max(0.000001, value));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function normalizeWeights(alpha: number, beta: number, gamma: number): { alpha: number; beta: number; gamma: number } {
|
|
190
|
+
alpha = clamp01(alpha);
|
|
191
|
+
beta = clamp01(beta);
|
|
192
|
+
gamma = clamp01(gamma);
|
|
193
|
+
|
|
194
|
+
const sum = alpha + beta + gamma;
|
|
195
|
+
if (sum <= 0) {
|
|
196
|
+
return { alpha: 0.7, beta: 0.2, gamma: 0.1 };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
alpha: alpha / sum,
|
|
201
|
+
beta: beta / sum,
|
|
202
|
+
gamma: gamma / sum,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function dedupeCandidates(items: SearchResult[]): SearchResult[] {
|
|
207
|
+
const seen = new Set<string>();
|
|
208
|
+
const out: SearchResult[] = [];
|
|
209
|
+
for (const item of items) {
|
|
210
|
+
const key = `${typeof item.metadata.collection === "string" ? item.metadata.collection : ""}::${item.id}`;
|
|
211
|
+
if (seen.has(key)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
seen.add(key);
|
|
215
|
+
out.push(item);
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function similarity(item: SearchResult): number {
|
|
221
|
+
return clampSimilarity(typeof item.score === "number" ? item.score : 0);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function accessCount(item: SearchResult): number {
|
|
225
|
+
const raw = item.metadata.access_count;
|
|
226
|
+
return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function authorityWeight(
|
|
230
|
+
item: SearchResult,
|
|
231
|
+
opts: {
|
|
232
|
+
now: number;
|
|
233
|
+
authorityRecencyLambda: number;
|
|
234
|
+
alphaR: number;
|
|
235
|
+
alphaF: number;
|
|
236
|
+
alphaA: number;
|
|
237
|
+
maxAccessCount: number;
|
|
238
|
+
},
|
|
239
|
+
): number {
|
|
240
|
+
const ts = typeof item.metadata.ts === "number" ? item.metadata.ts : opts.now;
|
|
241
|
+
const ageSeconds = Math.max(0, opts.now - ts) / 1000;
|
|
242
|
+
const recency = Math.exp(-opts.authorityRecencyLambda * ageSeconds);
|
|
243
|
+
const frequency = normalizedFrequency(accessCount(item), opts.maxAccessCount);
|
|
244
|
+
const authoredAuthority = clamp01(
|
|
245
|
+
typeof item.metadata.authority === "number"
|
|
246
|
+
? item.metadata.authority
|
|
247
|
+
: item.metadata.authored === true
|
|
248
|
+
? 1
|
|
249
|
+
: 0,
|
|
250
|
+
);
|
|
251
|
+
return clamp01(
|
|
252
|
+
opts.alphaR * recency +
|
|
253
|
+
opts.alphaF * frequency +
|
|
254
|
+
opts.alphaA * authoredAuthority,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function normalizedFrequency(accessCount: number, maxAccessCount: number): number {
|
|
259
|
+
if (accessCount <= 0 || maxAccessCount <= 0) {
|
|
260
|
+
return 0;
|
|
261
|
+
}
|
|
262
|
+
return Math.log(1 + accessCount) / Math.log(1 + maxAccessCount + 1);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function extractKeywords(text: string): string[] {
|
|
266
|
+
const tokens = normalizeTerms(text);
|
|
267
|
+
const seen = new Set<string>();
|
|
268
|
+
const keywords: string[] = [];
|
|
269
|
+
for (const token of tokens) {
|
|
270
|
+
if (token.length < 3 || seen.has(token)) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
seen.add(token);
|
|
274
|
+
keywords.push(token);
|
|
275
|
+
}
|
|
276
|
+
return keywords;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function normalizedKeywordCoverage(keywords: string[], text: string): number {
|
|
280
|
+
if (keywords.length === 0) {
|
|
281
|
+
return 0;
|
|
282
|
+
}
|
|
283
|
+
const docTerms = new Set(normalizeTerms(text));
|
|
284
|
+
let matches = 0;
|
|
285
|
+
for (const keyword of keywords) {
|
|
286
|
+
if (docTerms.has(keyword)) {
|
|
287
|
+
matches += 1;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return matches / Math.max(keywords.length, 1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function normalizeTerms(text: string): string[] {
|
|
294
|
+
return text
|
|
295
|
+
.toLowerCase()
|
|
296
|
+
.split(/[^a-z0-9_]+/i)
|
|
297
|
+
.filter((term) => term.length > 0);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function hopTargets(item: SearchResult): string[] {
|
|
301
|
+
const raw = item.metadata.hop_targets;
|
|
302
|
+
if (Array.isArray(raw)) {
|
|
303
|
+
return raw.filter((target): target is string => typeof target === "string" && target.length > 0);
|
|
304
|
+
}
|
|
305
|
+
if (typeof raw === "string") {
|
|
306
|
+
return raw
|
|
307
|
+
.split(",")
|
|
308
|
+
.map((part) => part.trim())
|
|
309
|
+
.filter((part) => part.length > 0);
|
|
310
|
+
}
|
|
311
|
+
return [];
|
|
312
|
+
}
|
package/src/tokens.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -37,7 +37,23 @@ export interface PluginConfig {
|
|
|
37
37
|
recencyLambdaUser?: number;
|
|
38
38
|
recencyLambdaGlobal?: number;
|
|
39
39
|
tokenBudgetFraction?: number;
|
|
40
|
+
authoredHardBudgetFraction?: number;
|
|
41
|
+
authoredSoftBudgetFraction?: number;
|
|
42
|
+
section7StartupTokenBudgetTokens?: number;
|
|
43
|
+
continuityMinTurns?: number;
|
|
44
|
+
continuityTailBudgetTokens?: number;
|
|
45
|
+
continuityPriorContextTokens?: number;
|
|
40
46
|
compactThreshold?: number;
|
|
47
|
+
section7CoarseTopK?: number;
|
|
48
|
+
section7SecondPassTopK?: number;
|
|
49
|
+
section7Theta1?: number;
|
|
50
|
+
section7Kappa?: number;
|
|
51
|
+
section7HopEta?: number;
|
|
52
|
+
section7HopThreshold?: number;
|
|
53
|
+
section7AuthorityRecencyLambda?: number;
|
|
54
|
+
section7AuthorityRecencyWeight?: number;
|
|
55
|
+
section7AuthorityFrequencyWeight?: number;
|
|
56
|
+
section7AuthorityAuthoredWeight?: number;
|
|
41
57
|
ollamaUrl?: string;
|
|
42
58
|
compactModel?: string;
|
|
43
59
|
rpcTimeoutMs?: number;
|
|
@@ -75,6 +91,19 @@ export interface SearchResult {
|
|
|
75
91
|
sessionId?: string;
|
|
76
92
|
userId?: string;
|
|
77
93
|
role?: string;
|
|
94
|
+
source_doc?: string;
|
|
95
|
+
node_kind?: string;
|
|
96
|
+
ordinal?: number;
|
|
97
|
+
tier?: number;
|
|
98
|
+
authored?: boolean;
|
|
99
|
+
authority?: number;
|
|
100
|
+
access_count?: number;
|
|
101
|
+
collection?: string;
|
|
102
|
+
hop_targets?: string[] | string;
|
|
103
|
+
token_estimate?: number;
|
|
104
|
+
continuity_tail?: boolean;
|
|
105
|
+
continuity_base?: boolean;
|
|
106
|
+
continuity_bundle_id?: string;
|
|
78
107
|
[key: string]: unknown;
|
|
79
108
|
};
|
|
80
109
|
finalScore?: number;
|
|
@@ -109,8 +138,10 @@ export interface RpcCallOptions {
|
|
|
109
138
|
export interface RecallCacheEntry<T = unknown> {
|
|
110
139
|
userId: string;
|
|
111
140
|
queryText: string;
|
|
112
|
-
|
|
113
|
-
|
|
141
|
+
durableVariantHits: T[];
|
|
142
|
+
userHits?: T[];
|
|
143
|
+
globalHits?: T[];
|
|
144
|
+
authoredVariantHits?: T[];
|
|
114
145
|
}
|
|
115
146
|
|
|
116
147
|
export interface RecallCache<T = unknown> {
|