memories-lite 0.9.0
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/MEMORIES.md +39 -0
- package/README.md +221 -0
- package/TECHNICAL.md +135 -0
- package/dist/config/defaults.d.ts +2 -0
- package/dist/config/defaults.js +61 -0
- package/dist/config/manager.d.ts +4 -0
- package/dist/config/manager.js +121 -0
- package/dist/embeddings/base.d.ts +4 -0
- package/dist/embeddings/base.js +2 -0
- package/dist/embeddings/google.d.ts +10 -0
- package/dist/embeddings/google.js +28 -0
- package/dist/embeddings/openai.d.ts +10 -0
- package/dist/embeddings/openai.js +31 -0
- package/dist/graphs/configs.d.ts +14 -0
- package/dist/graphs/configs.js +19 -0
- package/dist/graphs/tools.d.ts +271 -0
- package/dist/graphs/tools.js +220 -0
- package/dist/graphs/utils.d.ts +9 -0
- package/dist/graphs/utils.js +105 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +30 -0
- package/dist/llms/base.d.ts +16 -0
- package/dist/llms/base.js +2 -0
- package/dist/llms/google.d.ts +11 -0
- package/dist/llms/google.js +44 -0
- package/dist/llms/openai.d.ts +9 -0
- package/dist/llms/openai.js +73 -0
- package/dist/llms/openai_structured.d.ts +11 -0
- package/dist/llms/openai_structured.js +72 -0
- package/dist/memory/index.d.ts +42 -0
- package/dist/memory/index.js +499 -0
- package/dist/memory/memory.types.d.ts +23 -0
- package/dist/memory/memory.types.js +2 -0
- package/dist/prompts/index.d.ts +102 -0
- package/dist/prompts/index.js +233 -0
- package/dist/storage/DummyHistoryManager.d.ts +7 -0
- package/dist/storage/DummyHistoryManager.js +19 -0
- package/dist/storage/MemoryHistoryManager.d.ts +8 -0
- package/dist/storage/MemoryHistoryManager.js +36 -0
- package/dist/storage/base.d.ts +6 -0
- package/dist/storage/base.js +2 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.js +19 -0
- package/dist/types/index.d.ts +1071 -0
- package/dist/types/index.js +100 -0
- package/dist/utils/bm25.d.ts +13 -0
- package/dist/utils/bm25.js +51 -0
- package/dist/utils/factory.d.ts +13 -0
- package/dist/utils/factory.js +49 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.js +9 -0
- package/dist/utils/memory.d.ts +3 -0
- package/dist/utils/memory.js +44 -0
- package/dist/utils/telemetry.d.ts +11 -0
- package/dist/utils/telemetry.js +74 -0
- package/dist/utils/telemetry.types.d.ts +27 -0
- package/dist/utils/telemetry.types.js +2 -0
- package/dist/vectorstores/base.d.ts +11 -0
- package/dist/vectorstores/base.js +2 -0
- package/dist/vectorstores/lite.d.ts +40 -0
- package/dist/vectorstores/lite.js +319 -0
- package/dist/vectorstores/llm.d.ts +31 -0
- package/dist/vectorstores/llm.js +88 -0
- package/jest.config.js +22 -0
- package/memories-lite.db +0 -0
- package/package.json +38 -0
- package/src/config/defaults.ts +61 -0
- package/src/config/manager.ts +132 -0
- package/src/embeddings/base.ts +4 -0
- package/src/embeddings/google.ts +32 -0
- package/src/embeddings/openai.ts +33 -0
- package/src/graphs/configs.ts +30 -0
- package/src/graphs/tools.ts +267 -0
- package/src/graphs/utils.ts +114 -0
- package/src/index.ts +14 -0
- package/src/llms/base.ts +20 -0
- package/src/llms/google.ts +56 -0
- package/src/llms/openai.ts +85 -0
- package/src/llms/openai_structured.ts +82 -0
- package/src/memory/index.ts +723 -0
- package/src/memory/memory.types.ts +27 -0
- package/src/prompts/index.ts +268 -0
- package/src/storage/DummyHistoryManager.ts +27 -0
- package/src/storage/MemoryHistoryManager.ts +58 -0
- package/src/storage/base.ts +14 -0
- package/src/storage/index.ts +3 -0
- package/src/types/index.ts +243 -0
- package/src/utils/bm25.ts +64 -0
- package/src/utils/factory.ts +59 -0
- package/src/utils/logger.ts +13 -0
- package/src/utils/memory.ts +48 -0
- package/src/utils/telemetry.ts +98 -0
- package/src/utils/telemetry.types.ts +34 -0
- package/src/vectorstores/base.ts +27 -0
- package/src/vectorstores/lite.ts +402 -0
- package/src/vectorstores/llm.ts +126 -0
- package/tests/lite.spec.ts +158 -0
- package/tests/memory.facts.test.ts +211 -0
- package/tests/memory.test.ts +406 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
import { MemoriesLite } from "../src";
|
|
3
|
+
import { MemoryItem, SearchResult } from "../src/types";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
jest.setTimeout(30000); // Increase timeout to 30 seconds
|
|
9
|
+
|
|
10
|
+
describe("Memory Class facts regression tests", () => {
|
|
11
|
+
let memory: MemoriesLite;
|
|
12
|
+
const userId =
|
|
13
|
+
Math.random().toString(36).substring(2, 15) +
|
|
14
|
+
Math.random().toString(36).substring(2, 15);
|
|
15
|
+
|
|
16
|
+
const dimension = 768;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
// Initialize with default configuration
|
|
20
|
+
memory = new MemoriesLite({
|
|
21
|
+
version: "v1.1",
|
|
22
|
+
disableHistory: true,
|
|
23
|
+
customPrompt: "L'utilisateur travail pour une régie immobilière!",
|
|
24
|
+
embedder: {
|
|
25
|
+
provider: "openai",
|
|
26
|
+
config: {
|
|
27
|
+
dimension,
|
|
28
|
+
apiKey: process.env.OPENAI_API_KEY || "",
|
|
29
|
+
model: "text-embedding-3-small",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
vectorStore: {
|
|
33
|
+
provider: "lite",
|
|
34
|
+
config: {
|
|
35
|
+
dimension,
|
|
36
|
+
rootPath: ":memory:",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
llm: {
|
|
40
|
+
provider: "openai",
|
|
41
|
+
config: {
|
|
42
|
+
apiKey: process.env.OPENAI_API_KEY || "",
|
|
43
|
+
model: "gpt-4.1-mini",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
historyDbPath: ":memory:", // Use in-memory SQLite for tests
|
|
47
|
+
});
|
|
48
|
+
// Reset all memories before each test
|
|
49
|
+
await memory.reset(userId);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(async () => {
|
|
53
|
+
// Clean up after each test
|
|
54
|
+
await memory.reset(userId);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("Edge cases for Facts", () => {
|
|
58
|
+
|
|
59
|
+
it("should not extract personal information as facts from business queries", async () => {
|
|
60
|
+
// type?: "factual" | "episodic" | "semantic"|"procedural" | "assistant_preference";
|
|
61
|
+
// Capture a query that contains a name but is asking for contact information
|
|
62
|
+
const result = (await memory.capture(
|
|
63
|
+
"je cherche le téléphone de mon client Alphonse MAGLOIRE",
|
|
64
|
+
userId,
|
|
65
|
+
{},
|
|
66
|
+
)) as SearchResult;
|
|
67
|
+
|
|
68
|
+
// Verify no memory was created (business query)
|
|
69
|
+
expect(result).toBeDefined();
|
|
70
|
+
expect(result.results).toBeDefined();
|
|
71
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
72
|
+
expect(result.results.length).toBe(1);
|
|
73
|
+
const type = result.results[0]?.type;
|
|
74
|
+
expect(["procedural","episodic"].includes(type)).toBe(true);
|
|
75
|
+
// Now search for memories that might contain "Alphonse MAGLOIRE"
|
|
76
|
+
// const searchResult = (await memory.retrieve(
|
|
77
|
+
// "Qui est Alphonse MAGLOIRE?",
|
|
78
|
+
// userId,
|
|
79
|
+
// {},
|
|
80
|
+
// )) as SearchResult;
|
|
81
|
+
|
|
82
|
+
// // Verify no personal fact like "Je m'appelle Alphonse MAGLOIRE" was created
|
|
83
|
+
// expect(searchResult).toBeDefined();
|
|
84
|
+
// expect(searchResult.results).toBeDefined();
|
|
85
|
+
// expect(Array.isArray(searchResult.results)).toBe(true);
|
|
86
|
+
// expect(searchResult.results.length).toBe(0);
|
|
87
|
+
|
|
88
|
+
// // Ensure no memory contains the name as a personal fact
|
|
89
|
+
// const allMemories = await memory.getAll(userId, {});
|
|
90
|
+
// const personalFacts = allMemories.results.filter(mem =>
|
|
91
|
+
// mem.memory.toLowerCase().includes("Alphonse MAGLOIRE")
|
|
92
|
+
// );
|
|
93
|
+
// expect(personalFacts.length).toBe(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should add a single procedural memory", async () => {
|
|
97
|
+
const customFacts = "Je suis Olivier Poulain\nIT chez Immeuble SA";
|
|
98
|
+
const result = (await memory.capture([
|
|
99
|
+
{role:"user", content:"Qui suis-je ?"},
|
|
100
|
+
{role:"assitant", content:"Vous êtes Olivier Poulain, Chef de Projets au département IT & Gestion de projet, dans l'équipe IT de Immeuble SA. Ces informations proviennent de votre profil collaborateur enregistré dans le système interne de Immeuble SA"}],
|
|
101
|
+
userId,
|
|
102
|
+
{customFacts},
|
|
103
|
+
)) as SearchResult;
|
|
104
|
+
expect(result).toBeDefined();
|
|
105
|
+
expect(result.results).toBeDefined();
|
|
106
|
+
expect(result.results.length).toBe(1);
|
|
107
|
+
expect(result.results[0]?.type).toBe("factual");
|
|
108
|
+
});
|
|
109
|
+
it("should add a single episodic memory", async () => {
|
|
110
|
+
const result = (await memory.capture([
|
|
111
|
+
{role:"user", content:"je veux trouver un bon film pour ce soir au cinéma avec ma copine,"}],
|
|
112
|
+
userId,
|
|
113
|
+
{},
|
|
114
|
+
)) as SearchResult;
|
|
115
|
+
|
|
116
|
+
expect(result).toBeDefined();
|
|
117
|
+
expect(result.results).toBeDefined();
|
|
118
|
+
expect(result.results.length).toBeGreaterThan(0);
|
|
119
|
+
expect(result.results[0]?.type).toBe("episodic");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should add assistant_preference memory", async () => {
|
|
123
|
+
const result = (await memory.capture(
|
|
124
|
+
"tu dois répondre de manière concise et précise",
|
|
125
|
+
userId,
|
|
126
|
+
{},
|
|
127
|
+
)) as SearchResult;
|
|
128
|
+
|
|
129
|
+
expect(result).toBeDefined();
|
|
130
|
+
expect(result.results).toBeDefined();
|
|
131
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
132
|
+
expect(result.results.length).toBeGreaterThan(0);
|
|
133
|
+
expect(result.results[0]?.id).toBeDefined();
|
|
134
|
+
expect(result.results[0]?.type).toBe("assistant_preference");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("business facts are not factual (1)", async () => {
|
|
138
|
+
const result = (await memory.capture([
|
|
139
|
+
{role:"user", content:"Je sais que Alphonse MAGLOIRE a un logement de 4 pièces au 5ème étage",}],
|
|
140
|
+
userId,
|
|
141
|
+
{customFacts:"Je suis Olivier Poulain, Je m'occupe de la gérance locataire chez Immeuble SA"},
|
|
142
|
+
)) as SearchResult;
|
|
143
|
+
expect(result).toBeDefined();
|
|
144
|
+
expect(result.results).toBeDefined();
|
|
145
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
146
|
+
//
|
|
147
|
+
// result can also be empty
|
|
148
|
+
if(result.results.length > 0) {
|
|
149
|
+
expect(result.results.length).toBe(1);
|
|
150
|
+
expect(result.results[0]?.type).toBe("procedural");
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
it("business task are not factual (2)", async () => {
|
|
156
|
+
const result = (await memory.capture([
|
|
157
|
+
{role:"user", content:"Quelle est la procédure pour résilier un bail chez Pilet-Renaud SA ?"}],
|
|
158
|
+
userId,
|
|
159
|
+
{customFacts:"Je suis Olivier Poulain, Je m'occupe de la gérance locataire chez Immeuble SA"},
|
|
160
|
+
)) as SearchResult;
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
expect(result).toBeDefined();
|
|
164
|
+
expect(result.results).toBeDefined();
|
|
165
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
166
|
+
//
|
|
167
|
+
// result can also be empty
|
|
168
|
+
if(result.results.length > 0) {
|
|
169
|
+
expect(result.results.length).toBe(1);
|
|
170
|
+
expect(result.results[0]?.type).toBe("procedural");
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should not add business queries (3)", async () => {
|
|
175
|
+
const result = (await memory.capture([
|
|
176
|
+
{role:"user", content:"Est-ce que Jean BURRI est à jour avec son loyer ?"}],
|
|
177
|
+
userId,
|
|
178
|
+
{customFacts:"Je suis Olivier Poulain, Je m'occupe de la gérance locataire chez Immeuble SA"},
|
|
179
|
+
)) as SearchResult;
|
|
180
|
+
expect(result).toBeDefined();
|
|
181
|
+
expect(result.results).toBeDefined();
|
|
182
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
183
|
+
expect(result.results.length).toBe(1);
|
|
184
|
+
expect(result.results[0]?.type).toBe("procedural");
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
it("should add and remove a single memory", async () => {
|
|
191
|
+
const result = (await memory.capture(
|
|
192
|
+
"je veux que tu répondes en italien",
|
|
193
|
+
userId,
|
|
194
|
+
{},
|
|
195
|
+
)) as SearchResult;
|
|
196
|
+
expect(result.results?.[0]?.id).toBeDefined();
|
|
197
|
+
const memoryId = result.results[0]?.id;
|
|
198
|
+
await memory.capture(
|
|
199
|
+
"je ne veux plus que tu répondes en italien",
|
|
200
|
+
userId,
|
|
201
|
+
{},
|
|
202
|
+
) as SearchResult;
|
|
203
|
+
|
|
204
|
+
const id = await memory.get(memoryId, userId);
|
|
205
|
+
expect(id).toBeNull();
|
|
206
|
+
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
import { MemoriesLite } from "../src";
|
|
3
|
+
import { MemoryItem, SearchResult } from "../src/types";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
jest.setTimeout(30000); // Increase timeout to 30 seconds
|
|
9
|
+
|
|
10
|
+
describe("Memory Class", () => {
|
|
11
|
+
let memory: MemoriesLite;
|
|
12
|
+
const userId =
|
|
13
|
+
Math.random().toString(36).substring(2, 15) +
|
|
14
|
+
Math.random().toString(36).substring(2, 15);
|
|
15
|
+
|
|
16
|
+
const dimension = 768;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
// Initialize with default configuration
|
|
20
|
+
memory = new MemoriesLite({
|
|
21
|
+
version: "v1.1",
|
|
22
|
+
disableHistory: true,
|
|
23
|
+
embedder: {
|
|
24
|
+
provider: "openai",
|
|
25
|
+
config: {
|
|
26
|
+
dimension,
|
|
27
|
+
apiKey: process.env.OPENAI_API_KEY || "",
|
|
28
|
+
model: "text-embedding-3-small",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
vectorStore: {
|
|
32
|
+
provider: "lite",
|
|
33
|
+
config: {
|
|
34
|
+
dimension,
|
|
35
|
+
rootPath: ":memory:",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
llm: {
|
|
39
|
+
provider: "openai",
|
|
40
|
+
config: {
|
|
41
|
+
apiKey: process.env.OPENAI_API_KEY || "",
|
|
42
|
+
model: "gpt-4.1-mini",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
historyDbPath: ":memory:", // Use in-memory SQLite for tests
|
|
46
|
+
});
|
|
47
|
+
// Reset all memories before each test
|
|
48
|
+
await memory.reset(userId);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(async () => {
|
|
52
|
+
// Clean up after each test
|
|
53
|
+
await memory.reset(userId);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("Basic Memory Operations", () => {
|
|
57
|
+
it("should add a single memory", async () => {
|
|
58
|
+
const result = (await memory.capture(
|
|
59
|
+
"Bonjour, je m'appelle Olivier et je suis boulanger.",
|
|
60
|
+
userId,
|
|
61
|
+
{},
|
|
62
|
+
)) as SearchResult;
|
|
63
|
+
|
|
64
|
+
expect(result).toBeDefined();
|
|
65
|
+
expect(result.results).toBeDefined();
|
|
66
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
67
|
+
expect(result.results.length).toBeGreaterThan(0);
|
|
68
|
+
expect(result.results[0]?.id).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should add multiple messages", async () => {
|
|
72
|
+
const messages = [
|
|
73
|
+
{ role: "user", content: "What is your favorite city?" },
|
|
74
|
+
{ role: "assistant", content: "I love Paris, it is my favorite city." },
|
|
75
|
+
{ role: "user", content: "I prefer Geneva." },
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const result = (await memory.capture(messages, userId, {})) as SearchResult;
|
|
79
|
+
|
|
80
|
+
expect(result).toBeDefined();
|
|
81
|
+
expect(result.results).toBeDefined();
|
|
82
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
83
|
+
expect(result.results.length).toBeGreaterThan(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should get a single memory", async () => {
|
|
87
|
+
// First add a memory
|
|
88
|
+
const addResult = (await memory.capture(
|
|
89
|
+
"I am a big advocate of using AI to make the world a better place",
|
|
90
|
+
userId,
|
|
91
|
+
{},
|
|
92
|
+
)) as SearchResult;
|
|
93
|
+
|
|
94
|
+
expect(addResult.results?.[0]?.id).toBeDefined();
|
|
95
|
+
const memoryId = addResult.results[0].id;
|
|
96
|
+
const result = (await memory.get(memoryId, userId)) as MemoryItem;
|
|
97
|
+
|
|
98
|
+
expect(result).toBeDefined();
|
|
99
|
+
expect(result.id).toBe(memoryId);
|
|
100
|
+
expect(result.memory).toBeDefined();
|
|
101
|
+
expect(typeof result.memory).toBe("string");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should update a memory", async () => {
|
|
105
|
+
// First add a memory
|
|
106
|
+
const addResult = (await memory.capture(
|
|
107
|
+
"I love speaking foreign languages especially Spanish",
|
|
108
|
+
userId,
|
|
109
|
+
{},
|
|
110
|
+
)) as SearchResult;
|
|
111
|
+
expect(addResult.results?.[0]?.id).toBeDefined();
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
const memoryId = addResult.results[0].id;
|
|
115
|
+
const updatedContent = "Updated content";
|
|
116
|
+
const result = await memory.update(memoryId, updatedContent, userId);
|
|
117
|
+
|
|
118
|
+
expect(result).toBeDefined();
|
|
119
|
+
expect(result.message).toBe("Memory updated successfully!");
|
|
120
|
+
|
|
121
|
+
// Verify the update by getting the memory
|
|
122
|
+
const updatedMemory = (await memory.get(memoryId, userId)) as MemoryItem;
|
|
123
|
+
expect(updatedMemory.memory).toBe(updatedContent);
|
|
124
|
+
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should update a memory", async () => {
|
|
128
|
+
// First add a memory
|
|
129
|
+
const init = await memory.capture("I love to drink red wine", userId, {});
|
|
130
|
+
// expect(init.results?.[0]?.id).toBeDefined();
|
|
131
|
+
const initId = init.results?.[0]?.id;
|
|
132
|
+
const addResult = (await memory.capture("I love to drink red wine with friends", userId, {})) as SearchResult;
|
|
133
|
+
expect(addResult.results?.[0]?.id).toBeDefined();
|
|
134
|
+
|
|
135
|
+
const memoryId = addResult.results[0].id;
|
|
136
|
+
expect(memoryId).toBe(initId);
|
|
137
|
+
// Delete the memory
|
|
138
|
+
await memory.delete(memoryId, userId);
|
|
139
|
+
|
|
140
|
+
// Try to get the deleted memory - should throw or return null
|
|
141
|
+
const result = await memory.get(memoryId, userId);
|
|
142
|
+
expect(result).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
it("should get all memories for distinct users", async () => {
|
|
147
|
+
// Add a few memories
|
|
148
|
+
await memory.capture("I love visiting new places in the winters", userId, {});
|
|
149
|
+
await memory.capture("I like to rule the world", userId, {});
|
|
150
|
+
|
|
151
|
+
const result = (await memory.getAll(userId, {})) as SearchResult;
|
|
152
|
+
|
|
153
|
+
expect(result).toBeDefined();
|
|
154
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
155
|
+
expect(result.results.length).toBeGreaterThanOrEqual(2);
|
|
156
|
+
|
|
157
|
+
const otherUser = (await memory.getAll(userId+'-other', {})) as SearchResult;
|
|
158
|
+
|
|
159
|
+
expect(otherUser).toBeDefined();
|
|
160
|
+
expect(Array.isArray(otherUser.results)).toBe(true);
|
|
161
|
+
expect(otherUser.results.length).toBeGreaterThanOrEqual(0);
|
|
162
|
+
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should search memories", async () => {
|
|
166
|
+
// Add some test memories
|
|
167
|
+
await memory.capture("I love programming in Python", userId, {});
|
|
168
|
+
await memory.capture("JavaScript is my favorite language", userId, {});
|
|
169
|
+
|
|
170
|
+
const result = (await memory.retrieve(
|
|
171
|
+
"What programming languages do I know?",
|
|
172
|
+
userId,
|
|
173
|
+
{},
|
|
174
|
+
)) as SearchResult;
|
|
175
|
+
|
|
176
|
+
expect(result).toBeDefined();
|
|
177
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
178
|
+
expect(result.results.length).toBeGreaterThan(0);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it.skip("should get memory history", async () => {
|
|
182
|
+
// Add and update a memory to create history
|
|
183
|
+
const addResult = (await memory.capture(
|
|
184
|
+
"I like swimming in warm water",
|
|
185
|
+
userId,
|
|
186
|
+
{},
|
|
187
|
+
)) as SearchResult;
|
|
188
|
+
|
|
189
|
+
if (!addResult.results?.[0]?.id) {
|
|
190
|
+
throw new Error("Failed to create test memory");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const memoryId = addResult.results[0].id;
|
|
194
|
+
await memory.update(memoryId, "Updated content", userId);
|
|
195
|
+
|
|
196
|
+
const history = await memory.history(memoryId);
|
|
197
|
+
|
|
198
|
+
expect(history).toBeDefined();
|
|
199
|
+
expect(Array.isArray(history)).toBe(true);
|
|
200
|
+
expect(history.length).toBeGreaterThan(0);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("should delete a memory", async () => {
|
|
204
|
+
// First add a memory
|
|
205
|
+
const addResult = (await memory.capture("I love to drink red wine with friends", userId, {})) as SearchResult;
|
|
206
|
+
expect(addResult.results?.[0]?.id).toBeDefined();
|
|
207
|
+
|
|
208
|
+
const memoryId = addResult.results[0].id;
|
|
209
|
+
// Delete the memory
|
|
210
|
+
await memory.delete(memoryId, userId);
|
|
211
|
+
|
|
212
|
+
// Try to get the deleted memory - should throw or return null
|
|
213
|
+
const result = await memory.get(memoryId, userId);
|
|
214
|
+
expect(result).toBeNull();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should remember specific user semantic", async () => {
|
|
218
|
+
// First add a memory
|
|
219
|
+
const result = (await memory.capture([
|
|
220
|
+
{role: "user", content: "est-ce que pol pot à été formé avec l'histoire de la Commune de 1871"},
|
|
221
|
+
{role: "assistant", content: "En bref : oui — pendant ses années d'études à Paris (1949-1953), Saloth Sâr, le futur Pol Pot, a découvert le marxisme dans les cercles d'étudiants khmers et français où la Commune de 1871 était un épisode-clé du patrimoine révolutionnaire. Cette référence l'a fortement marqué ; il affirma plus tard que la Commune avait échoué parce que « le prolétariat n'avait pas exercé la dictature sur la bourgeoisie » et jura de ne pas « répéter cette erreur ». Il s'est donc bien « formé » idéologiquement, au moins en partie, à travers l'histoire et les leçons qu'on tirait alors de la Commune"},
|
|
222
|
+
],
|
|
223
|
+
userId,
|
|
224
|
+
{},
|
|
225
|
+
)) as SearchResult;
|
|
226
|
+
expect(result).toBeDefined();
|
|
227
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
228
|
+
expect(result.results.length).toBeGreaterThan(0);
|
|
229
|
+
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should avoid specific user discussions", async () => {
|
|
233
|
+
// First add a memory
|
|
234
|
+
const result = (await memory.capture([
|
|
235
|
+
{role: "user", content: "est-ce que pol pot à été formé avec l'histoire de la Commune de 1871"},
|
|
236
|
+
{role: "assistant", content: "En bref : oui — pendant ses années d'études à Paris (1949-1953), Saloth Sâr, le futur Pol Pot, a découvert le marxisme dans les cercles d'étudiants khmers et français où la Commune de 1871 était un épisode-clé du patrimoine révolutionnaire. Cette référence l'a fortement marqué ; il affirma plus tard que la Commune avait échoué parce que « le prolétariat n'avait pas exercé la dictature sur la bourgeoisie » et jura de ne pas « répéter cette erreur ». Il s'est donc bien « formé » idéologiquement, au moins en partie, à travers l'histoire et les leçons qu'on tirait alors de la Commune"},
|
|
237
|
+
{role: "user", content: "cette information ne m'interesse pas"},
|
|
238
|
+
],
|
|
239
|
+
userId,
|
|
240
|
+
{},
|
|
241
|
+
)) as SearchResult;
|
|
242
|
+
expect(result).toBeDefined();
|
|
243
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
244
|
+
expect(result.results.length).toBe(0);
|
|
245
|
+
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("should remember encoded user preferences", async () => {
|
|
249
|
+
// First add a memory
|
|
250
|
+
const addResult = (await memory.capture(
|
|
251
|
+
"lorsque j'utilise la commande spéciale 'en\"\"' dans mes questions tu dois traduire le texte",
|
|
252
|
+
userId,
|
|
253
|
+
{},
|
|
254
|
+
)) as SearchResult;
|
|
255
|
+
|
|
256
|
+
if (!addResult.results?.[0]?.id) {
|
|
257
|
+
throw new Error("Failed to create test memory");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
const result = (await memory.retrieve(
|
|
262
|
+
"en\"\"quelle est l'âge du capitaine\"\"",
|
|
263
|
+
userId,
|
|
264
|
+
{},
|
|
265
|
+
)) as SearchResult;
|
|
266
|
+
|
|
267
|
+
expect(result).toBeDefined();
|
|
268
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
269
|
+
expect(result.results.length).toBeGreaterThan(0);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
it("should include 'type' field when capturing and retrieving memory", async () => {
|
|
275
|
+
const captureResult = await memory.capture(
|
|
276
|
+
"User likes sushi.",
|
|
277
|
+
userId,
|
|
278
|
+
{},
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
expect(captureResult.results).toBeDefined();
|
|
282
|
+
expect(captureResult.results.length).toBeGreaterThan(0);
|
|
283
|
+
const memoryId = captureResult.results[0]?.id;
|
|
284
|
+
expect(memoryId).toBeDefined();
|
|
285
|
+
|
|
286
|
+
// Verify the type is present in the capture result (returned immediately)
|
|
287
|
+
// Note: The LLM decides the type, so we check for its existence, not a specific value unless mocked.
|
|
288
|
+
expect(captureResult.results[0]?.type).toBeDefined();
|
|
289
|
+
expect(typeof captureResult.results[0]?.type).toBe('string');
|
|
290
|
+
|
|
291
|
+
// Retrieve the memory to check persistence
|
|
292
|
+
const retrievedMemory = await memory.get(memoryId!, userId);
|
|
293
|
+
expect(retrievedMemory).not.toBeNull();
|
|
294
|
+
expect(retrievedMemory?.type).toBeDefined();
|
|
295
|
+
expect(typeof retrievedMemory?.type).toBe('string');
|
|
296
|
+
// Check payload directly via retrieve (which gets it from vector store)
|
|
297
|
+
const searchResult = await memory.retrieve("User preference on food", userId, {});
|
|
298
|
+
expect(searchResult.results).toBeDefined();
|
|
299
|
+
expect(searchResult.results.length).toBeGreaterThan(0);
|
|
300
|
+
const foundMemory = searchResult.results.find(m => m.id === memoryId);
|
|
301
|
+
expect(foundMemory).toBeDefined();
|
|
302
|
+
expect(foundMemory?.type).toBeDefined();
|
|
303
|
+
expect(typeof foundMemory?.type).toBe('string');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("should ensure 'type' field is passed to vector store during capture (ADD)", async () => {
|
|
307
|
+
const mockVectorStore = {
|
|
308
|
+
insert: jest.fn().mockResolvedValue(undefined),
|
|
309
|
+
search: jest.fn().mockResolvedValue([]), // Mock search to avoid errors in update logic
|
|
310
|
+
get: jest.fn(),
|
|
311
|
+
list: jest.fn(),
|
|
312
|
+
update: jest.fn(),
|
|
313
|
+
delete: jest.fn(),
|
|
314
|
+
deleteCol: jest.fn(),
|
|
315
|
+
ensureCollection: jest.fn(),
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// Mock the getVectorStore method to return our mock
|
|
319
|
+
jest.spyOn(memory, 'getVectorStore').mockResolvedValue(mockVectorStore as any);
|
|
320
|
+
|
|
321
|
+
// Mock LLM responses to force an ADD action with a specific type
|
|
322
|
+
const mockLlm = {
|
|
323
|
+
generateResponse: jest.fn()
|
|
324
|
+
.mockResolvedValueOnce({ // Fact extraction response
|
|
325
|
+
facts: [{ fact: "User likes cats", type: "PREFERENCE" }]
|
|
326
|
+
})
|
|
327
|
+
.mockResolvedValueOnce({ // Memory update response
|
|
328
|
+
memory: [{ event: "ADD", text: "User likes cats", type: "PREFERENCE" }]
|
|
329
|
+
})
|
|
330
|
+
};
|
|
331
|
+
// Replace the LLM instance with the mock
|
|
332
|
+
(memory as any).llm = mockLlm;
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
await memory.capture("User likes cats", userId, {});
|
|
336
|
+
|
|
337
|
+
// Verify mockVectorStore.insert was called
|
|
338
|
+
expect(mockVectorStore.insert).toHaveBeenCalled();
|
|
339
|
+
|
|
340
|
+
// Get the arguments passed to the insert call
|
|
341
|
+
const insertArgs = mockVectorStore.insert.mock.calls[0];
|
|
342
|
+
const metadataPayload = insertArgs[2][0]; // Metadata is the third argument, first item in the array
|
|
343
|
+
|
|
344
|
+
// Assert that the type field is present in the metadata payload
|
|
345
|
+
expect(metadataPayload).toBeDefined();
|
|
346
|
+
expect(metadataPayload.type).toBe('PREFERENCE');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe("Memory with Custom Configuration", () => {
|
|
352
|
+
let customMemory: MemoriesLite;
|
|
353
|
+
|
|
354
|
+
beforeEach(() => {
|
|
355
|
+
customMemory = new MemoriesLite({
|
|
356
|
+
version: "v1.1",
|
|
357
|
+
disableHistory: true,
|
|
358
|
+
embedder: {
|
|
359
|
+
provider: "openai",
|
|
360
|
+
config: {
|
|
361
|
+
dimension,
|
|
362
|
+
apiKey: process.env.OPENAI_API_KEY || "",
|
|
363
|
+
model: "text-embedding-3-small",
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
vectorStore: {
|
|
367
|
+
provider: "lite",
|
|
368
|
+
config: {
|
|
369
|
+
dimension,
|
|
370
|
+
rootPath: ":memory:",
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
llm: {
|
|
374
|
+
provider: "openai",
|
|
375
|
+
config: {
|
|
376
|
+
apiKey: process.env.OPENAI_API_KEY || "",
|
|
377
|
+
model: "gpt-4.1-mini",
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
historyDbPath: ":memory:", // Use in-memory SQLite for tests
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
afterEach(async () => {
|
|
385
|
+
await customMemory.reset(userId);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
it("should perform semantic search with custom embeddings", async () => {
|
|
390
|
+
// Add test memories
|
|
391
|
+
await customMemory.capture("The weather in London is rainy today", userId, {});
|
|
392
|
+
await customMemory.capture("The temperature in Paris is 25 degrees", userId, {});
|
|
393
|
+
|
|
394
|
+
const result = (await customMemory.retrieve(
|
|
395
|
+
"What is the weather like?",
|
|
396
|
+
userId,
|
|
397
|
+
{},
|
|
398
|
+
)) as SearchResult;
|
|
399
|
+
|
|
400
|
+
expect(result).toBeDefined();
|
|
401
|
+
expect(Array.isArray(result.results)).toBe(true);
|
|
402
|
+
// Results should be ordered by relevance
|
|
403
|
+
expect(result.results.length).toBe(0);
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*", "jest.config.js"],
|
|
15
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/index.ts","./src/config/defaults.ts","./src/config/manager.ts","./src/embeddings/base.ts","./src/embeddings/google.ts","./src/embeddings/openai.ts","./src/graphs/configs.ts","./src/graphs/tools.ts","./src/graphs/utils.ts","./src/llms/base.ts","./src/llms/google.ts","./src/llms/openai.ts","./src/llms/openai_structured.ts","./src/memory/index.ts","./src/memory/memory.types.ts","./src/prompts/index.ts","./src/storage/DummyHistoryManager.ts","./src/storage/MemoryHistoryManager.ts","./src/storage/base.ts","./src/storage/index.ts","./src/types/index.ts","./src/utils/bm25.ts","./src/utils/factory.ts","./src/utils/logger.ts","./src/utils/memory.ts","./src/utils/telemetry.ts","./src/utils/telemetry.types.ts","./src/vectorstores/base.ts","./src/vectorstores/lite.ts","./src/vectorstores/llm.ts"],"version":"5.8.3"}
|