memories-lite 0.10.1 → 0.99.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/defaults.js +11 -4
- package/dist/config/manager.js +2 -4
- package/dist/memory/index.d.ts +11 -0
- package/dist/memory/index.js +62 -124
- package/dist/memory/memory.types.d.ts +1 -2
- package/dist/prompts/index.d.ts +53 -17
- package/dist/prompts/index.js +63 -23
- package/dist/types/index.d.ts +28 -137
- package/dist/types/index.js +2 -8
- package/memories-lite-a42ac5108869b599bcbac21069f63fb47f07452fcc4b87e89b3c06a945612d0b.db +0 -0
- package/memories-lite-a9137698d8d3fdbf27efcdc8cd372084b52d484e8db866c5455bbb3f85299b54.db +0 -0
- package/package.json +1 -1
- package/src/config/defaults.ts +11 -4
- package/src/config/manager.ts +2 -4
- package/src/memory/index.ts +76 -158
- package/src/memory/memory.types.ts +1 -2
- package/src/prompts/index.ts +69 -26
- package/src/types/index.ts +4 -11
- package/tests/lite.spec.ts +44 -34
- package/tests/memory.discussion.test.ts +279 -0
- package/tests/memory.update.test.ts +5 -1
- package/tests/memory.facts.test.ts +0 -175
- package/tests/memory.todo.test.ts +0 -126
package/src/memory/index.ts
CHANGED
|
@@ -16,10 +16,8 @@ import {
|
|
|
16
16
|
HistoryManagerFactory,
|
|
17
17
|
} from "../utils/factory";
|
|
18
18
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
getUpdateMemoryMessages,
|
|
22
|
-
MemoryUpdateSchema,
|
|
19
|
+
DiscussionSynthesisSchema,
|
|
20
|
+
getDiscussionSynthesisMessages,
|
|
23
21
|
removeCodeBlocks,
|
|
24
22
|
} from "../prompts";
|
|
25
23
|
import { DummyHistoryManager } from "../storage/DummyHistoryManager";
|
|
@@ -152,180 +150,98 @@ export class MemoriesLite {
|
|
|
152
150
|
}
|
|
153
151
|
|
|
154
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Capture une discussion et génère une synthèse (title + summary)
|
|
155
|
+
* Utilise getDiscussionSynthesisMessages pour produire la synthèse
|
|
156
|
+
*/
|
|
155
157
|
private async addToVectorStore(
|
|
156
158
|
messages: Message[],
|
|
157
159
|
metadata: Record<string, any>,
|
|
158
160
|
userId: string,
|
|
159
161
|
filters: SearchFilters,
|
|
160
|
-
|
|
162
|
+
capturePrompt?: string,
|
|
161
163
|
): Promise<MemoryItem[]> {
|
|
162
164
|
|
|
163
165
|
const $t = this.$t;
|
|
164
166
|
const vectorStore = await this.getVectorStore(userId);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
//
|
|
167
|
+
|
|
168
|
+
//
|
|
169
|
+
// Formater les messages pour la synthèse
|
|
170
|
+
const parsedMessages = messages
|
|
171
|
+
.filter((m) => typeof m.content === 'string')
|
|
172
|
+
.map((m) => `**${m.role.toUpperCase()}**: ${$t(m.content as string)}`)
|
|
173
|
+
.join("\n\n");
|
|
168
174
|
|
|
169
|
-
|
|
175
|
+
//
|
|
176
|
+
// Générer la synthèse via LLM
|
|
177
|
+
const [systemPrompt, userPrompt] = getDiscussionSynthesisMessages(
|
|
178
|
+
parsedMessages,
|
|
179
|
+
capturePrompt || this.customPrompt
|
|
180
|
+
);
|
|
170
181
|
|
|
171
182
|
const response = await this.llm.generateResponse(
|
|
172
183
|
[
|
|
173
184
|
{ role: "system", content: systemPrompt },
|
|
174
185
|
{ role: "user", content: userPrompt },
|
|
175
186
|
],
|
|
176
|
-
{...zodResponseFormat(
|
|
187
|
+
{ ...zodResponseFormat(DiscussionSynthesisSchema, "DiscussionSynthesis") },
|
|
188
|
+
[],
|
|
189
|
+
false
|
|
177
190
|
);
|
|
178
|
-
|
|
191
|
+
|
|
192
|
+
//
|
|
193
|
+
// Parser la réponse
|
|
194
|
+
const parsedResponse = (res: any) => {
|
|
179
195
|
try {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return response;
|
|
196
|
+
if (typeof res === 'object') {
|
|
197
|
+
return res;
|
|
183
198
|
}
|
|
184
|
-
const cleanResponse = removeCodeBlocks(
|
|
199
|
+
const cleanResponse = removeCodeBlocks(res as string);
|
|
185
200
|
return JSON.parse(cleanResponse);
|
|
186
201
|
} catch (e) {
|
|
187
|
-
console.error(
|
|
188
|
-
|
|
189
|
-
response,
|
|
190
|
-
e,
|
|
191
|
-
response
|
|
192
|
-
);
|
|
193
|
-
return [];
|
|
202
|
+
console.error("Failed to parse synthesis from LLM response:", res, e);
|
|
203
|
+
return { title: "Sans titre", summary: "" };
|
|
194
204
|
}
|
|
195
|
-
}
|
|
196
|
-
//
|
|
197
|
-
// can use native structured output
|
|
198
|
-
// Drop factual facts at capture level (do not store factual memories)
|
|
199
|
-
// FIXME Drop factual should be done at prompt level
|
|
200
|
-
const facts = parsedResponse(response).facts?.filter((f:any) => !f.existing )||[];
|
|
205
|
+
};
|
|
201
206
|
|
|
202
|
-
|
|
203
|
-
// console.log("-- DBG facts:", facts);
|
|
207
|
+
const { title, summary } = parsedResponse(response);
|
|
204
208
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
209
|
+
if (!summary) {
|
|
210
|
+
console.warn("-- ⚠️ Empty summary from LLM, skipping memory creation");
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
208
213
|
|
|
209
214
|
//
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
// Create embeddings and search for similar memories
|
|
213
|
-
for (const elem of facts) {
|
|
214
|
-
const fact = elem.fact;
|
|
215
|
-
const embedding = await this.embedder.embed(fact);
|
|
216
|
-
newMessageEmbeddings[fact] = embedding;
|
|
217
|
-
|
|
218
|
-
const existingMemories = await vectorStore.search(
|
|
219
|
-
embedding,
|
|
220
|
-
5,
|
|
221
|
-
filters,
|
|
222
|
-
);
|
|
223
|
-
for (const mem of existingMemories) {
|
|
224
|
-
retrievedOldMemory.push({ id: mem.id, text: mem.payload.data,type: mem.payload.type });
|
|
225
|
-
}
|
|
226
|
-
}
|
|
215
|
+
// Créer l'embedding sur le summary (pour recherche sémantique)
|
|
216
|
+
const embedding = await this.embedder.embed(summary);
|
|
227
217
|
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
218
|
+
//
|
|
219
|
+
// Préparer les métadonnées
|
|
220
|
+
const memoryType: MemoryType = metadata.type || 'discussion';
|
|
221
|
+
const memoryMetadata = {
|
|
222
|
+
...metadata,
|
|
223
|
+
title,
|
|
224
|
+
type: memoryType,
|
|
225
|
+
userId,
|
|
226
|
+
};
|
|
234
227
|
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
// Get memory update decisions
|
|
243
|
-
const lastUserMessage = [...messages].reverse().find(m => m.role === 'user');
|
|
244
|
-
const userInstruction = typeof lastUserMessage?.content === 'string' ? lastUserMessage?.content as string : '';
|
|
245
|
-
const updatePrompt = getUpdateMemoryMessages(uniqueOldMemories, facts, 'French', userInstruction);
|
|
246
|
-
|
|
247
|
-
// console.log("-- DBG updatePrompt:", updatePrompt);
|
|
248
|
-
const updateResponse = await this.llm.generateResponse(
|
|
249
|
-
[{ role: "user", content: updatePrompt }],
|
|
250
|
-
{...zodResponseFormat(MemoryUpdateSchema,"Memory")},[],false,
|
|
228
|
+
//
|
|
229
|
+
// Stocker la mémoire
|
|
230
|
+
const memoryId = await this.createMemory(
|
|
231
|
+
summary,
|
|
232
|
+
{ [summary]: embedding },
|
|
233
|
+
memoryMetadata,
|
|
234
|
+
userId,
|
|
251
235
|
);
|
|
252
|
-
// console.log("-- DBG merge:", updatePrompt);
|
|
253
236
|
|
|
254
|
-
|
|
237
|
+
console.log(`-- 🧠 Memory created: "${title}" (${memoryType})`);
|
|
255
238
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
if(action.reason === "undefined") {
|
|
264
|
-
console.log(`-- ⛔ LLM Error: ${action.event}, ${action.type}, "${action.text}"`);
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
console.log(`-- DBG memory "${userId}": ${action.event}, ${action.type}, "${action.text}", why: "${action.reason}"`);
|
|
268
|
-
try {
|
|
269
|
-
switch (action.event) {
|
|
270
|
-
case "ADD": {
|
|
271
|
-
if(!action.type) {
|
|
272
|
-
// log error
|
|
273
|
-
console.error("Type is mandatory to manage memories:", action);
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
metadata.type = action.type;
|
|
277
|
-
const memoryId = await this.createMemory(
|
|
278
|
-
action.text,
|
|
279
|
-
newMessageEmbeddings,
|
|
280
|
-
metadata,
|
|
281
|
-
userId,
|
|
282
|
-
);
|
|
283
|
-
results.push({
|
|
284
|
-
id: memoryId,
|
|
285
|
-
memory: action.text,
|
|
286
|
-
type: action.type,
|
|
287
|
-
metadata: { event: action.event },
|
|
288
|
-
});
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
case "UPDATE": {
|
|
292
|
-
const realMemoryId = tempUuidMapping[action.id];
|
|
293
|
-
const type = metadata.type = uniqueOldMemories[action.id].type || action.type;
|
|
294
|
-
await this.updateMemory(
|
|
295
|
-
realMemoryId,
|
|
296
|
-
action.text,
|
|
297
|
-
newMessageEmbeddings,
|
|
298
|
-
metadata,
|
|
299
|
-
userId,
|
|
300
|
-
);
|
|
301
|
-
results.push({
|
|
302
|
-
id: realMemoryId,
|
|
303
|
-
memory: action.text,
|
|
304
|
-
type,
|
|
305
|
-
metadata: {
|
|
306
|
-
event: action.event,
|
|
307
|
-
previousMemory: action.old_memory,
|
|
308
|
-
},
|
|
309
|
-
});
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
case "DELETE": {
|
|
313
|
-
const realMemoryId = tempUuidMapping[action.id];
|
|
314
|
-
await this.deleteMemory(realMemoryId, userId);
|
|
315
|
-
results.push({
|
|
316
|
-
id: realMemoryId,
|
|
317
|
-
memory: action.text,
|
|
318
|
-
type: action.type,
|
|
319
|
-
metadata: { event: action.event },
|
|
320
|
-
});
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
} catch (error) {
|
|
325
|
-
console.error(`Error processing memory action: ${error}`);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
return results;
|
|
239
|
+
return [{
|
|
240
|
+
id: memoryId,
|
|
241
|
+
memory: summary,
|
|
242
|
+
type: memoryType,
|
|
243
|
+
metadata: { title, event: "ADD" },
|
|
244
|
+
}];
|
|
329
245
|
}
|
|
330
246
|
|
|
331
247
|
static fromConfig(configDict: Record<string, any>): MemoriesLite {
|
|
@@ -342,24 +258,24 @@ export class MemoriesLite {
|
|
|
342
258
|
return LiteVectorStore.from(userId, this.vectorStoreConfig);
|
|
343
259
|
}
|
|
344
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Capture une discussion et génère une mémoire (title + summary)
|
|
263
|
+
*
|
|
264
|
+
* @param messages - Messages de la discussion ou texte brut
|
|
265
|
+
* @param userId - ID utilisateur
|
|
266
|
+
* @param config - Options incluant capturePrompt pour personnaliser la synthèse
|
|
267
|
+
*/
|
|
345
268
|
async capture(
|
|
346
269
|
messages: string | Message[],
|
|
347
270
|
userId: string,
|
|
348
271
|
config: AddMemoryOptions,
|
|
349
272
|
): Promise<SearchResult> {
|
|
350
|
-
// await this._captureEvent("add", {
|
|
351
|
-
// message_count: Array.isArray(messages) ? messages.length : 1,
|
|
352
|
-
// has_metadata: !!config.metadata,
|
|
353
|
-
// has_filters: !!config.filters,
|
|
354
|
-
// infer: config.infer,
|
|
355
|
-
// });
|
|
356
273
|
const {
|
|
357
274
|
agentId,
|
|
358
275
|
runId,
|
|
359
276
|
metadata = {},
|
|
360
277
|
filters = {},
|
|
361
|
-
|
|
362
|
-
customFacts
|
|
278
|
+
capturePrompt
|
|
363
279
|
} = config;
|
|
364
280
|
|
|
365
281
|
if (agentId) filters.agentId = metadata.agentId = agentId;
|
|
@@ -377,16 +293,18 @@ export class MemoriesLite {
|
|
|
377
293
|
|
|
378
294
|
const final_parsedMessages = await parse_vision_messages(parsedMessages);
|
|
379
295
|
|
|
380
|
-
//
|
|
296
|
+
//
|
|
297
|
+
// Générer synthèse et stocker
|
|
381
298
|
const vectorStoreResult = await this.addToVectorStore(
|
|
382
299
|
final_parsedMessages,
|
|
383
300
|
metadata,
|
|
384
301
|
userId,
|
|
385
302
|
filters,
|
|
386
|
-
|
|
303
|
+
capturePrompt,
|
|
387
304
|
);
|
|
388
305
|
|
|
389
|
-
//
|
|
306
|
+
//
|
|
307
|
+
// Graph store (si configuré)
|
|
390
308
|
let graphResult;
|
|
391
309
|
if (this.graphMemory) {
|
|
392
310
|
try {
|
|
@@ -10,8 +10,7 @@ export interface Entity {
|
|
|
10
10
|
export interface AddMemoryOptions extends Entity {
|
|
11
11
|
metadata?: Record<string, any>;
|
|
12
12
|
filters?: SearchFilters;
|
|
13
|
-
|
|
14
|
-
infer?: boolean;
|
|
13
|
+
capturePrompt?: string;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export interface SearchMemoryOptions extends Entity {
|
package/src/prompts/index.ts
CHANGED
|
@@ -1,36 +1,83 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { MemoryItem } from "../types";
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
// DISCUSSION SYNTHESIS - Nouveau système de mémoire
|
|
6
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Schema pour la synthèse de discussion
|
|
10
|
+
* Produit un titre court et une synthèse opérationnelle
|
|
11
|
+
*/
|
|
12
|
+
export const DiscussionSynthesisSchema = z.object({
|
|
13
|
+
title: z.string().describe("Titre court et descriptif (6-10 mots)"),
|
|
14
|
+
summary: z.string().describe("Synthèse opérationnelle des points clés (50-100 mots)")
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Prompt par défaut pour la synthèse de discussion
|
|
19
|
+
* Peut être remplacé via capturePrompt dans AddMemoryOptions
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_DISCUSSION_PROMPT = `Tu es un expert en synthèse opérationnelle.
|
|
22
|
+
|
|
23
|
+
À partir de cette discussion, génère :
|
|
24
|
+
1. TITRE: Un titre court et descriptif (6-10 mots) qui capture l'essence de la demande
|
|
25
|
+
2. SUMMARY: Les points clés du chemin de résolution (50-100 mots)
|
|
26
|
+
|
|
27
|
+
Cette synthèse servira à retrouver et réappliquer ce pattern de résolution similaire.
|
|
28
|
+
Utilise la même langue que la discussion.
|
|
29
|
+
|
|
30
|
+
Discussion à synthétiser:
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Génère les messages pour la synthèse de discussion
|
|
35
|
+
* @param discussion - Contenu de la discussion formatée
|
|
36
|
+
* @param capturePrompt - Prompt custom optionnel (remplace DEFAULT_DISCUSSION_PROMPT)
|
|
37
|
+
* @returns [systemPrompt, userPrompt]
|
|
38
|
+
*/
|
|
39
|
+
export function getDiscussionSynthesisMessages(
|
|
40
|
+
discussion: string,
|
|
41
|
+
capturePrompt?: string
|
|
42
|
+
): [string, string] {
|
|
43
|
+
const systemPrompt = capturePrompt || DEFAULT_DISCUSSION_PROMPT;
|
|
44
|
+
return [systemPrompt, discussion];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
48
|
+
// @deprecated - Ancien système de capture (todo, factual)
|
|
49
|
+
// Ces exports sont conservés pour compatibilité mais ne doivent plus être utilisés
|
|
50
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @deprecated Use DiscussionSynthesisSchema instead
|
|
54
|
+
*/
|
|
5
55
|
export const FactRetrievalSchema_simple = z.object({
|
|
6
56
|
facts: z
|
|
7
57
|
.array(z.string())
|
|
8
58
|
.describe("An array of distinct facts extracted from the conversation."),
|
|
9
59
|
});
|
|
10
60
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
//
|
|
61
|
+
/**
|
|
62
|
+
* @deprecated Use DiscussionSynthesisSchema instead
|
|
63
|
+
* Types todo et factual supprimés - seul assistant_preference reste pour compatibilité
|
|
64
|
+
*/
|
|
16
65
|
export const FactRetrievalSchema_extended = z.object({
|
|
17
66
|
facts: z
|
|
18
67
|
.array(
|
|
19
68
|
z.object({
|
|
20
69
|
fact: z.string().describe("The fact extracted from the conversation."),
|
|
21
70
|
existing: z.boolean().describe("Whether the fact is already present"),
|
|
22
|
-
type: z.enum(["assistant_preference"
|
|
23
|
-
.describe(`The type of the fact.
|
|
24
|
-
Use 'assistant_preference' for Assistant behavior preferences (style/language/constraints/commands).
|
|
25
|
-
Use 'factual' for stable user facts (identity, preferences, beliefs, work context).
|
|
26
|
-
Use 'todo' ONLY if the user explicitly asks to save/keep as a todo (e.g., « garde/enregistre en todo », « ajoute un todo »). Do not infer todos.
|
|
27
|
-
`),
|
|
71
|
+
type: z.enum(["assistant_preference"])
|
|
72
|
+
.describe(`The type of the fact. Only 'assistant_preference' is supported.`),
|
|
28
73
|
})
|
|
29
74
|
)
|
|
30
75
|
});
|
|
31
76
|
|
|
32
77
|
|
|
33
|
-
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated Memory updates are disabled - use capture() for new memories
|
|
80
|
+
*/
|
|
34
81
|
export const MemoryUpdateSchema = z.object({
|
|
35
82
|
memory: z
|
|
36
83
|
.array(
|
|
@@ -54,21 +101,14 @@ export const MemoryUpdateSchema = z.object({
|
|
|
54
101
|
"The reason why you selected this event.",
|
|
55
102
|
),
|
|
56
103
|
type: z
|
|
57
|
-
.enum(["
|
|
58
|
-
.describe("Type of the memory.
|
|
104
|
+
.enum(["assistant_preference"])
|
|
105
|
+
.describe("Type of the memory. Only 'assistant_preference' is supported."),
|
|
59
106
|
}),
|
|
60
107
|
)
|
|
61
108
|
.describe(
|
|
62
109
|
"An array representing the state of memory items after processing new facts.",
|
|
63
110
|
),
|
|
64
111
|
});
|
|
65
|
-
/**
|
|
66
|
-
* Practical Application:
|
|
67
|
-
*
|
|
68
|
-
* If the task is "factual" (e.g., "Where do I live?", "What's my job?") → retrieve factual memory.
|
|
69
|
-
* If the task is about assistant behavior (e.g., "How should I respond?") → retrieve assistant_preference memory.
|
|
70
|
-
* If the task is a user task/reminder (e.g., "Add a reminder to call the bank tomorrow") → retrieve todo memory.
|
|
71
|
-
*/
|
|
72
112
|
export const MEMORY_STRING_SYSTEM = `# DIRECTIVES FOR MEMORIES
|
|
73
113
|
- Information stored in memory is always enclosed within the <memories> tag.
|
|
74
114
|
- Prioritize the latest user message over memories (the user's current question is authoritative).
|
|
@@ -80,10 +120,10 @@ export const MEMORY_STRING_SYSTEM = `# DIRECTIVES FOR MEMORIES
|
|
|
80
120
|
|
|
81
121
|
export const MEMORY_STRING_PREFIX = "Use these contextual memories to guide your response. Prioritize the user's question. Ignore irrelevant memories."
|
|
82
122
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
123
|
+
/**
|
|
124
|
+
* @deprecated Use getDiscussionSynthesisMessages instead
|
|
125
|
+
* Cette fonction est conservée pour compatibilité avec l'ancien système
|
|
126
|
+
*/
|
|
87
127
|
export function getFactRetrievalMessages(
|
|
88
128
|
parsedMessages: string,
|
|
89
129
|
customRules: string = "",
|
|
@@ -132,6 +172,9 @@ Remember the following:
|
|
|
132
172
|
return [systemPrompt, userPrompt];
|
|
133
173
|
}
|
|
134
174
|
|
|
175
|
+
/**
|
|
176
|
+
* @deprecated Memory updates are disabled by config
|
|
177
|
+
*/
|
|
135
178
|
export function getUpdateMemoryMessages(
|
|
136
179
|
retrievedOldMemory: Array<{ id: string; text: string }>,
|
|
137
180
|
newRetrievedFacts: any[],
|
package/src/types/index.ts
CHANGED
|
@@ -72,9 +72,8 @@ export interface MemoryTypeConfig {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export interface MemoryScoringConfig {
|
|
75
|
-
todo: MemoryTypeConfig;
|
|
76
|
-
factual: MemoryTypeConfig;
|
|
77
75
|
assistant_preference: MemoryTypeConfig;
|
|
76
|
+
discussion: MemoryTypeConfig;
|
|
78
77
|
default: MemoryTypeConfig; // Fallback if type is missing or unknown
|
|
79
78
|
}
|
|
80
79
|
|
|
@@ -101,7 +100,7 @@ export interface MemoryConfig {
|
|
|
101
100
|
enableGraph?: boolean;
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
export type MemoryType = '
|
|
103
|
+
export type MemoryType = 'assistant_preference' | 'discussion';
|
|
105
104
|
|
|
106
105
|
export interface MemoryItem {
|
|
107
106
|
id: string;
|
|
@@ -162,19 +161,13 @@ export const MemoryConfigSchema = z.object({
|
|
|
162
161
|
dimension: z.number().optional(),
|
|
163
162
|
client: z.any().optional(),
|
|
164
163
|
scoring: z.object({
|
|
165
|
-
|
|
166
|
-
alpha: z.number(),
|
|
167
|
-
beta: z.number(),
|
|
168
|
-
gamma: z.number(),
|
|
169
|
-
halfLifeDays: z.number(),
|
|
170
|
-
}),
|
|
171
|
-
factual: z.object({
|
|
164
|
+
assistant_preference: z.object({
|
|
172
165
|
alpha: z.number(),
|
|
173
166
|
beta: z.number(),
|
|
174
167
|
gamma: z.number(),
|
|
175
168
|
halfLifeDays: z.number(),
|
|
176
169
|
}),
|
|
177
|
-
|
|
170
|
+
discussion: z.object({
|
|
178
171
|
alpha: z.number(),
|
|
179
172
|
beta: z.number(),
|
|
180
173
|
gamma: z.number(),
|
package/tests/lite.spec.ts
CHANGED
|
@@ -25,6 +25,10 @@ jest.mock('sqlite3', () => {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Tests for LiteVectorStore private methods
|
|
30
|
+
* Updated to use new memory types: assistant_preference and discussion
|
|
31
|
+
*/
|
|
28
32
|
describe('LiteVectorStore Private Methods', () => {
|
|
29
33
|
let store: LiteVectorStore;
|
|
30
34
|
const userId = 'test-user';
|
|
@@ -98,59 +102,65 @@ describe('LiteVectorStore Private Methods', () => {
|
|
|
98
102
|
const veryOldDate = new Date(now.getTime() - 400 * 24 * 60 * 60 * 1000).toISOString();
|
|
99
103
|
const scoring = DEFAULT_MEMORY_CONFIG.vectorStore.config.scoring!;
|
|
100
104
|
|
|
101
|
-
it('should prioritize cosine similarity for
|
|
102
|
-
|
|
105
|
+
it('should prioritize cosine similarity for assistant_preference (high alpha)', () => {
|
|
106
|
+
//
|
|
107
|
+
// assistant_preference: { alpha: 0.60, beta: 0.05, gamma: 0.35, halfLifeDays: Infinity }
|
|
108
|
+
const payload: MemoryPayload = { memoryId: 'mem-ap1', userId: userId, type: 'assistant_preference', createdAt: twoDaysAgo };
|
|
103
109
|
const cosineScore = 0.9;
|
|
104
|
-
const expectedRecency =
|
|
105
|
-
const expectedScore = scoring.
|
|
110
|
+
const expectedRecency = 1.0; // Infinity halfLife → recency = 1
|
|
111
|
+
const expectedScore = scoring.assistant_preference.alpha * cosineScore + scoring.assistant_preference.beta * expectedRecency + scoring.assistant_preference.gamma;
|
|
106
112
|
const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
|
|
107
113
|
expect(hybridScore).toBeCloseTo(expectedScore, 5);
|
|
108
|
-
|
|
114
|
+
// alpha * cosine (0.54) > beta * recency (0.05)
|
|
115
|
+
expect(scoring.assistant_preference.alpha * cosineScore).toBeGreaterThan(scoring.assistant_preference.beta * expectedRecency);
|
|
109
116
|
});
|
|
110
117
|
|
|
111
|
-
it('should
|
|
112
|
-
|
|
118
|
+
it('should return constant score for discussion (beta=1, alpha=0, gamma=0)', () => {
|
|
119
|
+
//
|
|
120
|
+
// discussion: { alpha: 0, beta: 1, gamma: 0, halfLifeDays: Infinity }
|
|
121
|
+
// Score = 0 * cosine + 1 * recency + 0 = 1 (constant)
|
|
122
|
+
const payload: MemoryPayload = { memoryId: 'mem-d1', userId: userId, type: 'discussion', createdAt: twoDaysAgo };
|
|
113
123
|
const cosineScore = 0.5;
|
|
114
|
-
const expectedRecency =
|
|
115
|
-
const expectedScore = scoring.
|
|
124
|
+
const expectedRecency = 1.0; // Infinity halfLife → recency = 1
|
|
125
|
+
const expectedScore = scoring.discussion.alpha * cosineScore + scoring.discussion.beta * expectedRecency + scoring.discussion.gamma;
|
|
116
126
|
const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
|
|
117
127
|
expect(hybridScore).toBeCloseTo(expectedScore, 5);
|
|
118
|
-
expect(
|
|
128
|
+
expect(hybridScore).toBe(1); // Score constant = 1
|
|
119
129
|
});
|
|
120
130
|
|
|
121
|
-
it('should have
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
it('should have constant score for old discussion memory (no decay)', () => {
|
|
132
|
+
//
|
|
133
|
+
// discussion with Infinity halfLife → old memories have same score as new
|
|
134
|
+
const payload: MemoryPayload = { memoryId: 'mem-d2', userId: userId, type: 'discussion', createdAt: veryOldDate };
|
|
135
|
+
const cosineScore = 0.9;
|
|
136
|
+
const expectedRecency = 1.0; // Infinity halfLife → recency = 1 even for old dates
|
|
137
|
+
const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
|
|
138
|
+
expect(hybridScore).toBe(1); // Score constant = 1
|
|
129
139
|
});
|
|
130
140
|
|
|
131
141
|
it('should handle assistant_preference with no decay (Infinity half-life)', () => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
const payload: MemoryPayload = { memoryId: 'mem-a1', userId: userId, type: 'assistant_preference', createdAt: veryOldDate };
|
|
143
|
+
const cosineScore = 0.7;
|
|
144
|
+
const expectedRecency = 1.0;
|
|
145
|
+
const expectedScore = scoring.assistant_preference.alpha * cosineScore + scoring.assistant_preference.beta * expectedRecency + scoring.assistant_preference.gamma;
|
|
146
|
+
const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
|
|
147
|
+
expect(hybridScore).toBeCloseTo(expectedScore, 5);
|
|
138
148
|
});
|
|
139
149
|
|
|
140
150
|
it('should use default scoring if type is missing', () => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
const payload: MemoryPayload = { memoryId: 'mem-def1', userId: userId, createdAt: sevenDaysAgo }; // No type specified
|
|
152
|
+
const cosineScore = 0.8;
|
|
153
|
+
const expectedRecency = (store as any).calculateRecencyScore(sevenDaysAgo, scoring.default.halfLifeDays);
|
|
154
|
+
const expectedScore = scoring.default.alpha * cosineScore + scoring.default.beta * expectedRecency + scoring.default.gamma;
|
|
155
|
+
const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
|
|
156
|
+
expect(hybridScore).toBeCloseTo(expectedScore, 5);
|
|
147
157
|
});
|
|
148
158
|
|
|
149
159
|
it('should return score >= 0 even with negative cosine similarity', () => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
const payload: MemoryPayload = { memoryId: 'mem-neg1', userId: userId, type: 'assistant_preference', createdAt: twoDaysAgo };
|
|
161
|
+
const cosineScore = -0.5;
|
|
162
|
+
const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
|
|
163
|
+
expect(hybridScore).toBeGreaterThanOrEqual(0);
|
|
154
164
|
});
|
|
155
165
|
|
|
156
166
|
});
|