memories-lite 0.10.1 → 0.99.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.
@@ -0,0 +1,377 @@
1
+ /// <reference types="jest" />
2
+ import { MemoriesLite } from "../src";
3
+ import { SearchResult } from "../src/types";
4
+ import dotenv from "dotenv";
5
+ import { createTestMemory } from "./init.mem";
6
+
7
+ dotenv.config();
8
+
9
+ jest.setTimeout(120000); // 120s pour les tests avec LLM et multiples recherches
10
+
11
+ /**
12
+ * Tests de recherche sémantique pour les mémoires de discussion
13
+ * Objectif: Mesurer le clivage des scores entre recherches pertinentes et non pertinentes
14
+ */
15
+ describe("Memory Discussion - Recherche sémantique et scoring", () => {
16
+ let memory: MemoriesLite;
17
+ let userId: string;
18
+
19
+ // Test avec searchThreshold=0.50
20
+ let memoryWithThreshold: MemoriesLite;
21
+ let userIdWithThreshold: string;
22
+
23
+ //
24
+ // Discussion FAO (Feuille d'Avis Officielle) - Genève immeubles
25
+ const discussionFAO = [
26
+ {
27
+ role: "user",
28
+ content: "Tu dois vérifier les autorisations (ou APA) de la FAO de Genève, et ensuite tu dois extraire toutes les adresses et chercher nos immeubles. Merci de me mettre les liens des FAO que tu jugeras utiles."
29
+ },
30
+ {
31
+ role: "assistant",
32
+ content: `Voici un premier lot traité. Dites-moi si vous voulez que je continue sur plus d'avis.
33
+
34
+ Adresses extraites et recherche de nos immeubles
35
+ - Chemin Émile-Bressler 19, 1226 Thônex
36
+ - Immeubles PRSA à proximité (200 m) : aucun trouvé.
37
+ - FAO: Requête M/341685/1 — Démolition d'une villa
38
+ - Chemin du Port 9, 1246 Corsier
39
+ - FAO: Requête M/341714/1 — Démolition
40
+ - Boulevard de Saint‑Georges 46, 1205 Genève (Plainpalais)
41
+ - FAO: Autorisation DD 330818 — Transformation`
42
+ },
43
+ {
44
+ role: "user",
45
+ content: "oui merci pour le premier"
46
+ },
47
+ {
48
+ role: "assistant",
49
+ content: `Parfait. Je poursuis avec ces trois adresses et vous reviens avec:
50
+ - adresses exactes confirmées
51
+ - présence/absence de nos immeubles à 200 m
52
+ - liens FAO utiles
53
+
54
+ Cibles:
55
+ - Chemin du Port 9, 1246 Corsier
56
+ - Boulevard de Saint‑Georges 46, 1205 Genève
57
+ - Chemin du Champ‑des‑Bois 8, 1255 Veyrier`
58
+ },
59
+ {
60
+ role: "user",
61
+ content: "oui"
62
+ },
63
+ {
64
+ role: "assistant",
65
+ content: `Voici la suite.
66
+
67
+ Résultats par adresse (rayon 200 m)
68
+ - Chemin du Port 9, 1246 Corsier — aucun immeuble PRSA trouvé.
69
+ - Boulevard de Saint‑Georges 46, 1205 Genève — aucun immeuble PRSA trouvé.
70
+ - Chemin du Champ‑des‑Bois 8, 1255 Veyrier — aucun immeuble PRSA trouvé.
71
+
72
+ Souhaitez-vous que je:
73
+ - élargisse à 300–500 m,
74
+ - continue l'extraction sur d'autres avis FAO récents (démolitions/transformations/APA),
75
+ - ou cible des communes/quartiers spécifiques ?`
76
+ }
77
+ ];
78
+
79
+ //
80
+ // Discussion résiliation de bail
81
+ const discussionBail = [
82
+ { role: "user", content: "Comment résilier un bail commercial ?" },
83
+ { role: "assistant", content: `Voici la procédure de résiliation d'un bail commercial:
84
+
85
+ 1. **Délai de préavis**: Généralement 6 mois avant l'échéance
86
+ 2. **Forme**: Lettre recommandée obligatoire
87
+ 3. **Motifs légitimes**: Non-paiement, usage non conforme, besoin personnel
88
+ 4. **Formulaire officiel**: Utiliser le formulaire cantonal
89
+
90
+ Points importants:
91
+ - Respecter les délais stricts
92
+ - Conserver une copie de l'envoi
93
+ - Vérifier les clauses spéciales du bail` }
94
+ ];
95
+
96
+ //
97
+ // Discussion comptabilité
98
+ const discussionCompta = [
99
+ { role: "user", content: "Comment faire le bouclement annuel ?" },
100
+ { role: "assistant", content: `Procédure de bouclement annuel:
101
+
102
+ 1. **Inventaire**: Vérifier les stocks et actifs
103
+ 2. **Rapprochements**: Comptes bancaires, créanciers, débiteurs
104
+ 3. **Provisions**: Estimer les risques et charges
105
+ 4. **Amortissements**: Calculer selon le plan
106
+ 5. **Ajustements**: Régularisations de fin d'année
107
+ 6. **États financiers**: Bilan, compte de résultat, annexe
108
+
109
+ Échéances: 3 mois après clôture pour les sociétés suisses.` }
110
+ ];
111
+
112
+ beforeAll(async () => {
113
+ //
114
+ // Setup unique - capturer toutes les discussions une fois (sans seuil)
115
+ ({ memory, userId } = createTestMemory({}));
116
+ await memory.reset(userId);
117
+
118
+ // Capturer les 3 discussions
119
+ await memory.capture(discussionFAO, userId, {
120
+ metadata: { type: 'discussion', discussionId: 'fao-1' }
121
+ });
122
+
123
+ await memory.capture(discussionBail, userId, {
124
+ metadata: { type: 'discussion', discussionId: 'bail-1' }
125
+ });
126
+
127
+ await memory.capture(discussionCompta, userId, {
128
+ metadata: { type: 'discussion', discussionId: 'compta-1' }
129
+ });
130
+
131
+ //
132
+ // Setup avec searchThreshold=0.50
133
+ ({ memory: memoryWithThreshold, userId: userIdWithThreshold } = createTestMemory({ searchThreshold: 0.50 }));
134
+ await memoryWithThreshold.reset(userIdWithThreshold);
135
+
136
+ // Capturer les mêmes discussions
137
+ await memoryWithThreshold.capture(discussionFAO, userIdWithThreshold, {
138
+ metadata: { type: 'discussion', discussionId: 'fao-1' }
139
+ });
140
+
141
+ await memoryWithThreshold.capture(discussionBail, userIdWithThreshold, {
142
+ metadata: { type: 'discussion', discussionId: 'bail-1' }
143
+ });
144
+
145
+ await memoryWithThreshold.capture(discussionCompta, userIdWithThreshold, {
146
+ metadata: { type: 'discussion', discussionId: 'compta-1' }
147
+ });
148
+
149
+ console.log('\n📚 3 discussions capturées (2x: sans seuil + avec seuil 0.50)\n');
150
+ });
151
+
152
+ afterAll(async () => {
153
+ await memory.reset(userId);
154
+ await memoryWithThreshold.reset(userIdWithThreshold);
155
+ });
156
+
157
+ describe("Clivage des scores - Recherches pertinentes vs non pertinentes", () => {
158
+
159
+ it("should return HIGH score for FAO-related queries (> 0.3)", async () => {
160
+ const queries = [
161
+ "recherche FAO autorisation immeuble Genève",
162
+ "APA démolition villa Thônex",
163
+ "vérifier permis construction Corsier",
164
+ "immeubles PRSA rayon 200m"
165
+ ];
166
+
167
+ console.log('\n🎯 Recherches PERTINENTES pour FAO:\n');
168
+
169
+ const scores: number[] = [];
170
+ for (const query of queries) {
171
+ const result = await memory.retrieve(query, userId, {
172
+ limit: 3,
173
+ filters: { type: 'discussion' }
174
+ }) as SearchResult;
175
+
176
+ const topResult = result.results[0];
177
+ const score = topResult?.score || 0;
178
+ scores.push(score);
179
+
180
+ console.log(` "${query}"`);
181
+ console.log(` → ${topResult?.metadata?.title}`);
182
+ console.log(` → Score: ${score.toFixed(4)}\n`);
183
+
184
+ expect(topResult).toBeDefined();
185
+ }
186
+
187
+ // Les scores pour des recherches pertinentes devraient être > 0.3
188
+ const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
189
+ console.log(` 📊 Score moyen FAO: ${avgScore.toFixed(4)}\n`);
190
+ expect(avgScore).toBeGreaterThan(0.3);
191
+ });
192
+
193
+ it("should return LOW score for unrelated queries", async () => {
194
+ const queries = [
195
+ "recette de cuisine pasta carbonara",
196
+ "météo paris demain",
197
+ "comment jouer au tennis",
198
+ "voyage vacances Thaïlande"
199
+ ];
200
+
201
+ console.log('\n❌ Recherches NON PERTINENTES:\n');
202
+
203
+ const scores: number[] = [];
204
+
205
+ for (const query of queries) {
206
+ const result = await memory.retrieve(query, userId, {
207
+ limit: 3,
208
+ filters: { type: 'discussion' }
209
+ }) as SearchResult;
210
+
211
+ const topResult = result.results[0];
212
+ const score = topResult?.score || 0;
213
+ scores.push(score);
214
+
215
+ console.log(` "${query}"`);
216
+ console.log(` → ${topResult?.metadata?.title || 'Aucun résultat'}`);
217
+ console.log(` → Score: ${score.toFixed(4)}\n`);
218
+ }
219
+
220
+ // Les scores pour des sujets non pertinents devraient être constants (1.0 pour discussion)
221
+ // car discussion utilise alpha=0, beta=1 (pas de cosine similarity)
222
+ // Le clivage se fait via la présence/absence de résultats ou via des filtres
223
+ expect(scores.length).toBe(4);
224
+ });
225
+
226
+ it("should match bail queries to bail discussion", async () => {
227
+ const queries = [
228
+ "résiliation bail commercial",
229
+ "préavis locataire formulaire",
230
+ "lettre recommandée bail"
231
+ ];
232
+
233
+ console.log('\n🏠 Recherches BAIL:\n');
234
+
235
+ for (const query of queries) {
236
+ const result = await memory.retrieve(query, userId, {
237
+ limit: 3,
238
+ filters: { type: 'discussion' }
239
+ }) as SearchResult;
240
+
241
+ const topResult = result.results[0];
242
+ console.log(` "${query}"`);
243
+ console.log(` → ${topResult?.metadata?.title}`);
244
+ console.log(` → Score: ${topResult?.score?.toFixed(4)}\n`);
245
+
246
+ expect(topResult).toBeDefined();
247
+ }
248
+ });
249
+
250
+ it("should match comptabilité queries to compta discussion", async () => {
251
+ const queries = [
252
+ "bouclement annuel comptabilité",
253
+ "amortissements provisions",
254
+ "états financiers bilan"
255
+ ];
256
+
257
+ console.log('\n📊 Recherches COMPTABILITÉ:\n');
258
+
259
+ for (const query of queries) {
260
+ const result = await memory.retrieve(query, userId, {
261
+ limit: 3,
262
+ filters: { type: 'discussion' }
263
+ }) as SearchResult;
264
+
265
+ const topResult = result.results[0];
266
+ console.log(` "${query}"`);
267
+ console.log(` → ${topResult?.metadata?.title}`);
268
+ console.log(` → Score: ${topResult?.score?.toFixed(4)}\n`);
269
+
270
+ expect(topResult).toBeDefined();
271
+ }
272
+ });
273
+ });
274
+
275
+ describe("Analyse comparative des scores", () => {
276
+
277
+ it("should show score distribution across all queries", async () => {
278
+ const testCases = [
279
+ // Très pertinent FAO
280
+ { query: "FAO Genève permis démolition", expected: 'FAO' },
281
+ { query: "autorisation APA immeuble PRSA", expected: 'FAO' },
282
+ // Pertinent Bail
283
+ { query: "résilier contrat location", expected: 'bail' },
284
+ { query: "préavis bail commercial", expected: 'bail' },
285
+ // Pertinent Compta
286
+ { query: "clôture exercice comptable", expected: 'compta' },
287
+ { query: "bilan financier annuel", expected: 'compta' },
288
+ // Non pertinent
289
+ { query: "pizza margherita recette", expected: 'none' },
290
+ { query: "football match score", expected: 'none' },
291
+ ];
292
+
293
+ console.log('\n📈 ANALYSE COMPARATIVE DES SCORES:\n');
294
+ console.log('Query | Top Match | Score');
295
+ console.log('------|-----------|------');
296
+
297
+ const results: { query: string, title: string, score: number, expected: string }[] = [];
298
+
299
+ for (const tc of testCases) {
300
+ const result = await memory.retrieve(tc.query, userId, {
301
+ limit: 1,
302
+ filters: { type: 'discussion' }
303
+ }) as SearchResult;
304
+
305
+ const top = result.results[0];
306
+ results.push({
307
+ query: tc.query,
308
+ title: top?.metadata?.title || 'N/A',
309
+ score: top?.score || 0,
310
+ expected: tc.expected
311
+ });
312
+
313
+ console.log(`"${tc.query.substring(0, 30).padEnd(30)}" | ${(top?.metadata?.title || 'N/A').substring(0, 40).padEnd(40)} | ${top?.score?.toFixed(4) || 'N/A'}`);
314
+ }
315
+
316
+ console.log('\n');
317
+
318
+ // Note: Avec discussion (alpha=1), le score = cosine similarity
319
+ // Le clivage se fait sur le score absolu
320
+ expect(results.length).toBe(8);
321
+ });
322
+ });
323
+
324
+ describe("searchThreshold - Filtrage des résultats", () => {
325
+
326
+ it("should return NO results for unrelated queries with searchThreshold=0.50", async () => {
327
+ const unrelatedQueries = [
328
+ "pizza margherita recette",
329
+ "football match score",
330
+ "météo paris demain",
331
+ "voyage vacances Thaïlande"
332
+ ];
333
+
334
+ console.log('\n🔒 TEST searchThreshold=0.50 - Recherches NON pertinentes:\n');
335
+
336
+ for (const query of unrelatedQueries) {
337
+ const result = await memoryWithThreshold.retrieve(query, userIdWithThreshold, {
338
+ limit: 3,
339
+ filters: { type: 'discussion' }
340
+ }) as SearchResult;
341
+
342
+ console.log(` "${query}"`);
343
+ console.log(` → Résultats: ${result.results.length}\n`);
344
+
345
+ // ✅ Aucun résultat car score < 0.50
346
+ expect(result.results.length).toBe(0);
347
+ }
348
+ });
349
+
350
+ it("should return results for relevant queries with searchThreshold=0.50", async () => {
351
+ const relevantQueries = [
352
+ "autorisation APA immeuble PRSA",
353
+ "préavis bail commercial",
354
+ "bilan financier annuel"
355
+ ];
356
+
357
+ console.log('\n✅ TEST searchThreshold=0.50 - Recherches PERTINENTES:\n');
358
+
359
+ for (const query of relevantQueries) {
360
+ const result = await memoryWithThreshold.retrieve(query, userIdWithThreshold, {
361
+ limit: 3,
362
+ filters: { type: 'discussion' }
363
+ }) as SearchResult;
364
+
365
+ const topResult = result.results[0];
366
+ console.log(` "${query}"`);
367
+ console.log(` → Résultats: ${result.results.length}`);
368
+ console.log(` → Top: ${topResult?.metadata?.title} (score: ${topResult?.score?.toFixed(4)})\n`);
369
+
370
+ // ✅ Au moins un résultat car score >= 0.50
371
+ expect(result.results.length).toBeGreaterThan(0);
372
+ expect(topResult?.score).toBeGreaterThanOrEqual(0.50);
373
+ }
374
+ });
375
+ });
376
+ });
377
+
@@ -0,0 +1,279 @@
1
+ /// <reference types="jest" />
2
+ import { MemoriesLite } from "../src";
3
+ import { SearchResult } from "../src/types";
4
+ import dotenv from "dotenv";
5
+ import { createTestMemory } from "./init.mem";
6
+
7
+ dotenv.config();
8
+
9
+ jest.setTimeout(60000); // 60s pour les tests avec LLM
10
+
11
+ /**
12
+ * Tests pour le nouveau type de mémoire 'discussion'
13
+ * Permet de mémoriser une discussion avec synthèse (title + summary)
14
+ */
15
+ describe("Memory Discussion - Synthèse de discussions", () => {
16
+ let memory: MemoriesLite;
17
+ let userId: string;
18
+
19
+ //
20
+ // Exemple de discussion à synthétiser
21
+ const discussionMessages = [
22
+ {
23
+ role: "user",
24
+ content: "Tu dois vérifier les autorisations (ou APA) de la FAO de Genève, et ensuite tu dois extraire toutes les adresses et chercher nos immeubles. Merci de me mettre les liens des FAO que tu jugeras utiles."
25
+ },
26
+ {
27
+ role: "assistant",
28
+ content: `Voici un premier lot traité. Dites-moi si vous voulez que je continue sur plus d'avis.
29
+
30
+ Adresses extraites et recherche de nos immeubles
31
+ - Chemin Émile-Bressler 19, 1226 Thônex
32
+ - Immeubles PRSA à proximité (200 m) : aucun trouvé.
33
+ - FAO: Requête M/341685/1 — Démolition d'une villa
34
+ - Chemin du Port 9, 1246 Corsier
35
+ - À vérifier (souhaitez-vous que je cherche nos immeubles autour de cette adresse ?)
36
+ - FAO: Requête M/341714/1 — Démolition
37
+ - Boulevard de Saint‑Georges 46, 1205 Genève (Plainpalais)
38
+ - À vérifier (je peux lancer la recherche de nos immeubles).
39
+ - FAO: Autorisation DD 330818 — Transformation`
40
+ },
41
+ {
42
+ role: "user",
43
+ content: "oui merci pour le premier"
44
+ },
45
+ {
46
+ role: "assistant",
47
+ content: `Parfait. Je poursuis avec ces trois adresses et vous reviens avec:
48
+ - adresses exactes confirmées
49
+ - présence/absence de nos immeubles à 200 m
50
+ - liens FAO utiles
51
+
52
+ Cibles:
53
+ - Chemin du Port 9, 1246 Corsier
54
+ - Boulevard de Saint‑Georges 46, 1205 Genève
55
+ - Chemin du Champ‑des‑Bois 8, 1255 Veyrier`
56
+ },
57
+ {
58
+ role: "user",
59
+ content: "oui"
60
+ },
61
+ {
62
+ role: "assistant",
63
+ content: `Voici la suite.
64
+
65
+ Résultats par adresse (rayon 200 m)
66
+ - Chemin du Port 9, 1246 Corsier — aucun immeuble PRSA trouvé.
67
+ - Boulevard de Saint‑Georges 46, 1205 Genève — aucun immeuble PRSA trouvé.
68
+ - Chemin du Champ‑des‑Bois 8, 1255 Veyrier — aucun immeuble PRSA trouvé.
69
+
70
+ Souhaitez-vous que je:
71
+ - élargisse à 300–500 m,
72
+ - continue l'extraction sur d'autres avis FAO récents (démolitions/transformations/APA),
73
+ - ou cible des communes/quartiers spécifiques ?`
74
+ }
75
+ ];
76
+
77
+ beforeEach(async () => {
78
+ ({ memory, userId } = createTestMemory({}));
79
+ await memory.reset(userId);
80
+ });
81
+
82
+ afterEach(async () => {
83
+ await memory.reset(userId);
84
+ });
85
+
86
+ describe("Capture de discussion", () => {
87
+
88
+ it("should capture a discussion and generate exactly ONE memory with title + summary", async () => {
89
+ //
90
+ // Capturer la discussion avec type 'discussion'
91
+ const result = await memory.capture(discussionMessages, userId, {
92
+ metadata: {
93
+ type: 'discussion',
94
+ discussionId: '7c3e43ac-7621-470d-a22e-82713c3a113e'
95
+ }
96
+ }) as SearchResult;
97
+
98
+ expect(result).toBeDefined();
99
+ expect(result.results).toBeDefined();
100
+ expect(Array.isArray(result.results)).toBe(true);
101
+
102
+ //
103
+ // ✅ Vérifier qu'UNE SEULE mémoire est créée par capture
104
+ expect(result.results.length).toBe(1);
105
+
106
+ //
107
+ // Vérifier que la mémoire contient title et type
108
+ const mem = result.results[0];
109
+ expect(mem.id).toBeDefined();
110
+ expect(mem.type).toBe('discussion');
111
+ expect(mem.memory).toBeDefined(); // summary
112
+ expect(mem.metadata?.title).toBeDefined();
113
+
114
+ console.log('--- Title:', mem.metadata?.title);
115
+ console.log('--- Summary:', mem.memory);
116
+
117
+ //
118
+ // ✅ Vérifier qu'il n'y a bien qu'une seule mémoire stockée
119
+ const allMemories = await memory.getAll(userId, { type: 'discussion' });
120
+ expect(allMemories.results.length).toBe(1);
121
+ });
122
+
123
+ it("should use custom capturePrompt when provided", async () => {
124
+ const customPrompt = `Tu es un expert en synthèse. Génère:
125
+ 1. TITRE: Un titre technique en français (max 8 mots)
126
+ 2. SUMMARY: Résumé technique des actions effectuées (max 50 mots)
127
+
128
+ Discussion:
129
+ `;
130
+
131
+ const result = await memory.capture(discussionMessages, userId, {
132
+ capturePrompt: customPrompt,
133
+ metadata: {
134
+ type: 'discussion',
135
+ discussionId: 'custom-test-id'
136
+ }
137
+ }) as SearchResult;
138
+
139
+ expect(result).toBeDefined();
140
+ expect(result.results.length).toBeGreaterThan(0);
141
+ expect(result.results[0].type).toBe('discussion');
142
+ });
143
+
144
+ it("should retrieve discussion memories by semantic search with title in metadata", async () => {
145
+ //
146
+ // D'abord capturer la discussion
147
+ const captureResult = await memory.capture(discussionMessages, userId, {
148
+ metadata: {
149
+ type: 'discussion',
150
+ discussionId: 'search-test-id'
151
+ }
152
+ }) as SearchResult;
153
+
154
+ const originalTitle = captureResult.results[0].metadata?.title;
155
+ expect(originalTitle).toBeDefined();
156
+
157
+ //
158
+ // Recherche sémantique avec retrieve()
159
+ const searchResult = await memory.retrieve(
160
+ "recherche FAO autorisation immeuble Genève",
161
+ userId,
162
+ { filters: { type: 'discussion' } }
163
+ ) as SearchResult;
164
+
165
+ expect(searchResult).toBeDefined();
166
+ expect(searchResult.results).toBeDefined();
167
+ expect(searchResult.results.length).toBeGreaterThan(0);
168
+
169
+ //
170
+ // ✅ Vérifier que le title est dans metadata
171
+ expect(searchResult.results[0].metadata?.title).toBeDefined();
172
+ expect(searchResult.results[0].metadata?.title).toBe(originalTitle);
173
+ expect(searchResult.results[0].score).toBeDefined();
174
+ expect(searchResult.results[0].type).toBe('discussion');
175
+
176
+ console.log('--- retrieve() title:', searchResult.results[0].metadata?.title);
177
+ });
178
+
179
+ it("should list all discussion memories with title in metadata (getAll)", async () => {
180
+ //
181
+ // Capturer plusieurs discussions
182
+ const capture1 = await memory.capture(discussionMessages, userId, {
183
+ metadata: { type: 'discussion', discussionId: 'disc-1' }
184
+ }) as SearchResult;
185
+
186
+ const capture2 = await memory.capture([
187
+ { role: "user", content: "Comment résilier un bail ?" },
188
+ { role: "assistant", content: "Voici la procédure de résiliation..." }
189
+ ], userId, {
190
+ metadata: { type: 'discussion', discussionId: 'disc-2' }
191
+ }) as SearchResult;
192
+
193
+ //
194
+ // Lister toutes les mémoires discussion avec getAll()
195
+ const allResult = await memory.getAll(userId, { type: 'discussion' }) as SearchResult;
196
+
197
+ expect(allResult).toBeDefined();
198
+ expect(allResult.results.length).toBe(2);
199
+ expect(allResult.results.every(r => r.type === 'discussion')).toBe(true);
200
+
201
+ //
202
+ // ✅ Vérifier que chaque mémoire a un title dans metadata
203
+ expect(allResult.results.every(r => r.metadata?.title !== undefined)).toBe(true);
204
+
205
+ console.log('--- getAll() titles:', allResult.results.map(r => r.metadata?.title));
206
+ });
207
+
208
+ it("should get a single discussion memory with title in metadata (get)", async () => {
209
+ //
210
+ // Capturer
211
+ const captureResult = await memory.capture(discussionMessages, userId, {
212
+ metadata: { type: 'discussion', discussionId: 'get-test' }
213
+ }) as SearchResult;
214
+
215
+ const memoryId = captureResult.results[0].id;
216
+ const originalTitle = captureResult.results[0].metadata?.title;
217
+
218
+ //
219
+ // Récupérer avec get()
220
+ const getResult = await memory.get(memoryId, userId);
221
+
222
+ expect(getResult).not.toBeNull();
223
+ expect(getResult?.type).toBe('discussion');
224
+ expect(getResult?.memory).toBeDefined();
225
+
226
+ //
227
+ // ✅ Vérifier que le title est dans metadata
228
+ expect(getResult?.metadata?.title).toBeDefined();
229
+ expect(getResult?.metadata?.title).toBe(originalTitle);
230
+
231
+ console.log('--- get() title:', getResult?.metadata?.title);
232
+ });
233
+
234
+ it("should delete a discussion memory", async () => {
235
+ //
236
+ // Capturer
237
+ const result = await memory.capture(discussionMessages, userId, {
238
+ metadata: { type: 'discussion', discussionId: 'delete-test' }
239
+ }) as SearchResult;
240
+
241
+ const memoryId = result.results[0].id;
242
+
243
+ //
244
+ // Supprimer
245
+ await memory.delete(memoryId, userId);
246
+
247
+ //
248
+ // Vérifier suppression
249
+ const deleted = await memory.get(memoryId, userId);
250
+ expect(deleted).toBeNull();
251
+ });
252
+ });
253
+
254
+ describe("Scoring discussion", () => {
255
+
256
+ it("should have constant score (beta=1, halfLife=Infinity)", async () => {
257
+ //
258
+ // Capturer une discussion
259
+ await memory.capture(discussionMessages, userId, {
260
+ metadata: { type: 'discussion', discussionId: 'score-test' }
261
+ });
262
+
263
+ //
264
+ // Rechercher immédiatement
265
+ const result1 = await memory.retrieve("FAO Genève", userId, {
266
+ filters: { type: 'discussion' }
267
+ }) as SearchResult;
268
+
269
+ //
270
+ // Le score ne devrait pas dépendre du temps (halfLife=Infinity)
271
+ // Avec alpha=0, beta=1, gamma=0, le score = recency = 1 (constant)
272
+ expect(result1.results.length).toBeGreaterThan(0);
273
+ expect(result1.results[0].score).toBeDefined();
274
+
275
+ console.log('--- Score:', result1.results[0].score);
276
+ });
277
+ });
278
+ });
279
+
@@ -8,7 +8,11 @@ dotenv.config();
8
8
 
9
9
  jest.setTimeout(30000); // Increase timeout to 30 seconds
10
10
 
11
- describe("Memory Updates - Type Preservation", () => {
11
+ /**
12
+ * @deprecated Tests use factual/episodic types that have been removed
13
+ * These tests are skipped - see migration to discussion memory type
14
+ */
15
+ describe.skip("Memory Updates - Type Preservation (DEPRECATED)", () => {
12
16
  let memory: MemoriesLite;
13
17
  let userId: string;
14
18