memories-lite 0.99.1 → 0.99.3

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
+