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.
- package/dist/config/defaults.js +13 -20
- package/dist/config/manager.js +3 -10
- package/dist/memory/index.d.ts +12 -3
- package/dist/memory/index.js +61 -155
- 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 +40 -234
- package/dist/types/index.js +4 -27
- package/dist/vectorstores/lite.d.ts +1 -0
- package/dist/vectorstores/lite.js +11 -6
- 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 -20
- package/src/config/manager.ts +3 -10
- package/src/memory/index.ts +76 -197
- package/src/memory/memory.types.ts +1 -2
- package/src/prompts/index.ts +69 -26
- package/src/types/index.ts +8 -46
- package/src/vectorstores/lite.ts +13 -7
- package/tests/init.mem.ts +7 -4
- package/tests/lite.spec.ts +44 -34
- package/tests/memory.discussion.search.test.ts +377 -0
- 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
|
@@ -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
|
-
|
|
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
|
|