memories-lite 0.99.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.
@@ -7,18 +7,17 @@ const DEFAULT_SCORING_CONFIG = {
7
7
  assistant_preference: { alpha: 0.60, beta: 0.05, gamma: 0.35, halfLifeDays: Infinity },
8
8
  //
9
9
  // discussion: mémoires de discussions (synthèses opérationnelles)
10
- // - alpha=0: pas de cosine dans le score final
11
- // - beta=1: score basé sur recency (constant car halfLife=∞)
10
+ // - alpha=1: score basé uniquement sur la similarité cosinus
11
+ // - beta=0: pas de recency
12
12
  // - gamma=0: pas d'importance base
13
- discussion: { alpha: 0, beta: 1, gamma: 0, halfLifeDays: Infinity },
13
+ discussion: { alpha: 1, beta: 0, gamma: 0, halfLifeDays: Infinity },
14
14
  //
15
15
  // default: fallback si type manquant ou inconnu
16
16
  default: { alpha: 0.5, beta: 0.3, gamma: 0.1, halfLifeDays: 30 }
17
17
  };
18
18
  exports.DEFAULT_MEMORY_CONFIG = {
19
19
  disableHistory: true,
20
- enableGraph: false,
21
- version: "v1.1",
20
+ version: "v2.0",
22
21
  embedder: {
23
22
  provider: "openai",
24
23
  config: {
@@ -33,6 +32,7 @@ exports.DEFAULT_MEMORY_CONFIG = {
33
32
  collectionName: "memories",
34
33
  dimension: 768,
35
34
  scoring: DEFAULT_SCORING_CONFIG,
35
+ searchThreshold: 0.50, // Seuil minimum de score pour retourner un résultat
36
36
  },
37
37
  },
38
38
  llm: {
@@ -43,20 +43,6 @@ exports.DEFAULT_MEMORY_CONFIG = {
43
43
  modelProperties: undefined,
44
44
  },
45
45
  },
46
- graphStore: {
47
- provider: "neo4j",
48
- config: {
49
- url: process.env.NEO4J_URL || "neo4j://localhost:7687",
50
- username: process.env.NEO4J_USERNAME || "neo4j",
51
- password: process.env.NEO4J_PASSWORD || "password",
52
- },
53
- llm: {
54
- provider: "openai",
55
- config: {
56
- model: "gpt-5-mini",
57
- },
58
- },
59
- },
60
46
  historyStore: {
61
47
  provider: "dummy",
62
48
  config: {
@@ -96,17 +96,12 @@ class ConfigManager {
96
96
  })(),
97
97
  },
98
98
  historyDbPath: userConfig.historyDbPath || defaults_1.DEFAULT_MEMORY_CONFIG.historyDbPath,
99
- customPrompt: userConfig.customPrompt,
100
- graphStore: {
101
- ...defaults_1.DEFAULT_MEMORY_CONFIG.graphStore,
102
- ...userConfig.graphStore,
103
- },
99
+ capturePrompt: userConfig.capturePrompt,
104
100
  historyStore: {
105
101
  ...defaults_1.DEFAULT_MEMORY_CONFIG.historyStore,
106
102
  ...userConfig.historyStore,
107
103
  },
108
104
  disableHistory: userConfig.disableHistory || defaults_1.DEFAULT_MEMORY_CONFIG.disableHistory,
109
- enableGraph: userConfig.enableGraph || defaults_1.DEFAULT_MEMORY_CONFIG.enableGraph,
110
105
  };
111
106
  // Validate the merged config
112
107
  return types_1.MemoryConfigSchema.parse(mergedConfig);
@@ -3,15 +3,13 @@ import { VectorStore } from "../vectorstores/base";
3
3
  import { AddMemoryOptions, SearchMemoryOptions, DeleteAllMemoryOptions, GetAllMemoryOptions } from "./memory.types";
4
4
  export declare class MemoriesLite {
5
5
  private config;
6
- private customPrompt;
6
+ private capturePrompt;
7
7
  private embedder;
8
8
  private vectorStoreConfig;
9
9
  private llm;
10
10
  private db;
11
11
  private collectionName;
12
12
  private apiVersion;
13
- private graphMemory?;
14
- private enableGraph;
15
13
  telemetryId: string;
16
14
  constructor(config?: Partial<MemoryConfig>);
17
15
  private _initializeTelemetry;
@@ -16,7 +16,7 @@ class MemoriesLite {
16
16
  constructor(config = {}) {
17
17
  // Merge and validate config
18
18
  this.config = manager_1.ConfigManager.mergeConfig(config);
19
- this.customPrompt = this.config.customPrompt;
19
+ this.capturePrompt = this.config.capturePrompt;
20
20
  this.embedder = factory_1.EmbedderFactory.create(this.config.embedder.provider, this.config.embedder.config);
21
21
  //
22
22
  // vectorStore.provider is "lite"
@@ -39,12 +39,7 @@ class MemoriesLite {
39
39
  }
40
40
  this.collectionName = this.config.vectorStore.config.collectionName;
41
41
  this.apiVersion = this.config.version || "v1.0";
42
- this.enableGraph = this.config.enableGraph || false;
43
42
  this.telemetryId = "anonymous";
44
- // Initialize graph memory if configured
45
- if (this.enableGraph && this.config.graphStore) {
46
- // this.graphMemory = new MemoryGraph(this.config);
47
- }
48
43
  // Initialize telemetry if vector store is initialized
49
44
  // this._initializeTelemetry();
50
45
  }
@@ -56,7 +51,6 @@ class MemoriesLite {
56
51
  api_version: this.apiVersion,
57
52
  client_type: "Memory",
58
53
  collection_name: this.collectionName,
59
- enable_graph: this.enableGraph,
60
54
  });
61
55
  }
62
56
  catch (error) { }
@@ -108,7 +102,7 @@ class MemoriesLite {
108
102
  .join("\n\n");
109
103
  //
110
104
  // Générer la synthèse via LLM
111
- const [systemPrompt, userPrompt] = (0, prompts_1.getDiscussionSynthesisMessages)(parsedMessages, capturePrompt || this.customPrompt);
105
+ const [systemPrompt, userPrompt] = (0, prompts_1.getDiscussionSynthesisMessages)(parsedMessages, capturePrompt || this.capturePrompt);
112
106
  const response = await this.llm.generateResponse([
113
107
  { role: "system", content: systemPrompt },
114
108
  { role: "user", content: userPrompt },
@@ -134,8 +128,8 @@ class MemoriesLite {
134
128
  return [];
135
129
  }
136
130
  //
137
- // Créer l'embedding sur le summary (pour recherche sémantique)
138
- const embedding = await this.embedder.embed(summary);
131
+ // Créer l'embedding sur le title seul (TEST COMPARATIF)
132
+ const embedding = await this.embedder.embed(title);
139
133
  //
140
134
  // Préparer les métadonnées
141
135
  const memoryType = metadata.type || 'discussion';
@@ -192,20 +186,8 @@ class MemoriesLite {
192
186
  //
193
187
  // Générer synthèse et stocker
194
188
  const vectorStoreResult = await this.addToVectorStore(final_parsedMessages, metadata, userId, filters, capturePrompt);
195
- //
196
- // Graph store (si configuré)
197
- let graphResult;
198
- if (this.graphMemory) {
199
- try {
200
- graphResult = await this.graphMemory.add(final_parsedMessages.map((m) => m.content).join("\n"), filters);
201
- }
202
- catch (error) {
203
- console.error("Error adding to graph memory:", error);
204
- }
205
- }
206
189
  return {
207
190
  results: vectorStoreResult,
208
- relations: graphResult?.relations,
209
191
  };
210
192
  }
211
193
  async get(memoryId, userId) {
@@ -262,16 +244,6 @@ class MemoriesLite {
262
244
  // Search vector store
263
245
  const queryEmbedding = await this.embedder.embed(query);
264
246
  const memories = await vectorStore.search(queryEmbedding, limit, filters);
265
- // Search graph store if available
266
- let graphResults = [];
267
- if (this.graphMemory) {
268
- try {
269
- graphResults = await this.graphMemory.search(query, filters);
270
- }
271
- catch (error) {
272
- console.error("Error searching graph memory:", error);
273
- }
274
- }
275
247
  const excludedKeys = new Set([
276
248
  "userId",
277
249
  "agentId",
@@ -297,7 +269,6 @@ class MemoriesLite {
297
269
  }));
298
270
  return {
299
271
  results,
300
- relations: graphResults,
301
272
  };
302
273
  }
303
274
  async update(memoryId, data, userId) {
@@ -346,9 +317,6 @@ class MemoriesLite {
346
317
  await this.db.reset();
347
318
  // Check provider before attempting deleteCol
348
319
  await vectorStore.deleteCol();
349
- if (this.graphMemory) {
350
- await this.graphMemory.deleteAll({ userId: "default" }); // Assuming this is okay, or needs similar check?
351
- }
352
320
  // Re-initialize factories/clients based on the original config
353
321
  this.embedder = factory_1.EmbedderFactory.create(this.config.embedder.provider, this.config.embedder.config);
354
322
  this.llm = factory_1.LLMFactory.create(this.config.llm.provider, this.config.llm.config);
@@ -18,7 +18,7 @@ export declare const DiscussionSynthesisSchema: z.ZodObject<{
18
18
  * Prompt par défaut pour la synthèse de discussion
19
19
  * Peut être remplacé via capturePrompt dans AddMemoryOptions
20
20
  */
21
- export declare const DEFAULT_DISCUSSION_PROMPT = "Tu es un expert en synth\u00E8se op\u00E9rationnelle.\n\n\u00C0 partir de cette discussion, g\u00E9n\u00E8re :\n1. TITRE: Un titre court et descriptif (6-10 mots) qui capture l'essence de la demande\n2. SUMMARY: Les points cl\u00E9s du chemin de r\u00E9solution (50-100 mots)\n\nCette synth\u00E8se servira \u00E0 retrouver et r\u00E9appliquer ce pattern de r\u00E9solution similaire.\nUtilise la m\u00EAme langue que la discussion.\n\nDiscussion \u00E0 synth\u00E9tiser:\n";
21
+ export declare const DEFAULT_DISCUSSION_PROMPT = "Tu es un expert en synth\u00E8se op\u00E9rationnelle.\n\n\u00C0 partir de cette discussion, g\u00E9n\u00E8re :\n1. TITRE: Un titre court et descriptif (10-20 mots) qui capture l'essence de la demande\n2. SUMMARY: Les points cl\u00E9s du chemin de r\u00E9solution en markdown (max 150 mots)\n\nCette synth\u00E8se servira \u00E0 retrouver et r\u00E9appliquer ce pattern de r\u00E9solution similaire.\nUtilise la m\u00EAme langue que la discussion.\n\nDiscussion \u00E0 synth\u00E9tiser:\n";
22
22
  /**
23
23
  * Génère les messages pour la synthèse de discussion
24
24
  * @param discussion - Contenu de la discussion formatée
@@ -25,8 +25,8 @@ exports.DiscussionSynthesisSchema = zod_1.z.object({
25
25
  exports.DEFAULT_DISCUSSION_PROMPT = `Tu es un expert en synthèse opérationnelle.
26
26
 
27
27
  À partir de cette discussion, génère :
28
- 1. TITRE: Un titre court et descriptif (6-10 mots) qui capture l'essence de la demande
29
- 2. SUMMARY: Les points clés du chemin de résolution (50-100 mots)
28
+ 1. TITRE: Un titre court et descriptif (10-20 mots) qui capture l'essence de la demande
29
+ 2. SUMMARY: Les points clés du chemin de résolution en markdown (max 150 mots)
30
30
 
31
31
  Cette synthèse servira à retrouver et réappliquer ce pattern de résolution similaire.
32
32
  Utilise la même langue que la discussion.
@@ -26,6 +26,7 @@ export interface VectorStoreConfig {
26
26
  cacheTtl?: number;
27
27
  scoring?: MemoryScoringConfig;
28
28
  recencyCleanupThreshold?: number;
29
+ searchThreshold?: number;
29
30
  [key: string]: any;
30
31
  }
31
32
  export interface HistoryStoreConfig {
@@ -44,17 +45,6 @@ export interface LLMConfig {
44
45
  model?: string | any;
45
46
  modelProperties?: Record<string, any>;
46
47
  }
47
- export interface Neo4jConfig {
48
- url: string;
49
- username: string;
50
- password: string;
51
- }
52
- export interface GraphStoreConfig {
53
- provider: string;
54
- config: Neo4jConfig;
55
- llm?: LLMConfig;
56
- customPrompt?: string;
57
- }
58
48
  export interface MemoryTypeConfig {
59
49
  alpha: number;
60
50
  beta: number;
@@ -84,9 +74,7 @@ export interface MemoryConfig {
84
74
  historyStore?: HistoryStoreConfig;
85
75
  disableHistory?: boolean;
86
76
  historyDbPath?: string;
87
- customPrompt?: string;
88
- graphStore?: GraphStoreConfig;
89
- enableGraph?: boolean;
77
+ capturePrompt?: string;
90
78
  }
91
79
  export type MemoryType = 'assistant_preference' | 'discussion';
92
80
  export interface MemoryItem {
@@ -256,6 +244,7 @@ export declare const MemoryConfigSchema: z.ZodObject<{
256
244
  };
257
245
  }>>;
258
246
  recencyCleanupThreshold: z.ZodOptional<z.ZodNumber>;
247
+ searchThreshold: z.ZodOptional<z.ZodNumber>;
259
248
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
260
249
  collectionName: z.ZodOptional<z.ZodString>;
261
250
  dimension: z.ZodOptional<z.ZodNumber>;
@@ -349,6 +338,7 @@ export declare const MemoryConfigSchema: z.ZodObject<{
349
338
  };
350
339
  }>>;
351
340
  recencyCleanupThreshold: z.ZodOptional<z.ZodNumber>;
341
+ searchThreshold: z.ZodOptional<z.ZodNumber>;
352
342
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
353
343
  collectionName: z.ZodOptional<z.ZodString>;
354
344
  dimension: z.ZodOptional<z.ZodNumber>;
@@ -442,6 +432,7 @@ export declare const MemoryConfigSchema: z.ZodObject<{
442
432
  };
443
433
  }>>;
444
434
  recencyCleanupThreshold: z.ZodOptional<z.ZodNumber>;
435
+ searchThreshold: z.ZodOptional<z.ZodNumber>;
445
436
  }, z.ZodTypeAny, "passthrough">>;
446
437
  }, "strip", z.ZodTypeAny, {
447
438
  provider: string;
@@ -470,6 +461,7 @@ export declare const MemoryConfigSchema: z.ZodObject<{
470
461
  };
471
462
  } | undefined;
472
463
  recencyCleanupThreshold?: number | undefined;
464
+ searchThreshold?: number | undefined;
473
465
  } & {
474
466
  [k: string]: unknown;
475
467
  };
@@ -500,6 +492,7 @@ export declare const MemoryConfigSchema: z.ZodObject<{
500
492
  };
501
493
  } | undefined;
502
494
  recencyCleanupThreshold?: number | undefined;
495
+ searchThreshold?: number | undefined;
503
496
  } & {
504
497
  [k: string]: unknown;
505
498
  };
@@ -535,59 +528,7 @@ export declare const MemoryConfigSchema: z.ZodObject<{
535
528
  };
536
529
  }>;
537
530
  historyDbPath: z.ZodOptional<z.ZodString>;
538
- customPrompt: z.ZodOptional<z.ZodString>;
539
- enableGraph: z.ZodOptional<z.ZodBoolean>;
540
- graphStore: z.ZodOptional<z.ZodObject<{
541
- provider: z.ZodString;
542
- config: z.ZodObject<{
543
- url: z.ZodString;
544
- username: z.ZodString;
545
- password: z.ZodString;
546
- }, "strip", z.ZodTypeAny, {
547
- url: string;
548
- username: string;
549
- password: string;
550
- }, {
551
- url: string;
552
- username: string;
553
- password: string;
554
- }>;
555
- llm: z.ZodOptional<z.ZodObject<{
556
- provider: z.ZodString;
557
- config: z.ZodRecord<z.ZodString, z.ZodAny>;
558
- }, "strip", z.ZodTypeAny, {
559
- provider: string;
560
- config: Record<string, any>;
561
- }, {
562
- provider: string;
563
- config: Record<string, any>;
564
- }>>;
565
- customPrompt: z.ZodOptional<z.ZodString>;
566
- }, "strip", z.ZodTypeAny, {
567
- provider: string;
568
- config: {
569
- url: string;
570
- username: string;
571
- password: string;
572
- };
573
- llm?: {
574
- provider: string;
575
- config: Record<string, any>;
576
- } | undefined;
577
- customPrompt?: string | undefined;
578
- }, {
579
- provider: string;
580
- config: {
581
- url: string;
582
- username: string;
583
- password: string;
584
- };
585
- llm?: {
586
- provider: string;
587
- config: Record<string, any>;
588
- } | undefined;
589
- customPrompt?: string | undefined;
590
- }>>;
531
+ capturePrompt: z.ZodOptional<z.ZodString>;
591
532
  historyStore: z.ZodOptional<z.ZodObject<{
592
533
  provider: z.ZodString;
593
534
  config: z.ZodRecord<z.ZodString, z.ZodAny>;
@@ -636,6 +577,7 @@ export declare const MemoryConfigSchema: z.ZodObject<{
636
577
  };
637
578
  } | undefined;
638
579
  recencyCleanupThreshold?: number | undefined;
580
+ searchThreshold?: number | undefined;
639
581
  } & {
640
582
  [k: string]: unknown;
641
583
  };
@@ -650,21 +592,7 @@ export declare const MemoryConfigSchema: z.ZodObject<{
650
592
  };
651
593
  version?: string | undefined;
652
594
  historyDbPath?: string | undefined;
653
- customPrompt?: string | undefined;
654
- enableGraph?: boolean | undefined;
655
- graphStore?: {
656
- provider: string;
657
- config: {
658
- url: string;
659
- username: string;
660
- password: string;
661
- };
662
- llm?: {
663
- provider: string;
664
- config: Record<string, any>;
665
- } | undefined;
666
- customPrompt?: string | undefined;
667
- } | undefined;
595
+ capturePrompt?: string | undefined;
668
596
  historyStore?: {
669
597
  provider: string;
670
598
  config: Record<string, any>;
@@ -707,6 +635,7 @@ export declare const MemoryConfigSchema: z.ZodObject<{
707
635
  };
708
636
  } | undefined;
709
637
  recencyCleanupThreshold?: number | undefined;
638
+ searchThreshold?: number | undefined;
710
639
  } & {
711
640
  [k: string]: unknown;
712
641
  };
@@ -721,21 +650,7 @@ export declare const MemoryConfigSchema: z.ZodObject<{
721
650
  };
722
651
  version?: string | undefined;
723
652
  historyDbPath?: string | undefined;
724
- customPrompt?: string | undefined;
725
- enableGraph?: boolean | undefined;
726
- graphStore?: {
727
- provider: string;
728
- config: {
729
- url: string;
730
- username: string;
731
- password: string;
732
- };
733
- llm?: {
734
- provider: string;
735
- config: Record<string, any>;
736
- } | undefined;
737
- customPrompt?: string | undefined;
738
- } | undefined;
653
+ capturePrompt?: string | undefined;
739
654
  historyStore?: {
740
655
  provider: string;
741
656
  config: Record<string, any>;
@@ -41,6 +41,7 @@ exports.MemoryConfigSchema = zod_1.z.object({
41
41
  }),
42
42
  }).optional(),
43
43
  recencyCleanupThreshold: zod_1.z.number().min(0).max(1).optional(),
44
+ searchThreshold: zod_1.z.number().min(0).max(1).optional(),
44
45
  })
45
46
  .passthrough(),
46
47
  }),
@@ -53,25 +54,7 @@ exports.MemoryConfigSchema = zod_1.z.object({
53
54
  }),
54
55
  }),
55
56
  historyDbPath: zod_1.z.string().optional(),
56
- customPrompt: zod_1.z.string().optional(),
57
- enableGraph: zod_1.z.boolean().optional(),
58
- graphStore: zod_1.z
59
- .object({
60
- provider: zod_1.z.string(),
61
- config: zod_1.z.object({
62
- url: zod_1.z.string(),
63
- username: zod_1.z.string(),
64
- password: zod_1.z.string(),
65
- }),
66
- llm: zod_1.z
67
- .object({
68
- provider: zod_1.z.string(),
69
- config: zod_1.z.record(zod_1.z.string(), zod_1.z.any()),
70
- })
71
- .optional(),
72
- customPrompt: zod_1.z.string().optional(),
73
- })
74
- .optional(),
57
+ capturePrompt: zod_1.z.string().optional(),
75
58
  historyStore: zod_1.z
76
59
  .object({
77
60
  provider: zod_1.z.string(),
@@ -18,6 +18,7 @@ export declare class LiteVectorStore implements VectorStore {
18
18
  private currentUserId;
19
19
  private scoringConfig;
20
20
  private cleanupThreshold?;
21
+ private searchThreshold;
21
22
  private static cache;
22
23
  constructor(config: VectorStoreConfig, currentUserId: string);
23
24
  private init;
@@ -28,7 +28,8 @@ class LiteVectorStore {
28
28
  this.currentUserId = currentUserId;
29
29
  this.isSecure = config.secure || false;
30
30
  this.scoringConfig = config.scoring;
31
- this.cleanupThreshold = config.recencyCleanupThreshold || 0.25; // (default 0.25 means 2 times the half-life )
31
+ this.cleanupThreshold = config.recencyCleanupThreshold || 0.25; // (default 0.25 means 2 times the half-life)
32
+ this.searchThreshold = config.searchThreshold ?? 0; // Seuil de score (default 0 = pas de filtrage)
32
33
  config.rootPath = config.rootPath || process.cwd();
33
34
  const filename = this.isSecure ? `memories-lite-${currentUserId}.db` : `memories-lite-global.db`;
34
35
  this.dbPath = (config.rootPath == ':memory:') ? ':memory:' : path_1.default.join(config.rootPath, filename);
@@ -195,11 +196,15 @@ class LiteVectorStore {
195
196
  if (this.filterVector(memoryVector, filters)) {
196
197
  const cosineScore = this.cosineSimilarity(query, vector);
197
198
  const hybridScore = this.calculateHybridScore(cosineScore, payload);
198
- results.push({
199
- id: memoryVector.id,
200
- payload: memoryVector.payload,
201
- score: hybridScore,
202
- });
199
+ //
200
+ // Filtrer par searchThreshold - ne retourner que les résultats au-dessus du seuil
201
+ if (hybridScore >= this.searchThreshold) {
202
+ results.push({
203
+ id: memoryVector.id,
204
+ payload: memoryVector.payload,
205
+ score: hybridScore,
206
+ });
207
+ }
203
208
  }
204
209
  }
205
210
  results.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memories-lite",
3
- "version": "0.99.1",
3
+ "version": "0.99.2",
4
4
  "description": "A lightweight memory system for LLM agents",
5
5
  "author": "olivier@evaletolab.ch",
6
6
  "main": "dist/index.js",
@@ -6,10 +6,10 @@ const DEFAULT_SCORING_CONFIG: MemoryScoringConfig = {
6
6
  assistant_preference: { alpha: 0.60, beta: 0.05, gamma: 0.35, halfLifeDays: Infinity },
7
7
  //
8
8
  // discussion: mémoires de discussions (synthèses opérationnelles)
9
- // - alpha=0: pas de cosine dans le score final
10
- // - beta=1: score basé sur recency (constant car halfLife=∞)
9
+ // - alpha=1: score basé uniquement sur la similarité cosinus
10
+ // - beta=0: pas de recency
11
11
  // - gamma=0: pas d'importance base
12
- discussion: { alpha: 0, beta: 1, gamma: 0, halfLifeDays: Infinity },
12
+ discussion: { alpha: 1, beta: 0, gamma: 0, halfLifeDays: Infinity },
13
13
  //
14
14
  // default: fallback si type manquant ou inconnu
15
15
  default: { alpha: 0.5, beta: 0.3, gamma: 0.1, halfLifeDays: 30 }
@@ -17,8 +17,7 @@ const DEFAULT_SCORING_CONFIG: MemoryScoringConfig = {
17
17
 
18
18
  export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
19
19
  disableHistory: true,
20
- enableGraph: false,
21
- version: "v1.1",
20
+ version: "v2.0",
22
21
  embedder: {
23
22
  provider: "openai",
24
23
  config: {
@@ -33,6 +32,7 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
33
32
  collectionName: "memories",
34
33
  dimension: 768,
35
34
  scoring: DEFAULT_SCORING_CONFIG,
35
+ searchThreshold: 0.50, // Seuil minimum de score pour retourner un résultat
36
36
  },
37
37
  },
38
38
  llm: {
@@ -43,20 +43,6 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
43
43
  modelProperties: undefined,
44
44
  },
45
45
  },
46
- graphStore: {
47
- provider: "neo4j",
48
- config: {
49
- url: process.env.NEO4J_URL || "neo4j://localhost:7687",
50
- username: process.env.NEO4J_USERNAME || "neo4j",
51
- password: process.env.NEO4J_PASSWORD || "password",
52
- },
53
- llm: {
54
- provider: "openai",
55
- config: {
56
- model: "gpt-5-mini",
57
- },
58
- },
59
- },
60
46
  historyStore: {
61
47
  provider: "dummy",
62
48
  config: {
@@ -106,18 +106,13 @@ export class ConfigManager {
106
106
  },
107
107
  historyDbPath:
108
108
  userConfig.historyDbPath || DEFAULT_MEMORY_CONFIG.historyDbPath,
109
- customPrompt: userConfig.customPrompt,
110
- graphStore: {
111
- ...DEFAULT_MEMORY_CONFIG.graphStore,
112
- ...userConfig.graphStore,
113
- },
109
+ capturePrompt: userConfig.capturePrompt,
114
110
  historyStore: {
115
111
  ...DEFAULT_MEMORY_CONFIG.historyStore,
116
112
  ...userConfig.historyStore,
117
113
  },
118
114
  disableHistory:
119
115
  userConfig.disableHistory || DEFAULT_MEMORY_CONFIG.disableHistory,
120
- enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph,
121
116
  };
122
117
 
123
118
  // Validate the merged config
@@ -40,22 +40,20 @@ import { zodResponseFormat } from "openai/helpers/zod";
40
40
 
41
41
  export class MemoriesLite {
42
42
  private config: MemoryConfig;
43
- private customPrompt: string | undefined;
43
+ private capturePrompt: string | undefined;
44
44
  private embedder: Embedder;
45
45
  private vectorStoreConfig: VectorStoreConfig;
46
46
  private llm: LLM;
47
47
  private db: HistoryManager;
48
48
  private collectionName: string | undefined;
49
49
  private apiVersion: string;
50
- private graphMemory?: any;
51
- private enableGraph: boolean;
52
50
  telemetryId: string;
53
51
 
54
52
  constructor(config: Partial<MemoryConfig> = {}) {
55
53
  // Merge and validate config
56
54
  this.config = ConfigManager.mergeConfig(config);
57
55
 
58
- this.customPrompt = this.config.customPrompt;
56
+ this.capturePrompt = this.config.capturePrompt;
59
57
  this.embedder = EmbedderFactory.create(
60
58
  this.config.embedder.provider,
61
59
  this.config.embedder.config,
@@ -88,14 +86,8 @@ export class MemoriesLite {
88
86
 
89
87
  this.collectionName = this.config.vectorStore.config.collectionName;
90
88
  this.apiVersion = this.config.version || "v1.0";
91
- this.enableGraph = this.config.enableGraph || false;
92
89
  this.telemetryId = "anonymous";
93
90
 
94
- // Initialize graph memory if configured
95
- if (this.enableGraph && this.config.graphStore) {
96
- // this.graphMemory = new MemoryGraph(this.config);
97
- }
98
-
99
91
  // Initialize telemetry if vector store is initialized
100
92
  // this._initializeTelemetry();
101
93
  }
@@ -109,7 +101,6 @@ export class MemoriesLite {
109
101
  api_version: this.apiVersion,
110
102
  client_type: "Memory",
111
103
  collection_name: this.collectionName,
112
- enable_graph: this.enableGraph,
113
104
  });
114
105
  } catch (error) {}
115
106
  }
@@ -176,7 +167,7 @@ export class MemoriesLite {
176
167
  // Générer la synthèse via LLM
177
168
  const [systemPrompt, userPrompt] = getDiscussionSynthesisMessages(
178
169
  parsedMessages,
179
- capturePrompt || this.customPrompt
170
+ capturePrompt || this.capturePrompt
180
171
  );
181
172
 
182
173
  const response = await this.llm.generateResponse(
@@ -212,8 +203,8 @@ export class MemoriesLite {
212
203
  }
213
204
 
214
205
  //
215
- // Créer l'embedding sur le summary (pour recherche sémantique)
216
- const embedding = await this.embedder.embed(summary);
206
+ // Créer l'embedding sur le title seul (TEST COMPARATIF)
207
+ const embedding = await this.embedder.embed(title);
217
208
 
218
209
  //
219
210
  // Préparer les métadonnées
@@ -303,23 +294,8 @@ export class MemoriesLite {
303
294
  capturePrompt,
304
295
  );
305
296
 
306
- //
307
- // Graph store (si configuré)
308
- let graphResult;
309
- if (this.graphMemory) {
310
- try {
311
- graphResult = await this.graphMemory.add(
312
- final_parsedMessages.map((m) => m.content).join("\n"),
313
- filters,
314
- );
315
- } catch (error) {
316
- console.error("Error adding to graph memory:", error);
317
- }
318
- }
319
-
320
297
  return {
321
298
  results: vectorStoreResult,
322
- relations: graphResult?.relations,
323
299
  };
324
300
  }
325
301
 
@@ -398,16 +374,6 @@ export class MemoriesLite {
398
374
  );
399
375
 
400
376
 
401
- // Search graph store if available
402
- let graphResults =[];
403
- if (this.graphMemory) {
404
- try {
405
- graphResults = await this.graphMemory.search(query, filters);
406
- } catch (error) {
407
- console.error("Error searching graph memory:", error);
408
- }
409
- }
410
-
411
377
  const excludedKeys = new Set([
412
378
  "userId",
413
379
  "agentId",
@@ -434,7 +400,6 @@ export class MemoriesLite {
434
400
 
435
401
  return {
436
402
  results,
437
- relations: graphResults,
438
403
  };
439
404
  }
440
405
 
@@ -496,10 +461,6 @@ export class MemoriesLite {
496
461
  // Check provider before attempting deleteCol
497
462
  await vectorStore.deleteCol();
498
463
 
499
- if (this.graphMemory) {
500
- await this.graphMemory.deleteAll({ userId: "default" }); // Assuming this is okay, or needs similar check?
501
- }
502
-
503
464
  // Re-initialize factories/clients based on the original config
504
465
  this.embedder = EmbedderFactory.create(
505
466
  this.config.embedder.provider,
@@ -21,8 +21,8 @@ export const DiscussionSynthesisSchema = z.object({
21
21
  export const DEFAULT_DISCUSSION_PROMPT = `Tu es un expert en synthèse opérationnelle.
22
22
 
23
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)
24
+ 1. TITRE: Un titre court et descriptif (10-20 mots) qui capture l'essence de la demande
25
+ 2. SUMMARY: Les points clés du chemin de résolution en markdown (max 150 mots)
26
26
 
27
27
  Cette synthèse servira à retrouver et réappliquer ce pattern de résolution similaire.
28
28
  Utilise la même langue que la discussion.
@@ -30,6 +30,7 @@ export interface VectorStoreConfig {
30
30
  cacheTtl?: number;
31
31
  scoring?: MemoryScoringConfig;
32
32
  recencyCleanupThreshold?: number;
33
+ searchThreshold?: number; // Seuil minimum de score pour retourner un résultat (0-1)
33
34
  [key: string]: any;
34
35
  }
35
36
 
@@ -51,19 +52,6 @@ export interface LLMConfig {
51
52
  modelProperties?: Record<string, any>;
52
53
  }
53
54
 
54
- export interface Neo4jConfig {
55
- url: string;
56
- username: string;
57
- password: string;
58
- }
59
-
60
- export interface GraphStoreConfig {
61
- provider: string;
62
- config: Neo4jConfig;
63
- llm?: LLMConfig;
64
- customPrompt?: string;
65
- }
66
-
67
55
  export interface MemoryTypeConfig {
68
56
  alpha: number; // Weight for cosine similarity
69
57
  beta: number; // Weight for recency
@@ -95,9 +83,7 @@ export interface MemoryConfig {
95
83
  historyStore?: HistoryStoreConfig;
96
84
  disableHistory?: boolean;
97
85
  historyDbPath?: string;
98
- customPrompt?: string;
99
- graphStore?: GraphStoreConfig;
100
- enableGraph?: boolean;
86
+ capturePrompt?: string; // Prompt par défaut pour la synthèse de discussion
101
87
  }
102
88
 
103
89
  export type MemoryType = 'assistant_preference' | 'discussion';
@@ -181,6 +167,7 @@ export const MemoryConfigSchema = z.object({
181
167
  }),
182
168
  }).optional(),
183
169
  recencyCleanupThreshold: z.number().min(0).max(1).optional(),
170
+ searchThreshold: z.number().min(0).max(1).optional(),
184
171
  })
185
172
  .passthrough(),
186
173
  }),
@@ -193,25 +180,7 @@ export const MemoryConfigSchema = z.object({
193
180
  }),
194
181
  }),
195
182
  historyDbPath: z.string().optional(),
196
- customPrompt: z.string().optional(),
197
- enableGraph: z.boolean().optional(),
198
- graphStore: z
199
- .object({
200
- provider: z.string(),
201
- config: z.object({
202
- url: z.string(),
203
- username: z.string(),
204
- password: z.string(),
205
- }),
206
- llm: z
207
- .object({
208
- provider: z.string(),
209
- config: z.record(z.string(), z.any()),
210
- })
211
- .optional(),
212
- customPrompt: z.string().optional(),
213
- })
214
- .optional(),
183
+ capturePrompt: z.string().optional(),
215
184
  historyStore: z
216
185
  .object({
217
186
  provider: z.string(),
@@ -31,7 +31,8 @@ export class LiteVectorStore implements VectorStore {
31
31
  private dimension: number;
32
32
  private currentUserId: string;
33
33
  private scoringConfig: MemoryScoringConfig;
34
- private cleanupThreshold?: number; // Store the threshold
34
+ private cleanupThreshold?: number; // Seuil de recency pour cleanup
35
+ private searchThreshold: number; // Seuil minimum de score pour retourner un résultat
35
36
  private static cache: Map<string, LiteVectorStore>;
36
37
  constructor(config: VectorStoreConfig, currentUserId: string) {
37
38
  if (!config.scoring) {
@@ -41,7 +42,8 @@ export class LiteVectorStore implements VectorStore {
41
42
  this.currentUserId = currentUserId;
42
43
  this.isSecure = config.secure || false;
43
44
  this.scoringConfig = config.scoring;
44
- this.cleanupThreshold = config.recencyCleanupThreshold || 0.25; // (default 0.25 means 2 times the half-life )
45
+ this.cleanupThreshold = config.recencyCleanupThreshold || 0.25; // (default 0.25 means 2 times the half-life)
46
+ this.searchThreshold = config.searchThreshold ?? 0; // Seuil de score (default 0 = pas de filtrage)
45
47
  config.rootPath = config.rootPath || process.cwd();
46
48
  const filename = this.isSecure ? `memories-lite-${currentUserId}.db` : `memories-lite-global.db`;
47
49
  this.dbPath = (config.rootPath == ':memory:') ? ':memory:' : path.join(config.rootPath, filename);
@@ -246,11 +248,15 @@ export class LiteVectorStore implements VectorStore {
246
248
  const cosineScore = this.cosineSimilarity(query, vector);
247
249
  const hybridScore = this.calculateHybridScore(cosineScore, payload);
248
250
 
249
- results.push({
250
- id: memoryVector.id,
251
- payload: memoryVector.payload,
252
- score: hybridScore,
253
- });
251
+ //
252
+ // Filtrer par searchThreshold - ne retourner que les résultats au-dessus du seuil
253
+ if (hybridScore >= this.searchThreshold) {
254
+ results.push({
255
+ id: memoryVector.id,
256
+ payload: memoryVector.payload,
257
+ score: hybridScore,
258
+ });
259
+ }
254
260
  }
255
261
  }
256
262
 
package/tests/init.mem.ts CHANGED
@@ -6,9 +6,10 @@ dotenv.config();
6
6
 
7
7
  /**
8
8
  * Helper to initialize MemoriesLite instance and generate a random userId.
9
- * @param customPrompt Optional prompt to inject into the memory config.
9
+ * @param capturePrompt Optional prompt to inject into the memory config.
10
+ * @param searchThreshold Optional minimum score threshold for search results (0-1)
10
11
  */
11
- export function createTestMemory({customPrompt, dimension, rootPath, secure}:any) {
12
+ export function createTestMemory({capturePrompt, dimension, rootPath, secure, searchThreshold}:any) {
12
13
  dimension = dimension || 768;
13
14
  const userId =
14
15
  Math.random().toString(36).substring(2, 15) +
@@ -17,7 +18,7 @@ export function createTestMemory({customPrompt, dimension, rootPath, secure}:any
17
18
  const memory = new MemoriesLite({
18
19
  version: "v1.1",
19
20
  disableHistory: true,
20
- ...(customPrompt ? { customPrompt } : {}),
21
+ ...(capturePrompt ? { capturePrompt } : {}),
21
22
  embedder: {
22
23
  provider: "openai",
23
24
  config: { dimension, apiKey: process.env.OPENAI_API_KEY!, model: "text-embedding-3-small" }
@@ -27,7 +28,9 @@ export function createTestMemory({customPrompt, dimension, rootPath, secure}:any
27
28
  config: {
28
29
  dimension,
29
30
  rootPath: (rootPath || ":memory:"),
30
- secure: secure || false }
31
+ secure: secure || false,
32
+ searchThreshold: searchThreshold ?? 0 // Par défaut 0 (pas de filtrage) pour les tests existants
33
+ }
31
34
  },
32
35
  llm: {
33
36
  provider: "openai",
@@ -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
+