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.
@@ -1,36 +1,83 @@
1
1
  import { z } from "zod";
2
2
  import { MemoryItem } from "../types";
3
3
 
4
- // Define Zod schema for fact retrieval output
4
+ // ══════════════════════════════════════════════════════════════════════════════
5
+ // DISCUSSION SYNTHESIS - Nouveau système de mémoire
6
+ // ══════════════════════════════════════════════════════════════════════════════
7
+
8
+ /**
9
+ * Schema pour la synthèse de discussion
10
+ * Produit un titre court et une synthèse opérationnelle
11
+ */
12
+ export const DiscussionSynthesisSchema = z.object({
13
+ title: z.string().describe("Titre court et descriptif (6-10 mots)"),
14
+ summary: z.string().describe("Synthèse opérationnelle des points clés (50-100 mots)")
15
+ });
16
+
17
+ /**
18
+ * Prompt par défaut pour la synthèse de discussion
19
+ * Peut être remplacé via capturePrompt dans AddMemoryOptions
20
+ */
21
+ export const DEFAULT_DISCUSSION_PROMPT = `Tu es un expert en synthèse opérationnelle.
22
+
23
+ À partir de cette discussion, génère :
24
+ 1. TITRE: Un titre court et descriptif (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
+
27
+ Cette synthèse servira à retrouver et réappliquer ce pattern de résolution similaire.
28
+ Utilise la même langue que la discussion.
29
+
30
+ Discussion à synthétiser:
31
+ `;
32
+
33
+ /**
34
+ * Génère les messages pour la synthèse de discussion
35
+ * @param discussion - Contenu de la discussion formatée
36
+ * @param capturePrompt - Prompt custom optionnel (remplace DEFAULT_DISCUSSION_PROMPT)
37
+ * @returns [systemPrompt, userPrompt]
38
+ */
39
+ export function getDiscussionSynthesisMessages(
40
+ discussion: string,
41
+ capturePrompt?: string
42
+ ): [string, string] {
43
+ const systemPrompt = capturePrompt || DEFAULT_DISCUSSION_PROMPT;
44
+ return [systemPrompt, discussion];
45
+ }
46
+
47
+ // ══════════════════════════════════════════════════════════════════════════════
48
+ // @deprecated - Ancien système de capture (todo, factual)
49
+ // Ces exports sont conservés pour compatibilité mais ne doivent plus être utilisés
50
+ // ══════════════════════════════════════════════════════════════════════════════
51
+
52
+ /**
53
+ * @deprecated Use DiscussionSynthesisSchema instead
54
+ */
5
55
  export const FactRetrievalSchema_simple = z.object({
6
56
  facts: z
7
57
  .array(z.string())
8
58
  .describe("An array of distinct facts extracted from the conversation."),
9
59
  });
10
60
 
11
-
12
- //1. **Factual memory** stable facts & preferences about the user
13
- //2. **Todo memory** explicit user tasks to remember
14
- //3. **Assistant preference memory** – how the user wants the AI to behave
15
- //
61
+ /**
62
+ * @deprecated Use DiscussionSynthesisSchema instead
63
+ * Types todo et factual supprimés - seul assistant_preference reste pour compatibilité
64
+ */
16
65
  export const FactRetrievalSchema_extended = z.object({
17
66
  facts: z
18
67
  .array(
19
68
  z.object({
20
69
  fact: z.string().describe("The fact extracted from the conversation."),
21
70
  existing: z.boolean().describe("Whether the fact is already present"),
22
- type: z.enum(["assistant_preference","factual","todo"])
23
- .describe(`The type of the fact.
24
- Use 'assistant_preference' for Assistant behavior preferences (style/language/constraints/commands).
25
- Use 'factual' for stable user facts (identity, preferences, beliefs, work context).
26
- Use 'todo' ONLY if the user explicitly asks to save/keep as a todo (e.g., « garde/enregistre en todo », « ajoute un todo »). Do not infer todos.
27
- `),
71
+ type: z.enum(["assistant_preference"])
72
+ .describe(`The type of the fact. Only 'assistant_preference' is supported.`),
28
73
  })
29
74
  )
30
75
  });
31
76
 
32
77
 
33
- // Define Zod schema for memory update output
78
+ /**
79
+ * @deprecated Memory updates are disabled - use capture() for new memories
80
+ */
34
81
  export const MemoryUpdateSchema = z.object({
35
82
  memory: z
36
83
  .array(
@@ -54,21 +101,14 @@ export const MemoryUpdateSchema = z.object({
54
101
  "The reason why you selected this event.",
55
102
  ),
56
103
  type: z
57
- .enum(["factual", "todo", "assistant_preference"])
58
- .describe("Type of the memory. Use 'assistant_preference' for Assistant behavior preferences, 'factual' for user facts, 'todo' for explicit tasks."),
104
+ .enum(["assistant_preference"])
105
+ .describe("Type of the memory. Only 'assistant_preference' is supported."),
59
106
  }),
60
107
  )
61
108
  .describe(
62
109
  "An array representing the state of memory items after processing new facts.",
63
110
  ),
64
111
  });
65
- /**
66
- * Practical Application:
67
- *
68
- * If the task is "factual" (e.g., "Where do I live?", "What's my job?") → retrieve factual memory.
69
- * If the task is about assistant behavior (e.g., "How should I respond?") → retrieve assistant_preference memory.
70
- * If the task is a user task/reminder (e.g., "Add a reminder to call the bank tomorrow") → retrieve todo memory.
71
- */
72
112
  export const MEMORY_STRING_SYSTEM = `# DIRECTIVES FOR MEMORIES
73
113
  - Information stored in memory is always enclosed within the <memories> tag.
74
114
  - Prioritize the latest user message over memories (the user's current question is authoritative).
@@ -80,10 +120,10 @@ export const MEMORY_STRING_SYSTEM = `# DIRECTIVES FOR MEMORIES
80
120
 
81
121
  export const MEMORY_STRING_PREFIX = "Use these contextual memories to guide your response. Prioritize the user's question. Ignore irrelevant memories."
82
122
 
83
-
84
- // Deprecated: getFactRetrievalMessages_O removed in favor of getFactRetrievalMessages
85
-
86
-
123
+ /**
124
+ * @deprecated Use getDiscussionSynthesisMessages instead
125
+ * Cette fonction est conservée pour compatibilité avec l'ancien système
126
+ */
87
127
  export function getFactRetrievalMessages(
88
128
  parsedMessages: string,
89
129
  customRules: string = "",
@@ -132,6 +172,9 @@ Remember the following:
132
172
  return [systemPrompt, userPrompt];
133
173
  }
134
174
 
175
+ /**
176
+ * @deprecated Memory updates are disabled by config
177
+ */
135
178
  export function getUpdateMemoryMessages(
136
179
  retrievedOldMemory: Array<{ id: string; text: string }>,
137
180
  newRetrievedFacts: any[],
@@ -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
@@ -72,9 +60,8 @@ export interface MemoryTypeConfig {
72
60
  }
73
61
 
74
62
  export interface MemoryScoringConfig {
75
- todo: MemoryTypeConfig;
76
- factual: MemoryTypeConfig;
77
63
  assistant_preference: MemoryTypeConfig;
64
+ discussion: MemoryTypeConfig;
78
65
  default: MemoryTypeConfig; // Fallback if type is missing or unknown
79
66
  }
80
67
 
@@ -96,12 +83,10 @@ export interface MemoryConfig {
96
83
  historyStore?: HistoryStoreConfig;
97
84
  disableHistory?: boolean;
98
85
  historyDbPath?: string;
99
- customPrompt?: string;
100
- graphStore?: GraphStoreConfig;
101
- enableGraph?: boolean;
86
+ capturePrompt?: string; // Prompt par défaut pour la synthèse de discussion
102
87
  }
103
88
 
104
- export type MemoryType = 'todo' | 'factual' | 'assistant_preference';
89
+ export type MemoryType = 'assistant_preference' | 'discussion';
105
90
 
106
91
  export interface MemoryItem {
107
92
  id: string;
@@ -162,19 +147,13 @@ export const MemoryConfigSchema = z.object({
162
147
  dimension: z.number().optional(),
163
148
  client: z.any().optional(),
164
149
  scoring: z.object({
165
- todo: z.object({
166
- alpha: z.number(),
167
- beta: z.number(),
168
- gamma: z.number(),
169
- halfLifeDays: z.number(),
170
- }),
171
- factual: z.object({
150
+ assistant_preference: z.object({
172
151
  alpha: z.number(),
173
152
  beta: z.number(),
174
153
  gamma: z.number(),
175
154
  halfLifeDays: z.number(),
176
155
  }),
177
- assistant_preference: z.object({
156
+ discussion: z.object({
178
157
  alpha: z.number(),
179
158
  beta: z.number(),
180
159
  gamma: z.number(),
@@ -188,6 +167,7 @@ export const MemoryConfigSchema = z.object({
188
167
  }),
189
168
  }).optional(),
190
169
  recencyCleanupThreshold: z.number().min(0).max(1).optional(),
170
+ searchThreshold: z.number().min(0).max(1).optional(),
191
171
  })
192
172
  .passthrough(),
193
173
  }),
@@ -200,25 +180,7 @@ export const MemoryConfigSchema = z.object({
200
180
  }),
201
181
  }),
202
182
  historyDbPath: z.string().optional(),
203
- customPrompt: z.string().optional(),
204
- enableGraph: z.boolean().optional(),
205
- graphStore: z
206
- .object({
207
- provider: z.string(),
208
- config: z.object({
209
- url: z.string(),
210
- username: z.string(),
211
- password: z.string(),
212
- }),
213
- llm: z
214
- .object({
215
- provider: z.string(),
216
- config: z.record(z.string(), z.any()),
217
- })
218
- .optional(),
219
- customPrompt: z.string().optional(),
220
- })
221
- .optional(),
183
+ capturePrompt: z.string().optional(),
222
184
  historyStore: z
223
185
  .object({
224
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",
@@ -25,6 +25,10 @@ jest.mock('sqlite3', () => {
25
25
  });
26
26
 
27
27
 
28
+ /**
29
+ * Tests for LiteVectorStore private methods
30
+ * Updated to use new memory types: assistant_preference and discussion
31
+ */
28
32
  describe('LiteVectorStore Private Methods', () => {
29
33
  let store: LiteVectorStore;
30
34
  const userId = 'test-user';
@@ -98,59 +102,65 @@ describe('LiteVectorStore Private Methods', () => {
98
102
  const veryOldDate = new Date(now.getTime() - 400 * 24 * 60 * 60 * 1000).toISOString();
99
103
  const scoring = DEFAULT_MEMORY_CONFIG.vectorStore.config.scoring!;
100
104
 
101
- it('should prioritize cosine similarity for factual memory (high alpha)', () => {
102
- const payload: MemoryPayload = { memoryId: 'mem-f1', userId: userId, type: 'factual', createdAt: twoDaysAgo };
105
+ it('should prioritize cosine similarity for assistant_preference (high alpha)', () => {
106
+ //
107
+ // assistant_preference: { alpha: 0.60, beta: 0.05, gamma: 0.35, halfLifeDays: Infinity }
108
+ const payload: MemoryPayload = { memoryId: 'mem-ap1', userId: userId, type: 'assistant_preference', createdAt: twoDaysAgo };
103
109
  const cosineScore = 0.9;
104
- const expectedRecency = (store as any).calculateRecencyScore(twoDaysAgo, scoring.factual.halfLifeDays);
105
- const expectedScore = scoring.factual.alpha * cosineScore + scoring.factual.beta * expectedRecency + scoring.factual.gamma;
110
+ const expectedRecency = 1.0; // Infinity halfLife → recency = 1
111
+ const expectedScore = scoring.assistant_preference.alpha * cosineScore + scoring.assistant_preference.beta * expectedRecency + scoring.assistant_preference.gamma;
106
112
  const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
107
113
  expect(hybridScore).toBeCloseTo(expectedScore, 5);
108
- expect(scoring.factual.alpha * cosineScore).toBeGreaterThan(scoring.factual.beta * expectedRecency);
114
+ // alpha * cosine (0.54) > beta * recency (0.05)
115
+ expect(scoring.assistant_preference.alpha * cosineScore).toBeGreaterThan(scoring.assistant_preference.beta * expectedRecency);
109
116
  });
110
117
 
111
- it('should prioritize recency for episodic memory (high beta, short half-life)', () => {
112
- const payload: MemoryPayload = { memoryId: 'mem-e1', userId: userId, type: 'episodic', createdAt: twoDaysAgo };
118
+ it('should return constant score for discussion (beta=1, alpha=0, gamma=0)', () => {
119
+ //
120
+ // discussion: { alpha: 0, beta: 1, gamma: 0, halfLifeDays: Infinity }
121
+ // Score = 0 * cosine + 1 * recency + 0 = 1 (constant)
122
+ const payload: MemoryPayload = { memoryId: 'mem-d1', userId: userId, type: 'discussion', createdAt: twoDaysAgo };
113
123
  const cosineScore = 0.5;
114
- const expectedRecency = (store as any).calculateRecencyScore(twoDaysAgo, scoring.episodic.halfLifeDays);
115
- const expectedScore = scoring.episodic.alpha * cosineScore + scoring.episodic.beta * expectedRecency + scoring.episodic.gamma;
124
+ const expectedRecency = 1.0; // Infinity halfLife → recency = 1
125
+ const expectedScore = scoring.discussion.alpha * cosineScore + scoring.discussion.beta * expectedRecency + scoring.discussion.gamma;
116
126
  const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
117
127
  expect(hybridScore).toBeCloseTo(expectedScore, 5);
118
- expect(scoring.episodic.beta * expectedRecency).toBeGreaterThan(0.2);
128
+ expect(hybridScore).toBe(1); // Score constant = 1
119
129
  });
120
130
 
121
- it('should have low score for old episodic memory', () => {
122
- const payload: MemoryPayload = { memoryId: 'mem-e2', userId: userId, type: 'episodic', createdAt: veryOldDate };
123
- const cosineScore = 0.9;
124
- const expectedRecency = (store as any).calculateRecencyScore(veryOldDate, scoring.episodic.halfLifeDays);
125
- const expectedScore = scoring.episodic.alpha * cosineScore + scoring.episodic.beta * expectedRecency + scoring.episodic.gamma;
126
- const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
127
- expect(expectedRecency).toBeLessThan(0.01);
128
- expect(hybridScore).toBeCloseTo(scoring.episodic.alpha * cosineScore + scoring.episodic.gamma, 5);
131
+ it('should have constant score for old discussion memory (no decay)', () => {
132
+ //
133
+ // discussion with Infinity halfLife → old memories have same score as new
134
+ const payload: MemoryPayload = { memoryId: 'mem-d2', userId: userId, type: 'discussion', createdAt: veryOldDate };
135
+ const cosineScore = 0.9;
136
+ const expectedRecency = 1.0; // Infinity halfLife → recency = 1 even for old dates
137
+ const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
138
+ expect(hybridScore).toBe(1); // Score constant = 1
129
139
  });
130
140
 
131
141
  it('should handle assistant_preference with no decay (Infinity half-life)', () => {
132
- const payload: MemoryPayload = { memoryId: 'mem-a1', userId: userId, type: 'assistant_preference', createdAt: veryOldDate };
133
- const cosineScore = 0.7;
134
- const expectedRecency = 1.0;
135
- const expectedScore = scoring.assistant_preference.alpha * cosineScore + scoring.assistant_preference.beta * expectedRecency + scoring.assistant_preference.gamma;
136
- const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
137
- expect(hybridScore).toBeCloseTo(expectedScore, 5);
142
+ const payload: MemoryPayload = { memoryId: 'mem-a1', userId: userId, type: 'assistant_preference', createdAt: veryOldDate };
143
+ const cosineScore = 0.7;
144
+ const expectedRecency = 1.0;
145
+ const expectedScore = scoring.assistant_preference.alpha * cosineScore + scoring.assistant_preference.beta * expectedRecency + scoring.assistant_preference.gamma;
146
+ const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
147
+ expect(hybridScore).toBeCloseTo(expectedScore, 5);
138
148
  });
139
149
 
140
150
  it('should use default scoring if type is missing', () => {
141
- const payload: MemoryPayload = { memoryId: 'mem-d1', userId: userId, createdAt: sevenDaysAgo }; // No type specified
142
- const cosineScore = 0.8;
143
- const expectedRecency = (store as any).calculateRecencyScore(sevenDaysAgo, scoring.default.halfLifeDays);
144
- const expectedScore = scoring.default.alpha * cosineScore + scoring.default.beta * expectedRecency + scoring.default.gamma;
145
- const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
146
- expect(hybridScore).toBeCloseTo(expectedScore, 5);
151
+ const payload: MemoryPayload = { memoryId: 'mem-def1', userId: userId, createdAt: sevenDaysAgo }; // No type specified
152
+ const cosineScore = 0.8;
153
+ const expectedRecency = (store as any).calculateRecencyScore(sevenDaysAgo, scoring.default.halfLifeDays);
154
+ const expectedScore = scoring.default.alpha * cosineScore + scoring.default.beta * expectedRecency + scoring.default.gamma;
155
+ const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
156
+ expect(hybridScore).toBeCloseTo(expectedScore, 5);
147
157
  });
148
158
 
149
159
  it('should return score >= 0 even with negative cosine similarity', () => {
150
- const payload: MemoryPayload = { memoryId: 'mem-s1', userId: userId, type: 'factual', createdAt: twoDaysAgo };
151
- const cosineScore = -0.5;
152
- const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
153
- expect(hybridScore).toBeGreaterThanOrEqual(0);
160
+ const payload: MemoryPayload = { memoryId: 'mem-neg1', userId: userId, type: 'assistant_preference', createdAt: twoDaysAgo };
161
+ const cosineScore = -0.5;
162
+ const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
163
+ expect(hybridScore).toBeGreaterThanOrEqual(0);
154
164
  });
155
165
 
156
166
  });