memories-lite 0.9.5 → 0.99.1
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 -8
- package/dist/config/manager.js +2 -8
- package/dist/memory/index.d.ts +11 -0
- package/dist/memory/index.js +62 -123
- package/dist/memory/memory.types.d.ts +1 -2
- package/dist/prompts/index.d.ts +53 -18
- package/dist/prompts/index.js +133 -84
- package/dist/types/index.d.ts +28 -355
- package/dist/types/index.js +2 -20
- 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 -8
- package/src/config/manager.ts +2 -8
- package/src/memory/index.ts +76 -157
- package/src/memory/memory.types.ts +1 -2
- package/src/prompts/index.ts +138 -87
- package/src/types/index.ts +4 -25
- package/tests/lite.spec.ts +44 -34
- package/tests/memory.discussion.test.ts +279 -0
- package/tests/memory.update.test.ts +5 -1
- package/tests/memory.facts.test.ts +0 -168
- package/tests/memory.todo.test.ts +0 -127
package/src/types/index.ts
CHANGED
|
@@ -72,11 +72,8 @@ export interface MemoryTypeConfig {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export interface MemoryScoringConfig {
|
|
75
|
-
todo: MemoryTypeConfig;
|
|
76
|
-
procedural: MemoryTypeConfig;
|
|
77
|
-
episodic: MemoryTypeConfig;
|
|
78
|
-
factual: MemoryTypeConfig;
|
|
79
75
|
assistant_preference: MemoryTypeConfig;
|
|
76
|
+
discussion: MemoryTypeConfig;
|
|
80
77
|
default: MemoryTypeConfig; // Fallback if type is missing or unknown
|
|
81
78
|
}
|
|
82
79
|
|
|
@@ -103,7 +100,7 @@ export interface MemoryConfig {
|
|
|
103
100
|
enableGraph?: boolean;
|
|
104
101
|
}
|
|
105
102
|
|
|
106
|
-
export type MemoryType = '
|
|
103
|
+
export type MemoryType = 'assistant_preference' | 'discussion';
|
|
107
104
|
|
|
108
105
|
export interface MemoryItem {
|
|
109
106
|
id: string;
|
|
@@ -164,31 +161,13 @@ export const MemoryConfigSchema = z.object({
|
|
|
164
161
|
dimension: z.number().optional(),
|
|
165
162
|
client: z.any().optional(),
|
|
166
163
|
scoring: z.object({
|
|
167
|
-
|
|
168
|
-
alpha: z.number(),
|
|
169
|
-
beta: z.number(),
|
|
170
|
-
gamma: z.number(),
|
|
171
|
-
halfLifeDays: z.number(),
|
|
172
|
-
}),
|
|
173
|
-
procedural: z.object({
|
|
174
|
-
alpha: z.number(),
|
|
175
|
-
beta: z.number(),
|
|
176
|
-
gamma: z.number(),
|
|
177
|
-
halfLifeDays: z.number(),
|
|
178
|
-
}),
|
|
179
|
-
episodic: z.object({
|
|
180
|
-
alpha: z.number(),
|
|
181
|
-
beta: z.number(),
|
|
182
|
-
gamma: z.number(),
|
|
183
|
-
halfLifeDays: z.number(),
|
|
184
|
-
}),
|
|
185
|
-
factual: z.object({
|
|
164
|
+
assistant_preference: z.object({
|
|
186
165
|
alpha: z.number(),
|
|
187
166
|
beta: z.number(),
|
|
188
167
|
gamma: z.number(),
|
|
189
168
|
halfLifeDays: z.number(),
|
|
190
169
|
}),
|
|
191
|
-
|
|
170
|
+
discussion: z.object({
|
|
192
171
|
alpha: z.number(),
|
|
193
172
|
beta: z.number(),
|
|
194
173
|
gamma: z.number(),
|
package/tests/lite.spec.ts
CHANGED
|
@@ -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
|
|
102
|
-
|
|
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 =
|
|
105
|
-
const expectedScore = scoring.
|
|
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
|
-
|
|
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
|
|
112
|
-
|
|
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 =
|
|
115
|
-
const expectedScore = scoring.
|
|
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(
|
|
128
|
+
expect(hybridScore).toBe(1); // Score constant = 1
|
|
119
129
|
});
|
|
120
130
|
|
|
121
|
-
it('should have
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
});
|
|
@@ -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
|
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/// <reference types="jest" />
|
|
2
|
-
import { MemoriesLite } from "../src";
|
|
3
|
-
import { MemoryItem, SearchResult } from "../src/types";
|
|
4
|
-
import dotenv from "dotenv";
|
|
5
|
-
import { createTestMemory } from "./init.mem";
|
|
6
|
-
|
|
7
|
-
dotenv.config();
|
|
8
|
-
|
|
9
|
-
jest.setTimeout(30000); // Increase timeout to 30 seconds
|
|
10
|
-
|
|
11
|
-
describe("Memory Class facts regression tests", () => {
|
|
12
|
-
let memory: MemoriesLite;
|
|
13
|
-
let userId: string;
|
|
14
|
-
|
|
15
|
-
beforeEach(async () => {
|
|
16
|
-
// Initialize memory via helper
|
|
17
|
-
({ memory, userId } = createTestMemory({customPrompt:"L'utilisateur travail pour une régie immobilière!"}));
|
|
18
|
-
// Reset all memories before each test
|
|
19
|
-
await memory.reset(userId);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(async () => {
|
|
23
|
-
// Clean up after each test
|
|
24
|
-
await memory.reset(userId);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe("Edge cases for Facts", () => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
it("should not add memory: Qui suis-je ?", async () => {
|
|
31
|
-
const customFacts = "Je suis Olivier Poulain\nIT et je travaille chez Immeuble SA";
|
|
32
|
-
const result = (await memory.capture([
|
|
33
|
-
{role:"user", content:"Qui suis-je ?"},
|
|
34
|
-
{role:"assistant", content:"Vous êtes Olivier Poulain, Chef de Projets au département IT & Gestion de projet, dans l'équipe IT de Immeuble SA"}],
|
|
35
|
-
userId,
|
|
36
|
-
{customFacts},
|
|
37
|
-
)) as SearchResult;
|
|
38
|
-
expect(result).toBeDefined();
|
|
39
|
-
expect(result.results).toBeDefined();
|
|
40
|
-
expect(result.results.length).toBe(0);
|
|
41
|
-
// expect(result.results[0]?.type).toBe("factual");
|
|
42
|
-
});
|
|
43
|
-
it("episodic: Je veux manger des sushis pour ma pause de midi.", async () => {
|
|
44
|
-
const customFacts = "Je suis Olivier Poulain\nIT et je travaille chez Immeuble SA";
|
|
45
|
-
const result = (await memory.capture([
|
|
46
|
-
{role:"user", content:"J'ai faim, je veux manger des sushis pour ma pause de midi."},
|
|
47
|
-
{role:"user", content:"Cherche un restaurant de sushis près de chez moi."}],
|
|
48
|
-
userId,
|
|
49
|
-
{customFacts},
|
|
50
|
-
)) as SearchResult;
|
|
51
|
-
|
|
52
|
-
expect(result).toBeDefined();
|
|
53
|
-
expect(result.results).toBeDefined();
|
|
54
|
-
expect(result.results.length).toBeGreaterThan(1);
|
|
55
|
-
expect(result.results[0]?.type).toBe("episodic");
|
|
56
|
-
expect(result.results[1]?.type).toBe("episodic");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
it("should add assistant_preference memory", async () => {
|
|
61
|
-
const result = (await memory.capture(
|
|
62
|
-
"tu dois répondre de manière concise et précise",
|
|
63
|
-
userId,
|
|
64
|
-
{},
|
|
65
|
-
)) as SearchResult;
|
|
66
|
-
|
|
67
|
-
expect(result).toBeDefined();
|
|
68
|
-
expect(result.results).toBeDefined();
|
|
69
|
-
expect(Array.isArray(result.results)).toBe(true);
|
|
70
|
-
expect(result.results.length).toBeGreaterThan(0);
|
|
71
|
-
expect(result.results[0]?.id).toBeDefined();
|
|
72
|
-
expect(result.results[0]?.type).toBe("assistant_preference");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("business:je cherche le téléphone de mon client Alphonse MAGLOIRE", async () => {
|
|
76
|
-
// type?: "factual" | "episodic" | "todo"|"procedural" | "assistant_preference";
|
|
77
|
-
// Capture a query that contains a name but is asking for contact information
|
|
78
|
-
const result = (await memory.capture(
|
|
79
|
-
"je cherche le téléphone de mon client Alphonse MAGLOIRE",
|
|
80
|
-
userId,
|
|
81
|
-
{},
|
|
82
|
-
)) as SearchResult;
|
|
83
|
-
|
|
84
|
-
// Verify no memory was created (business query)
|
|
85
|
-
expect(result).toBeDefined();
|
|
86
|
-
expect(result.results).toBeDefined();
|
|
87
|
-
expect(Array.isArray(result.results)).toBe(true);
|
|
88
|
-
expect(result.results.length).toBe(1);
|
|
89
|
-
const type = result.results[0]?.type;
|
|
90
|
-
expect(["procedural","episodic"].includes(type)).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("business:Le logement de Alphonse MAGLOIRE au 5ème étage est de combien pièces", async () => {
|
|
94
|
-
const result = (await memory.capture([
|
|
95
|
-
{role:"user", content:"Le logement de Alphonse MAGLOIRE au 5ème étage est de combien pièces.",},
|
|
96
|
-
{role:"assitant", content:"Alphonse MAGLOIRE a un logement de 4 pièces au 5ème étage",}],
|
|
97
|
-
userId,
|
|
98
|
-
{customFacts:"Je suis Olivier Poulain, Je m'occupe de la gérance locataire chez Immeuble SA"},
|
|
99
|
-
)) as SearchResult;
|
|
100
|
-
expect(result).toBeDefined();
|
|
101
|
-
expect(result.results).toBeDefined();
|
|
102
|
-
expect(Array.isArray(result.results)).toBe(true);
|
|
103
|
-
//
|
|
104
|
-
// result can also be empty
|
|
105
|
-
if(result.results.length > 0) {
|
|
106
|
-
expect(result.results.length).toBe(1);
|
|
107
|
-
expect(result.results[0]?.type).toBe("procedural");
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
it("business task are not factual (2)", async () => {
|
|
113
|
-
const result = (await memory.capture([
|
|
114
|
-
{role:"user", content:"Quelle est la procédure pour résilier un bail chez Pouet & Compagnie SA ?"}],
|
|
115
|
-
userId,
|
|
116
|
-
{customFacts:"Je suis Olivier Poulain, Je m'occupe de la gérance locataire chez Immeuble SA"},
|
|
117
|
-
)) as SearchResult;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
expect(result).toBeDefined();
|
|
121
|
-
expect(result.results).toBeDefined();
|
|
122
|
-
expect(Array.isArray(result.results)).toBe(true);
|
|
123
|
-
//
|
|
124
|
-
// result can also be empty
|
|
125
|
-
if(result.results.length > 0) {
|
|
126
|
-
expect(result.results.length).toBe(1);
|
|
127
|
-
expect(result.results[0]?.type).toBe("procedural");
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("business:Est-ce que Claude RIBUR est à jour avec son loyer ?", async () => {
|
|
132
|
-
const result = (await memory.capture([
|
|
133
|
-
{role:"user", content:"Est-ce que Claude RIBUR est à jour avec son loyer ?"}],
|
|
134
|
-
userId,
|
|
135
|
-
{customFacts:"Je suis Olivier Poulain, Je m'occupe de la gérance locataire chez Immeuble SA"},
|
|
136
|
-
)) as SearchResult;
|
|
137
|
-
expect(result).toBeDefined();
|
|
138
|
-
expect(result.results).toBeDefined();
|
|
139
|
-
expect(Array.isArray(result.results)).toBe(true);
|
|
140
|
-
expect(result.results.length).toBe(1);
|
|
141
|
-
expect(result.results[0]?.type).toBe("procedural");
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
it("should add and remove a single memory", async () => {
|
|
148
|
-
const result = (await memory.capture(
|
|
149
|
-
"je veux que tu répondes en italien",
|
|
150
|
-
userId,
|
|
151
|
-
{},
|
|
152
|
-
)) as SearchResult;
|
|
153
|
-
expect(result.results?.[0]?.id).toBeDefined();
|
|
154
|
-
const memoryId = result.results[0]?.id;
|
|
155
|
-
await memory.capture(
|
|
156
|
-
"je ne veux plus que tu répondes en italien",
|
|
157
|
-
userId,
|
|
158
|
-
{},
|
|
159
|
-
) as SearchResult;
|
|
160
|
-
|
|
161
|
-
const id = await memory.get(memoryId, userId);
|
|
162
|
-
expect(id).toBeNull();
|
|
163
|
-
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
});
|
|
168
|
-
});
|