memories-lite 0.9.5 → 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 +13 -8
- package/dist/config/manager.js +2 -8
- package/dist/memory/index.d.ts +11 -0
- package/dist/memory/index.js +62 -123
- package/dist/memory/memory.types.d.ts +1 -2
- package/dist/prompts/index.d.ts +53 -18
- package/dist/prompts/index.js +133 -84
- package/dist/types/index.d.ts +28 -355
- package/dist/types/index.js +2 -20
- package/memories-lite-a42ac5108869b599bcbac21069f63fb47f07452fcc4b87e89b3c06a945612d0b.db +0 -0
- package/memories-lite-a9137698d8d3fdbf27efcdc8cd372084b52d484e8db866c5455bbb3f85299b54.db +0 -0
- package/package.json +1 -1
- package/src/config/defaults.ts +13 -8
- package/src/config/manager.ts +2 -8
- package/src/memory/index.ts +76 -157
- package/src/memory/memory.types.ts +1 -2
- package/src/prompts/index.ts +138 -87
- package/src/types/index.ts +4 -25
- 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 -168
- package/tests/memory.todo.test.ts +0 -127
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,179 +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 && f.type !== 'factual')||[];
|
|
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
|
-
const updateResponse = await this.llm.generateResponse(
|
|
248
|
-
[{ role: "user", content: updatePrompt }],
|
|
249
|
-
{...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,
|
|
250
235
|
);
|
|
251
|
-
// console.log("-- DBG merge:", updatePrompt);
|
|
252
236
|
|
|
253
|
-
|
|
237
|
+
console.log(`-- 🧠 Memory created: "${title}" (${memoryType})`);
|
|
254
238
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
if(action.reason === "undefined") {
|
|
263
|
-
console.log(`-- ⛔ LLM Error: ${action.event}, ${action.type}, "${action.text}"`);
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
console.log(`-- DBG memory "${userId}": ${action.event}, ${action.type}, "${action.text}", why: "${action.reason}"`);
|
|
267
|
-
try {
|
|
268
|
-
switch (action.event) {
|
|
269
|
-
case "ADD": {
|
|
270
|
-
if(!action.type) {
|
|
271
|
-
// log error
|
|
272
|
-
console.error("Type is mandatory to manage memories:", action);
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
metadata.type = action.type;
|
|
276
|
-
const memoryId = await this.createMemory(
|
|
277
|
-
action.text,
|
|
278
|
-
newMessageEmbeddings,
|
|
279
|
-
metadata,
|
|
280
|
-
userId,
|
|
281
|
-
);
|
|
282
|
-
results.push({
|
|
283
|
-
id: memoryId,
|
|
284
|
-
memory: action.text,
|
|
285
|
-
type: action.type,
|
|
286
|
-
metadata: { event: action.event },
|
|
287
|
-
});
|
|
288
|
-
break;
|
|
289
|
-
}
|
|
290
|
-
case "UPDATE": {
|
|
291
|
-
const realMemoryId = tempUuidMapping[action.id];
|
|
292
|
-
const type = metadata.type = uniqueOldMemories[action.id].type || action.type;
|
|
293
|
-
await this.updateMemory(
|
|
294
|
-
realMemoryId,
|
|
295
|
-
action.text,
|
|
296
|
-
newMessageEmbeddings,
|
|
297
|
-
metadata,
|
|
298
|
-
userId,
|
|
299
|
-
);
|
|
300
|
-
results.push({
|
|
301
|
-
id: realMemoryId,
|
|
302
|
-
memory: action.text,
|
|
303
|
-
type,
|
|
304
|
-
metadata: {
|
|
305
|
-
event: action.event,
|
|
306
|
-
previousMemory: action.old_memory,
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
case "DELETE": {
|
|
312
|
-
const realMemoryId = tempUuidMapping[action.id];
|
|
313
|
-
await this.deleteMemory(realMemoryId, userId);
|
|
314
|
-
results.push({
|
|
315
|
-
id: realMemoryId,
|
|
316
|
-
memory: action.text,
|
|
317
|
-
type: action.type,
|
|
318
|
-
metadata: { event: action.event },
|
|
319
|
-
});
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
} catch (error) {
|
|
324
|
-
console.error(`Error processing memory action: ${error}`);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return results;
|
|
239
|
+
return [{
|
|
240
|
+
id: memoryId,
|
|
241
|
+
memory: summary,
|
|
242
|
+
type: memoryType,
|
|
243
|
+
metadata: { title, event: "ADD" },
|
|
244
|
+
}];
|
|
328
245
|
}
|
|
329
246
|
|
|
330
247
|
static fromConfig(configDict: Record<string, any>): MemoriesLite {
|
|
@@ -341,24 +258,24 @@ export class MemoriesLite {
|
|
|
341
258
|
return LiteVectorStore.from(userId, this.vectorStoreConfig);
|
|
342
259
|
}
|
|
343
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
|
+
*/
|
|
344
268
|
async capture(
|
|
345
269
|
messages: string | Message[],
|
|
346
270
|
userId: string,
|
|
347
271
|
config: AddMemoryOptions,
|
|
348
272
|
): Promise<SearchResult> {
|
|
349
|
-
// await this._captureEvent("add", {
|
|
350
|
-
// message_count: Array.isArray(messages) ? messages.length : 1,
|
|
351
|
-
// has_metadata: !!config.metadata,
|
|
352
|
-
// has_filters: !!config.filters,
|
|
353
|
-
// infer: config.infer,
|
|
354
|
-
// });
|
|
355
273
|
const {
|
|
356
274
|
agentId,
|
|
357
275
|
runId,
|
|
358
276
|
metadata = {},
|
|
359
277
|
filters = {},
|
|
360
|
-
|
|
361
|
-
customFacts
|
|
278
|
+
capturePrompt
|
|
362
279
|
} = config;
|
|
363
280
|
|
|
364
281
|
if (agentId) filters.agentId = metadata.agentId = agentId;
|
|
@@ -376,16 +293,18 @@ export class MemoriesLite {
|
|
|
376
293
|
|
|
377
294
|
const final_parsedMessages = await parse_vision_messages(parsedMessages);
|
|
378
295
|
|
|
379
|
-
//
|
|
296
|
+
//
|
|
297
|
+
// Générer synthèse et stocker
|
|
380
298
|
const vectorStoreResult = await this.addToVectorStore(
|
|
381
299
|
final_parsedMessages,
|
|
382
300
|
metadata,
|
|
383
301
|
userId,
|
|
384
302
|
filters,
|
|
385
|
-
|
|
303
|
+
capturePrompt,
|
|
386
304
|
);
|
|
387
305
|
|
|
388
|
-
//
|
|
306
|
+
//
|
|
307
|
+
// Graph store (si configuré)
|
|
389
308
|
let graphResult;
|
|
390
309
|
if (this.graphMemory) {
|
|
391
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,38 +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
|
-
//4. **Todo memory** – explicit user tasks to remember
|
|
16
|
-
//
|
|
61
|
+
/**
|
|
62
|
+
* @deprecated Use DiscussionSynthesisSchema instead
|
|
63
|
+
* Types todo et factual supprimés - seul assistant_preference reste pour compatibilité
|
|
64
|
+
*/
|
|
17
65
|
export const FactRetrievalSchema_extended = z.object({
|
|
18
66
|
facts: z
|
|
19
67
|
.array(
|
|
20
68
|
z.object({
|
|
21
69
|
fact: z.string().describe("The fact extracted from the conversation."),
|
|
22
70
|
existing: z.boolean().describe("Whether the fact is already present"),
|
|
23
|
-
type: z.enum(["assistant_preference"
|
|
24
|
-
.describe(`The type of the fact.
|
|
25
|
-
Use 'assistant_preference' for Assistant behavior preferences (style/language/constraints/commands).
|
|
26
|
-
Use 'episodic' always for time-based events.
|
|
27
|
-
Use 'procedural' for how-to/business questions (e.g., « je veux résilier un bail, comment faire ? »).
|
|
28
|
-
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.
|
|
29
|
-
`),
|
|
71
|
+
type: z.enum(["assistant_preference"])
|
|
72
|
+
.describe(`The type of the fact. Only 'assistant_preference' is supported.`),
|
|
30
73
|
})
|
|
31
74
|
)
|
|
32
75
|
});
|
|
33
76
|
|
|
34
77
|
|
|
35
|
-
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated Memory updates are disabled - use capture() for new memories
|
|
80
|
+
*/
|
|
36
81
|
export const MemoryUpdateSchema = z.object({
|
|
37
82
|
memory: z
|
|
38
83
|
.array(
|
|
@@ -56,21 +101,14 @@ export const MemoryUpdateSchema = z.object({
|
|
|
56
101
|
"The reason why you selected this event.",
|
|
57
102
|
),
|
|
58
103
|
type: z
|
|
59
|
-
.enum(["
|
|
60
|
-
.describe("Type of the memory.
|
|
104
|
+
.enum(["assistant_preference"])
|
|
105
|
+
.describe("Type of the memory. Only 'assistant_preference' is supported."),
|
|
61
106
|
}),
|
|
62
107
|
)
|
|
63
108
|
.describe(
|
|
64
109
|
"An array representing the state of memory items after processing new facts.",
|
|
65
110
|
),
|
|
66
111
|
});
|
|
67
|
-
/**
|
|
68
|
-
* Practical Application:
|
|
69
|
-
*
|
|
70
|
-
* If the task is "factual" (e.g., "Where do I live?") → retrieve factual memory.
|
|
71
|
-
* If the task is temporal or event-based ("What was I doing yesterday?") → retrieve episodic memory.
|
|
72
|
-
* If the task is a user task/reminder (e.g., "Add a reminder to call the bank tomorrow") → retrieve todo memory.
|
|
73
|
-
*/
|
|
74
112
|
export const MEMORY_STRING_SYSTEM = `# DIRECTIVES FOR MEMORIES
|
|
75
113
|
- Information stored in memory is always enclosed within the <memories> tag.
|
|
76
114
|
- Prioritize the latest user message over memories (the user's current question is authoritative).
|
|
@@ -82,15 +120,10 @@ export const MEMORY_STRING_SYSTEM = `# DIRECTIVES FOR MEMORIES
|
|
|
82
120
|
|
|
83
121
|
export const MEMORY_STRING_PREFIX = "Use these contextual memories to guide your response. Prioritize the user's question. Ignore irrelevant memories."
|
|
84
122
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`;
|
|
90
|
-
|
|
91
|
-
// Deprecated: getFactRetrievalMessages_O removed in favor of getFactRetrievalMessages
|
|
92
|
-
|
|
93
|
-
|
|
123
|
+
/**
|
|
124
|
+
* @deprecated Use getDiscussionSynthesisMessages instead
|
|
125
|
+
* Cette fonction est conservée pour compatibilité avec l'ancien système
|
|
126
|
+
*/
|
|
94
127
|
export function getFactRetrievalMessages(
|
|
95
128
|
parsedMessages: string,
|
|
96
129
|
customRules: string = "",
|
|
@@ -98,36 +131,31 @@ export function getFactRetrievalMessages(
|
|
|
98
131
|
): [string, string] {
|
|
99
132
|
|
|
100
133
|
const injectCustomRules = (customRules:string) => customRules ?`\n# PRE-EXISTING FACTS\n${customRules}` : "";
|
|
101
|
-
const systemPrompt = `You are a Personal Information Organizer, specialized in
|
|
134
|
+
const systemPrompt = `You are a Personal Information Organizer, specialized in extracting and structuring user facts and preferences for AI personalization. You also handle explicit task extraction (todos only).
|
|
102
135
|
|
|
103
136
|
Filter content before extracting triplets:
|
|
104
|
-
- Relevance: keep only statements directly about the user (preferences, identity, actions
|
|
137
|
+
- Relevance: keep only statements directly about the user (preferences with the AI, identity relevant to personalization, actions/experiences that affect responses) or explicit todos; drop weather/small talk.
|
|
105
138
|
- Disinterest: if the user rejects the topic (e.g., "cette information ne m'intéresse pas", "not interested"), return {"facts":[]}.
|
|
106
|
-
-
|
|
107
|
-
- Action requests to the assistant (find/search/locate/call/email/book/reserve) are NOT preferences. Unless the user explicitly asks to save as a todo,
|
|
108
|
-
|
|
109
|
-
You must strictly extract {Subject, Predicate, Object} triplets
|
|
139
|
+
- Ignore business/process/regulation/company-policy content entirely (no extraction, no memory).
|
|
140
|
+
- Action requests to the assistant (find/search/locate/call/email/book/reserve) are NOT preferences. Unless the user explicitly asks to save as a todo, return {"facts":[]}.
|
|
141
|
+
|
|
142
|
+
You must strictly extract {Subject, Predicate, Object} triplets (max 12):
|
|
110
143
|
1. Identify named entities, preferences, and meaningful user-related concepts:
|
|
111
|
-
- Extract triplets
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
- "assistant_preference":
|
|
115
|
-
- "
|
|
116
|
-
- "todo": ONLY if the user explicitly
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
-
|
|
126
|
-
- DO NOT infer personal facts from third-party informations.
|
|
127
|
-
- Treat "**ASSISTANT**:" as responses to enrich context of your reasoning process about the USER query.
|
|
128
|
-
2. Use pronoun "I" instead of "The user" in the subject of the triplet.
|
|
129
|
-
3. Do not output any facts already present in section # PRE-EXISTING FACTS.
|
|
130
|
-
- If you find facts already present in section # PRE-EXISTING FACTS, use field "existing" to store them.
|
|
144
|
+
- Extract triplets *about the user* that help AI personalization (preferences, stable facts, explicit todos).
|
|
145
|
+
- Use explicit, precise, unambiguous predicates (e.g., "prefers", "speaks", "is a", "uses").
|
|
146
|
+
- Triplet type ∈ {"assistant_preference","factual","todo"} only:
|
|
147
|
+
- "assistant_preference": response style/language/format or interaction constraints.
|
|
148
|
+
- "factual": stable user data relevant to personalization (e.g., language, timezone, tools used).
|
|
149
|
+
- "todo": ONLY if the user explicitly asks to save/keep as todo. Never infer from intent alone.
|
|
150
|
+
- Remove introductions, sub-facts, repetitions, fillers, vague statements; prefer the general fact over details.
|
|
151
|
+
- Each triplet (S,P,O) ≤ 10 words total.
|
|
152
|
+
- Do not include type labels inside fact text; use the 'type' field only.
|
|
153
|
+
- Do not infer personal facts from third-party information.
|
|
154
|
+
- Treat "**ASSISTANT**:" as context only; never as a fact source.
|
|
155
|
+
|
|
156
|
+
2. Use pronoun "I" as the Subject (not "The user").
|
|
157
|
+
3. Do not output facts already in # PRE-EXISTING FACTS.
|
|
158
|
+
- If found, put them in "existing" (list of matched facts or IDs).
|
|
131
159
|
|
|
132
160
|
${injectCustomRules(customRules)}
|
|
133
161
|
|
|
@@ -144,6 +172,9 @@ Remember the following:
|
|
|
144
172
|
return [systemPrompt, userPrompt];
|
|
145
173
|
}
|
|
146
174
|
|
|
175
|
+
/**
|
|
176
|
+
* @deprecated Memory updates are disabled by config
|
|
177
|
+
*/
|
|
147
178
|
export function getUpdateMemoryMessages(
|
|
148
179
|
retrievedOldMemory: Array<{ id: string; text: string }>,
|
|
149
180
|
newRetrievedFacts: any[],
|
|
@@ -153,46 +184,66 @@ export function getUpdateMemoryMessages(
|
|
|
153
184
|
const serializeFacts = (facts: any[]) => {
|
|
154
185
|
if(facts.length === 0) return "";
|
|
155
186
|
if(facts[0].fact) {
|
|
156
|
-
return facts.map((elem) =>
|
|
187
|
+
return facts.map((elem) => `- "${elem.fact}" (type:${elem.type})`).join("\n");
|
|
157
188
|
} else {
|
|
158
189
|
return facts.join("\n");
|
|
159
190
|
}
|
|
160
191
|
}
|
|
161
192
|
const serializeMemory = (memory: any[]) => {
|
|
162
|
-
return memory.map((elem) =>
|
|
193
|
+
return memory.map((elem) => `- "${elem.text}" (id:${elem.id})`).join("\n");
|
|
163
194
|
}
|
|
164
195
|
return `ROLE:
|
|
165
|
-
You are
|
|
196
|
+
You are the Memory Manager module of an AI assistant. You are specialized in semantic reasoning, fact consistency, and memory lifecycle operations.
|
|
197
|
+
Your job is to maintain a coherent, contradiction-free knowledge base (long-term memory) of user facts.
|
|
166
198
|
|
|
167
199
|
MISSION:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
-
|
|
183
|
-
|
|
200
|
+
Given:
|
|
201
|
+
1. A set of **Current Memory** facts (each with unique ID and textual content).
|
|
202
|
+
2. A set of **New Retrieved Facts** from the user or external sources.
|
|
203
|
+
3. (Optional) A **User Instruction** indicating explicit intent (add, modify, delete).
|
|
204
|
+
|
|
205
|
+
You must process each new fact individually and decide **exactly one** action: **ADD**, **DELETE**, **UPDATE**, or **NONE**, following these rules, in this order:
|
|
206
|
+
|
|
207
|
+
1. **User intent override**
|
|
208
|
+
If the User Instruction clearly requests adding, updating, or removal (e.g. “ajoute X”, “mets à jour Y”, “supprime Z”), you **must** respect that and assign the corresponding action for the matching fact, superseding semantic rules.
|
|
209
|
+
|
|
210
|
+
2. **Semantic consistency check**
|
|
211
|
+
For each new fact:
|
|
212
|
+
- If it **contradicts**, **negates**, or **cancels** an existing memory item, you **DELETE** the memory item.
|
|
213
|
+
- Else, if the new fact is a **specialization** (i.e. same core meaning + additional detail) of an existing one, **UPDATE** that memory (keeping the same ID).
|
|
214
|
+
- Else, if it is **semantically equivalent** (i.e. redundant or paraphrased), assign **NONE** (no change).
|
|
215
|
+
- Else, if it is entirely **new** (no overlap or relation), **ADD** it (generate a new ID).
|
|
216
|
+
- Otherwise (if ambiguous or borderline), assign **NONE** (do not delete).
|
|
217
|
+
|
|
218
|
+
3. **ID reuse and consistency**
|
|
219
|
+
- For **UPDATE**, reuse the existing memory item’s ID.
|
|
220
|
+
- For **DELETE**, simply remove the item from the final memory output.
|
|
221
|
+
- For **ADD**, generate a new unique ID (e.g. UUID).
|
|
222
|
+
- If memory is initially empty, treat all new facts as **ADD**.
|
|
223
|
+
|
|
224
|
+
4. **Output formatting**
|
|
225
|
+
Return the updated memory state in strict JSON format. Each memory entry must include:
|
|
226
|
+
- \`id\` (string)
|
|
227
|
+
- \`text\` (string, the pure factual content)
|
|
228
|
+
- Optionally for updates: \`old_text\` (the prior version)
|
|
229
|
+
- *(No extra annotation or type markup in \`text\`)*
|
|
230
|
+
|
|
231
|
+
If there are no facts at all, return \`{"memory": []}\`.
|
|
232
|
+
|
|
233
|
+
*You must not output any other text besides the valid JSON result.*
|
|
184
234
|
|
|
185
235
|
# Output Instructions
|
|
186
|
-
- Default user language is ${defaultLanguage}.
|
|
236
|
+
- Default user language is "${defaultLanguage}".
|
|
187
237
|
- Each memory item must follow this strict format:
|
|
188
|
-
- UPDATE also include the previous text: \`
|
|
189
|
-
-
|
|
190
|
-
-
|
|
238
|
+
- UPDATE must also include the previous text: \`old_text\`.
|
|
239
|
+
- Reuse correct IDs for UPDATE.
|
|
240
|
+
- For DELETE, exclude the removed item from the final memory list.
|
|
241
|
+
- Generate random IDs for ADDs (format: UUID).
|
|
191
242
|
- If memory is empty, treat all facts as ADD.
|
|
192
243
|
- Without facts, return an empty memory: \`{"memory": []}\`
|
|
193
244
|
- Memory must strictly reflect valid facts.
|
|
194
|
-
- Contradictions, cancellations,
|
|
195
|
-
|
|
245
|
+
- Contradictions, cancellations, or negations must be handled by DELETE. Ambiguities must be handled by NONE.
|
|
246
|
+
- The field 'text' must be the pure memory content only: do not add any type markers or parentheses.
|
|
196
247
|
|
|
197
248
|
# Current Memory (extract and reuse their IDs for UPDATE or DELETE events):
|
|
198
249
|
${serializeMemory(retrievedOldMemory)}
|
|
@@ -200,8 +251,7 @@ ${serializeMemory(retrievedOldMemory)}
|
|
|
200
251
|
# New Retrieved Facts:
|
|
201
252
|
${serializeFacts(newRetrievedFacts)}
|
|
202
253
|
|
|
203
|
-
# User Instruction:
|
|
204
|
-
${userInstruction || ""}
|
|
254
|
+
# User Instruction: "${userInstruction || ''}"
|
|
205
255
|
|
|
206
256
|
Return the updated memory in JSON format only. Do not output anything else.`;
|
|
207
257
|
}
|
|
@@ -229,8 +279,9 @@ export const getMemoriesAsPrefix = (memories: MemoryItem[]) => {
|
|
|
229
279
|
};
|
|
230
280
|
|
|
231
281
|
export const getMemoriesAsSystem = (memories: MemoryItem[], facts?: string[]) => {
|
|
282
|
+
if(!memories || memories.length === 0) return "";
|
|
232
283
|
const memoryString = memories.map((mem) => `- ${mem.memory}`).concat(facts||[]).join("\n");
|
|
233
|
-
return `${MEMORY_STRING_SYSTEM}\n<memories
|
|
284
|
+
return `${MEMORY_STRING_SYSTEM}\n<memories>\n${memoryString}\n</memories>`;
|
|
234
285
|
}
|
|
235
286
|
|
|
236
287
|
export function parseMessages(messages: string[]): string {
|