@mastra/memory 0.14.3-alpha.0 → 0.14.3-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/package.json +18 -5
- package/.turbo/turbo-build.log +0 -8
- package/eslint.config.js +0 -12
- package/integration-tests-v5/.env.test +0 -5
- package/integration-tests-v5/CHANGELOG.md +0 -159
- package/integration-tests-v5/docker-compose.yml +0 -39
- package/integration-tests-v5/node_modules/.bin/next +0 -21
- package/integration-tests-v5/node_modules/.bin/tsc +0 -21
- package/integration-tests-v5/node_modules/.bin/tsserver +0 -21
- package/integration-tests-v5/node_modules/.bin/vitest +0 -21
- package/integration-tests-v5/package.json +0 -43
- package/integration-tests-v5/src/agent-memory.test.ts +0 -621
- package/integration-tests-v5/src/mastra/agents/weather.ts +0 -75
- package/integration-tests-v5/src/mastra/index.ts +0 -13
- package/integration-tests-v5/src/mastra/tools/weather.ts +0 -24
- package/integration-tests-v5/src/processors.test.ts +0 -604
- package/integration-tests-v5/src/streaming-memory.test.ts +0 -367
- package/integration-tests-v5/src/test-utils.ts +0 -147
- package/integration-tests-v5/src/working-memory.test.ts +0 -1064
- package/integration-tests-v5/tsconfig.json +0 -13
- package/integration-tests-v5/vitest.config.ts +0 -18
- package/src/index.ts +0 -1040
- package/src/processors/index.test.ts +0 -246
- package/src/processors/index.ts +0 -2
- package/src/processors/token-limiter.ts +0 -159
- package/src/processors/tool-call-filter.ts +0 -77
- package/src/tools/working-memory.ts +0 -154
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -17
- package/vitest.config.ts +0 -11
|
@@ -1,1064 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { mkdtemp } from 'node:fs/promises';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { openai } from '@ai-sdk/openai';
|
|
6
|
-
import { Agent } from '@mastra/core/agent';
|
|
7
|
-
import type { MastraMessageV1 } from '@mastra/core/memory';
|
|
8
|
-
import { fastembed } from '@mastra/fastembed';
|
|
9
|
-
import { LibSQLVector, LibSQLStore } from '@mastra/libsql';
|
|
10
|
-
import { Memory } from '@mastra/memory';
|
|
11
|
-
import type { ToolCallPart } from 'ai';
|
|
12
|
-
import { config } from 'dotenv';
|
|
13
|
-
import type { JSONSchema7 } from 'json-schema';
|
|
14
|
-
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
|
|
15
|
-
import { z } from 'zod';
|
|
16
|
-
|
|
17
|
-
const resourceId = 'test-resource';
|
|
18
|
-
let messageCounter = 0;
|
|
19
|
-
|
|
20
|
-
// Test helpers
|
|
21
|
-
const createTestThread = (title: string, metadata = {}) => ({
|
|
22
|
-
id: randomUUID(),
|
|
23
|
-
title,
|
|
24
|
-
resourceId,
|
|
25
|
-
metadata,
|
|
26
|
-
createdAt: new Date(),
|
|
27
|
-
updatedAt: new Date(),
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const createTestMessage = (threadId: string, content: string, role: 'user' | 'assistant' = 'user'): MastraMessageV1 => {
|
|
31
|
-
messageCounter++;
|
|
32
|
-
return {
|
|
33
|
-
id: randomUUID(),
|
|
34
|
-
threadId,
|
|
35
|
-
content,
|
|
36
|
-
role,
|
|
37
|
-
type: 'text',
|
|
38
|
-
createdAt: new Date(Date.now() + messageCounter * 1000),
|
|
39
|
-
resourceId,
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
function extractUserData(obj: any) {
|
|
44
|
-
// Remove common schema keys
|
|
45
|
-
const { type, properties, required, additionalProperties, $schema, ...data } = obj;
|
|
46
|
-
return data;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
config({ path: '.env.test' });
|
|
50
|
-
|
|
51
|
-
describe('Working Memory Tests', () => {
|
|
52
|
-
let memory: Memory;
|
|
53
|
-
let thread: any;
|
|
54
|
-
let storage: LibSQLStore;
|
|
55
|
-
let vector: LibSQLVector;
|
|
56
|
-
|
|
57
|
-
describe('Working Memory Test with Template', () => {
|
|
58
|
-
beforeEach(async () => {
|
|
59
|
-
// Create a new unique database file in the temp directory for each test
|
|
60
|
-
const dbPath = join(await mkdtemp(join(tmpdir(), `memory-working-test-${Date.now()}`)), 'test.db');
|
|
61
|
-
console.log('dbPath', dbPath);
|
|
62
|
-
|
|
63
|
-
storage = new LibSQLStore({
|
|
64
|
-
url: `file:${dbPath}`,
|
|
65
|
-
});
|
|
66
|
-
vector = new LibSQLVector({
|
|
67
|
-
connectionUrl: `file:${dbPath}`,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// Create memory instance with working memory enabled
|
|
71
|
-
memory = new Memory({
|
|
72
|
-
options: {
|
|
73
|
-
workingMemory: {
|
|
74
|
-
enabled: true,
|
|
75
|
-
template: `# User Information
|
|
76
|
-
- **First Name**:
|
|
77
|
-
- **Last Name**:
|
|
78
|
-
- **Location**:
|
|
79
|
-
- **Interests**:
|
|
80
|
-
`,
|
|
81
|
-
},
|
|
82
|
-
lastMessages: 10,
|
|
83
|
-
semanticRecall: {
|
|
84
|
-
topK: 3,
|
|
85
|
-
messageRange: 2,
|
|
86
|
-
},
|
|
87
|
-
threads: {
|
|
88
|
-
generateTitle: false,
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
storage,
|
|
92
|
-
vector,
|
|
93
|
-
embedder: fastembed,
|
|
94
|
-
});
|
|
95
|
-
// Reset message counter
|
|
96
|
-
messageCounter = 0;
|
|
97
|
-
// Create a new thread for each test
|
|
98
|
-
thread = await memory.saveThread({
|
|
99
|
-
thread: createTestThread('Working Memory Test Thread'),
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
afterEach(async () => {
|
|
104
|
-
//@ts-ignore
|
|
105
|
-
await storage.client.close();
|
|
106
|
-
//@ts-ignore
|
|
107
|
-
await vector.turso.close();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should handle LLM responses with working memory using OpenAI (test that the working memory prompt works)', async () => {
|
|
111
|
-
const agent = new Agent({
|
|
112
|
-
name: 'Memory Test Agent',
|
|
113
|
-
instructions: 'You are a helpful AI agent. Always add working memory tags to remember user information.',
|
|
114
|
-
model: openai('gpt-4o'),
|
|
115
|
-
memory,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
await agent.generateVNext('Hi, my name is Tyler and I live in San Francisco', {
|
|
119
|
-
threadId: thread.id,
|
|
120
|
-
resourceId,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Get working memory
|
|
124
|
-
const workingMemory = await memory.getWorkingMemory({ threadId: thread.id });
|
|
125
|
-
expect(workingMemory).not.toBeNull();
|
|
126
|
-
if (workingMemory) {
|
|
127
|
-
// Check for specific Markdown format
|
|
128
|
-
expect(workingMemory).toContain('# User Information');
|
|
129
|
-
expect(workingMemory).toContain('**First Name**: Tyler');
|
|
130
|
-
expect(workingMemory).toContain('**Location**: San Francisco');
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('should initialize with default working memory template', async () => {
|
|
135
|
-
const systemInstruction = await memory.getSystemMessage({ threadId: thread.id });
|
|
136
|
-
expect(systemInstruction).not.toBeNull();
|
|
137
|
-
if (systemInstruction) {
|
|
138
|
-
// Should match our Markdown template
|
|
139
|
-
expect(systemInstruction).toContain('# User Information');
|
|
140
|
-
expect(systemInstruction).toContain('First Name');
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('should hide working memory tags in remembered messages', async () => {
|
|
145
|
-
const messages = [
|
|
146
|
-
createTestMessage(thread.id, 'Hi, my name is John'),
|
|
147
|
-
createTestMessage(
|
|
148
|
-
thread.id,
|
|
149
|
-
`Hello John!
|
|
150
|
-
<working_memory>
|
|
151
|
-
# User Information
|
|
152
|
-
- **First Name**: John
|
|
153
|
-
- **Last Name**:
|
|
154
|
-
- **Location**:
|
|
155
|
-
- **Interests**:
|
|
156
|
-
</working_memory>`,
|
|
157
|
-
'assistant',
|
|
158
|
-
),
|
|
159
|
-
];
|
|
160
|
-
|
|
161
|
-
await memory.saveMessages({ messages, format: 'v2' });
|
|
162
|
-
|
|
163
|
-
const remembered = await memory.rememberMessages({
|
|
164
|
-
threadId: thread.id,
|
|
165
|
-
config: { lastMessages: 10 },
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// Working memory tags should be stripped from the messages
|
|
169
|
-
expect(remembered.messages[1].content).not.toContain('<working_memory>');
|
|
170
|
-
expect(remembered.messages[1].content).toContain('Hello John!');
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('should respect working memory enabled/disabled setting', async () => {
|
|
174
|
-
const dbPath = join(await mkdtemp(join(tmpdir(), `memory-working-test-${Date.now()}`)), 'test.db');
|
|
175
|
-
|
|
176
|
-
// Create memory instance with working memory disabled
|
|
177
|
-
const disabledMemory = new Memory({
|
|
178
|
-
storage: new LibSQLStore({
|
|
179
|
-
url: `file:${dbPath}`,
|
|
180
|
-
}),
|
|
181
|
-
vector: new LibSQLVector({
|
|
182
|
-
connectionUrl: `file:${dbPath}`,
|
|
183
|
-
}),
|
|
184
|
-
embedder: openai.embedding('text-embedding-3-small'),
|
|
185
|
-
options: {
|
|
186
|
-
workingMemory: {
|
|
187
|
-
enabled: false,
|
|
188
|
-
template: `# User Information
|
|
189
|
-
- **First Name**:
|
|
190
|
-
- **Last Name**:
|
|
191
|
-
`,
|
|
192
|
-
},
|
|
193
|
-
lastMessages: 10,
|
|
194
|
-
semanticRecall: {
|
|
195
|
-
topK: 3,
|
|
196
|
-
messageRange: 2,
|
|
197
|
-
},
|
|
198
|
-
threads: {
|
|
199
|
-
generateTitle: false,
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
const thread = await disabledMemory.saveThread({
|
|
205
|
-
thread: createTestThread('Disabled Working Memory Thread'),
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const messages = [
|
|
209
|
-
createTestMessage(thread.id, 'Hi, my name is John'),
|
|
210
|
-
createTestMessage(
|
|
211
|
-
thread.id,
|
|
212
|
-
`Hello John!
|
|
213
|
-
<working_memory>
|
|
214
|
-
# User Information
|
|
215
|
-
- **First Name**: John
|
|
216
|
-
- **Last Name**:
|
|
217
|
-
</working_memory>`,
|
|
218
|
-
'assistant',
|
|
219
|
-
),
|
|
220
|
-
];
|
|
221
|
-
|
|
222
|
-
await disabledMemory.saveMessages({ messages, format: 'v2' });
|
|
223
|
-
|
|
224
|
-
// Working memory should be null when disabled
|
|
225
|
-
const workingMemory = await disabledMemory.getWorkingMemory({ threadId: thread.id });
|
|
226
|
-
expect(workingMemory).toBeNull();
|
|
227
|
-
|
|
228
|
-
// Thread metadata should not contain working memory
|
|
229
|
-
const updatedThread = await disabledMemory.getThreadById({ threadId: thread.id });
|
|
230
|
-
expect(updatedThread?.metadata?.workingMemory).toBeUndefined();
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('should handle LLM responses with working memory using tool calls', async () => {
|
|
234
|
-
const agent = new Agent({
|
|
235
|
-
name: 'Memory Test Agent',
|
|
236
|
-
instructions: 'You are a helpful AI agent. Always add working memory tags to remember user information.',
|
|
237
|
-
model: openai('gpt-4o'),
|
|
238
|
-
memory,
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
const thread = await memory.createThread(createTestThread(`Tool call working memory test`));
|
|
242
|
-
|
|
243
|
-
await agent.generateVNext('Hi, my name is Tyler and I live in San Francisco', {
|
|
244
|
-
threadId: thread.id,
|
|
245
|
-
resourceId,
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
const workingMemory = await memory.getWorkingMemory({ threadId: thread.id });
|
|
249
|
-
expect(workingMemory).not.toBeNull();
|
|
250
|
-
if (workingMemory) {
|
|
251
|
-
// Check for specific Markdown format
|
|
252
|
-
expect(workingMemory).toContain('# User Information');
|
|
253
|
-
expect(workingMemory).toContain('**First Name**: Tyler');
|
|
254
|
-
expect(workingMemory).toContain('**Location**: San Francisco');
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("shouldn't pollute context with working memory tool call args, only the system instruction working memory should exist", async () => {
|
|
259
|
-
const agent = new Agent({
|
|
260
|
-
name: 'Memory Test Agent',
|
|
261
|
-
instructions: 'You are a helpful AI agent. Always add working memory tags to remember user information.',
|
|
262
|
-
model: openai('gpt-4o'),
|
|
263
|
-
memory,
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const thread = await memory.createThread(createTestThread(`Tool call working memory context pollution test`));
|
|
267
|
-
|
|
268
|
-
await agent.generateVNext('Hi, my name is Tyler and I live in a submarine under the sea', {
|
|
269
|
-
threadId: thread.id,
|
|
270
|
-
resourceId,
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
let workingMemory = await memory.getWorkingMemory({ threadId: thread.id });
|
|
274
|
-
expect(workingMemory).not.toBeNull();
|
|
275
|
-
if (workingMemory) {
|
|
276
|
-
expect(workingMemory).toContain('# User Information');
|
|
277
|
-
expect(workingMemory).toContain('**First Name**: Tyler');
|
|
278
|
-
expect(workingMemory?.toLowerCase()).toContain('**location**:');
|
|
279
|
-
expect(workingMemory?.toLowerCase()).toContain('submarine under the sea');
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
await agent.generateVNext('I changed my name to Jim', {
|
|
283
|
-
threadId: thread.id,
|
|
284
|
-
resourceId,
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
workingMemory = await memory.getWorkingMemory({ threadId: thread.id });
|
|
288
|
-
expect(workingMemory).not.toBeNull();
|
|
289
|
-
if (workingMemory) {
|
|
290
|
-
expect(workingMemory).toContain('# User Information');
|
|
291
|
-
expect(workingMemory).toContain('**First Name**: Jim');
|
|
292
|
-
expect(workingMemory?.toLowerCase()).toContain('**location**:');
|
|
293
|
-
expect(workingMemory?.toLowerCase()).toContain('submarine under the sea');
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
await agent.generateVNext('I moved to Vancouver Island', {
|
|
297
|
-
threadId: thread.id,
|
|
298
|
-
resourceId,
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
workingMemory = await memory.getWorkingMemory({ threadId: thread.id });
|
|
302
|
-
expect(workingMemory).not.toBeNull();
|
|
303
|
-
if (workingMemory) {
|
|
304
|
-
expect(workingMemory).toContain('# User Information');
|
|
305
|
-
expect(workingMemory).toContain('**First Name**: Jim');
|
|
306
|
-
expect(workingMemory).toContain('**Location**: Vancouver Island');
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const history = await memory.query({
|
|
310
|
-
threadId: thread.id,
|
|
311
|
-
resourceId,
|
|
312
|
-
selectBy: {
|
|
313
|
-
last: 20,
|
|
314
|
-
},
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
const memoryArgs: string[] = [];
|
|
318
|
-
|
|
319
|
-
for (const message of history.messages) {
|
|
320
|
-
if (message.role === `assistant`) {
|
|
321
|
-
for (const part of message.content) {
|
|
322
|
-
if (typeof part === `string`) continue;
|
|
323
|
-
if (part.type === `tool-call` && part.toolName === `updateWorkingMemory`) {
|
|
324
|
-
memoryArgs.push((part.args as any).memory);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
expect(memoryArgs).not.toContain(`Tyler`);
|
|
331
|
-
expect(memoryArgs).not.toContain('submarine under the sea');
|
|
332
|
-
expect(memoryArgs).not.toContain('Jim');
|
|
333
|
-
expect(memoryArgs).not.toContain('Vancouver Island');
|
|
334
|
-
expect(memoryArgs).toEqual([]);
|
|
335
|
-
|
|
336
|
-
workingMemory = await memory.getWorkingMemory({ threadId: thread.id });
|
|
337
|
-
expect(workingMemory).not.toBeNull();
|
|
338
|
-
if (workingMemory) {
|
|
339
|
-
// Format-specific assertion that checks for Markdown format
|
|
340
|
-
expect(workingMemory).toContain('# User Information');
|
|
341
|
-
expect(workingMemory).toContain('**First Name**: Jim');
|
|
342
|
-
expect(workingMemory).toContain('**Location**: Vancouver Island');
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it('should remove tool-call/tool-result messages with toolName "updateWorkingMemory"', async () => {
|
|
347
|
-
const threadId = thread.id;
|
|
348
|
-
const messages = [
|
|
349
|
-
createTestMessage(threadId, 'User says something'),
|
|
350
|
-
// Pure tool-call message (should be removed)
|
|
351
|
-
{
|
|
352
|
-
id: randomUUID(),
|
|
353
|
-
threadId,
|
|
354
|
-
role: 'assistant',
|
|
355
|
-
type: 'tool-call',
|
|
356
|
-
content: [
|
|
357
|
-
{
|
|
358
|
-
type: 'tool-call',
|
|
359
|
-
toolName: 'updateWorkingMemory',
|
|
360
|
-
// ...other fields as needed
|
|
361
|
-
},
|
|
362
|
-
],
|
|
363
|
-
toolNames: ['updateWorkingMemory'],
|
|
364
|
-
createdAt: new Date(),
|
|
365
|
-
resourceId,
|
|
366
|
-
},
|
|
367
|
-
// Mixed content: tool-call + text (tool-call part should be filtered, text kept)
|
|
368
|
-
{
|
|
369
|
-
id: randomUUID(),
|
|
370
|
-
threadId,
|
|
371
|
-
role: 'assistant',
|
|
372
|
-
type: 'text',
|
|
373
|
-
content: [
|
|
374
|
-
{
|
|
375
|
-
type: 'tool-call',
|
|
376
|
-
toolName: 'updateWorkingMemory',
|
|
377
|
-
args: { memory: 'should not persist' },
|
|
378
|
-
},
|
|
379
|
-
{
|
|
380
|
-
type: 'text',
|
|
381
|
-
text: 'Normal message',
|
|
382
|
-
},
|
|
383
|
-
],
|
|
384
|
-
createdAt: new Date(),
|
|
385
|
-
resourceId,
|
|
386
|
-
},
|
|
387
|
-
// Pure text message (should be kept)
|
|
388
|
-
{
|
|
389
|
-
id: randomUUID(),
|
|
390
|
-
threadId,
|
|
391
|
-
role: 'assistant',
|
|
392
|
-
type: 'text',
|
|
393
|
-
content: 'Another normal message',
|
|
394
|
-
createdAt: new Date(),
|
|
395
|
-
resourceId,
|
|
396
|
-
},
|
|
397
|
-
];
|
|
398
|
-
|
|
399
|
-
// Save messages
|
|
400
|
-
const saved = await memory.saveMessages({ messages: messages as MastraMessageV1[], format: 'v2' });
|
|
401
|
-
|
|
402
|
-
// Should not include any updateWorkingMemory tool-call messages (pure or mixed)
|
|
403
|
-
expect(
|
|
404
|
-
saved.some(
|
|
405
|
-
m =>
|
|
406
|
-
(m.type === 'tool-call' || m.type === 'tool-result') &&
|
|
407
|
-
Array.isArray(m.content.parts) &&
|
|
408
|
-
m.content.parts.some(
|
|
409
|
-
c => c.type === 'tool-invocation' && c.toolInvocation.toolName === `updateWorkingMemory`,
|
|
410
|
-
),
|
|
411
|
-
),
|
|
412
|
-
).toBe(false);
|
|
413
|
-
|
|
414
|
-
// Mixed content message: should only keep the text part
|
|
415
|
-
const assistantMessages = saved.filter(m => m.role === 'assistant');
|
|
416
|
-
expect(
|
|
417
|
-
assistantMessages.every(m => {
|
|
418
|
-
// TODO: seems like saveMessages says it returns MastraMessageV2 but it's returning V1
|
|
419
|
-
return JSON.stringify(m).includes(`updateWorkingMemory`);
|
|
420
|
-
}),
|
|
421
|
-
).toBe(false);
|
|
422
|
-
// working memory should not be present
|
|
423
|
-
expect(
|
|
424
|
-
saved.some(
|
|
425
|
-
m =>
|
|
426
|
-
(m.type === 'tool-call' || m.type === 'tool-result') &&
|
|
427
|
-
Array.isArray(m.content) &&
|
|
428
|
-
m.content.some(c => (c as ToolCallPart).toolName === 'updateWorkingMemory'),
|
|
429
|
-
),
|
|
430
|
-
).toBe(false);
|
|
431
|
-
|
|
432
|
-
// TODO: again seems like we're getting V1 here but types say V2
|
|
433
|
-
// It actually should return V1 for now (CoreMessage compatible)
|
|
434
|
-
|
|
435
|
-
// Pure text message should be present
|
|
436
|
-
expect(saved.some(m => m.content.content === 'Another normal message')).toBe(true);
|
|
437
|
-
// User message should be present
|
|
438
|
-
expect(
|
|
439
|
-
saved.some(m => typeof m.content.content === 'string' && m.content.content.includes('User says something')),
|
|
440
|
-
).toBe(true);
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
describe('Working Memory with agent memory', () => {
|
|
445
|
-
let agent: Agent;
|
|
446
|
-
let thread: any;
|
|
447
|
-
let memory: Memory;
|
|
448
|
-
|
|
449
|
-
beforeEach(async () => {
|
|
450
|
-
const dbPath = join(await mkdtemp(join(tmpdir(), `memory-working-test-${Date.now()}`)), 'test.db');
|
|
451
|
-
storage = new LibSQLStore({
|
|
452
|
-
url: `file:${dbPath}`,
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
memory = new Memory({
|
|
456
|
-
storage,
|
|
457
|
-
options: {
|
|
458
|
-
workingMemory: {
|
|
459
|
-
enabled: true,
|
|
460
|
-
schema: z.object({
|
|
461
|
-
favouriteAnimal: z.string(),
|
|
462
|
-
}),
|
|
463
|
-
},
|
|
464
|
-
lastMessages: 1,
|
|
465
|
-
threads: {
|
|
466
|
-
generateTitle: false,
|
|
467
|
-
},
|
|
468
|
-
},
|
|
469
|
-
});
|
|
470
|
-
// Reset message counter
|
|
471
|
-
messageCounter = 0;
|
|
472
|
-
|
|
473
|
-
// Create a new thread for each test
|
|
474
|
-
thread = await memory.saveThread({
|
|
475
|
-
thread: createTestThread('Working Memory Test Thread'),
|
|
476
|
-
});
|
|
477
|
-
expect(await memory.getWorkingMemory({ threadId: thread.id })).toBeNull();
|
|
478
|
-
agent = new Agent({
|
|
479
|
-
name: 'Memory Test Agent',
|
|
480
|
-
instructions: 'You are a helpful AI agent. Always add working memory tags to remember user information.',
|
|
481
|
-
model: openai('gpt-4o'),
|
|
482
|
-
memory,
|
|
483
|
-
});
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
it('should remember information from working memory in subsequent calls', async () => {
|
|
487
|
-
const thread = await memory.saveThread({
|
|
488
|
-
thread: createTestThread('Remembering Test'),
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
// First call to establish a fact in working memory
|
|
492
|
-
await agent.generateVNext('My favorite animal is the majestic wolf.', {
|
|
493
|
-
threadId: thread.id,
|
|
494
|
-
resourceId,
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
// Verify it's in the working memory
|
|
498
|
-
const workingMemoryAfterFirstCall = await memory.getWorkingMemory({ threadId: thread.id });
|
|
499
|
-
expect(workingMemoryAfterFirstCall).not.toBeNull();
|
|
500
|
-
if (workingMemoryAfterFirstCall) {
|
|
501
|
-
expect(workingMemoryAfterFirstCall.toLowerCase()).toContain('wolf');
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// add messages to the thread
|
|
505
|
-
await agent.generateVNext('How are you doing?', {
|
|
506
|
-
threadId: thread.id,
|
|
507
|
-
resourceId,
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
// third call to see if the agent remembers the fact
|
|
511
|
-
const response = await agent.generateVNext('What is my favorite animal?', {
|
|
512
|
-
threadId: thread.id,
|
|
513
|
-
resourceId,
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
expect(response.text.toLowerCase()).toContain('wolf');
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
describe('Working Memory with Schema', () => {
|
|
520
|
-
let agent: Agent;
|
|
521
|
-
beforeEach(async () => {
|
|
522
|
-
const dbPath = join(await mkdtemp(join(tmpdir(), `memory-working-test-${Date.now()}`)), 'test.db');
|
|
523
|
-
storage = new LibSQLStore({
|
|
524
|
-
url: `file:${dbPath}`,
|
|
525
|
-
});
|
|
526
|
-
vector = new LibSQLVector({
|
|
527
|
-
connectionUrl: `file:${dbPath}`,
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
memory = new Memory({
|
|
531
|
-
storage,
|
|
532
|
-
vector,
|
|
533
|
-
embedder: fastembed,
|
|
534
|
-
options: {
|
|
535
|
-
workingMemory: {
|
|
536
|
-
enabled: true,
|
|
537
|
-
schema: z.object({
|
|
538
|
-
city: z.string(),
|
|
539
|
-
temperature: z.number().optional(),
|
|
540
|
-
}),
|
|
541
|
-
},
|
|
542
|
-
lastMessages: 10,
|
|
543
|
-
semanticRecall: {
|
|
544
|
-
topK: 3,
|
|
545
|
-
messageRange: 2,
|
|
546
|
-
},
|
|
547
|
-
threads: {
|
|
548
|
-
generateTitle: false,
|
|
549
|
-
},
|
|
550
|
-
},
|
|
551
|
-
});
|
|
552
|
-
// Reset message counter
|
|
553
|
-
messageCounter = 0;
|
|
554
|
-
|
|
555
|
-
// Create a new thread for each test
|
|
556
|
-
thread = await memory.saveThread({
|
|
557
|
-
thread: createTestThread('Working Memory Test Thread'),
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
expect(await memory.getWorkingMemory({ threadId: thread.id })).toBeNull();
|
|
561
|
-
|
|
562
|
-
agent = new Agent({
|
|
563
|
-
name: 'Memory Test Agent',
|
|
564
|
-
instructions: 'You are a helpful AI agent. Always add working memory tags to remember user information.',
|
|
565
|
-
model: openai('gpt-4o'),
|
|
566
|
-
memory,
|
|
567
|
-
});
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
afterEach(async () => {
|
|
571
|
-
//@ts-ignore
|
|
572
|
-
await storage.client.close();
|
|
573
|
-
//@ts-ignore
|
|
574
|
-
await vector.turso.close();
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
it('should accept valid working memory updates matching the schema', async () => {
|
|
578
|
-
const validMemory = { city: 'Austin', temperature: 85 };
|
|
579
|
-
await agent.generateVNext('I am in Austin and it is 85 degrees', {
|
|
580
|
-
threadId: thread.id,
|
|
581
|
-
resourceId,
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
const wmRaw = await memory.getWorkingMemory({ threadId: thread.id });
|
|
585
|
-
const wm = typeof wmRaw === 'string' ? JSON.parse(wmRaw) : wmRaw;
|
|
586
|
-
const wmObj = typeof wm === 'string' ? JSON.parse(wm) : wm;
|
|
587
|
-
expect(extractUserData(wmObj)).toMatchObject(validMemory);
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
it('should recall the most recent valid schema-based working memory', async () => {
|
|
591
|
-
const second = { city: 'Denver', temperature: 75 };
|
|
592
|
-
await agent.generateVNext('Now I am in Seattle and it is 60 degrees', {
|
|
593
|
-
threadId: thread.id,
|
|
594
|
-
resourceId,
|
|
595
|
-
});
|
|
596
|
-
await agent.generateVNext('Now I am in Denver and it is 75 degrees', {
|
|
597
|
-
threadId: thread.id,
|
|
598
|
-
resourceId,
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
const wmRaw = await memory.getWorkingMemory({ threadId: thread.id });
|
|
602
|
-
const wm = typeof wmRaw === 'string' ? JSON.parse(wmRaw) : wmRaw;
|
|
603
|
-
const wmObj = typeof wm === 'string' ? JSON.parse(wm) : wm;
|
|
604
|
-
expect(extractUserData(wmObj)).toMatchObject(second);
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
// Skip this for now it's an edge case where an agent updates the working memory based off of the
|
|
608
|
-
// message history.
|
|
609
|
-
it.skip('should not update working from message history', async () => {
|
|
610
|
-
const newThread = await memory.saveThread({
|
|
611
|
-
thread: createTestThread('Test111'),
|
|
612
|
-
});
|
|
613
|
-
const first = { city: 'Toronto', temperature: 80 };
|
|
614
|
-
const generateOptions = {
|
|
615
|
-
memory: {
|
|
616
|
-
resource: resourceId,
|
|
617
|
-
thread: newThread.id,
|
|
618
|
-
options: {
|
|
619
|
-
lastMessages: 0,
|
|
620
|
-
semanticRecall: undefined,
|
|
621
|
-
workingMemory: {
|
|
622
|
-
enabled: true,
|
|
623
|
-
schema: z.object({
|
|
624
|
-
city: z.string(),
|
|
625
|
-
temperature: z.number().optional(),
|
|
626
|
-
}),
|
|
627
|
-
},
|
|
628
|
-
threads: {
|
|
629
|
-
generateTitle: false,
|
|
630
|
-
},
|
|
631
|
-
},
|
|
632
|
-
},
|
|
633
|
-
};
|
|
634
|
-
await agent.generateVNext('Now I am in Toronto and it is 80 degrees', generateOptions);
|
|
635
|
-
|
|
636
|
-
await agent.generateVNext('how are you doing?', generateOptions);
|
|
637
|
-
|
|
638
|
-
const firstWorkingMemory = await memory.getWorkingMemory({ threadId: newThread.id });
|
|
639
|
-
const wm = typeof firstWorkingMemory === 'string' ? JSON.parse(firstWorkingMemory) : firstWorkingMemory;
|
|
640
|
-
const wmObj = typeof wm === 'string' ? JSON.parse(wm) : wm;
|
|
641
|
-
|
|
642
|
-
expect(wmObj).toMatchObject(first);
|
|
643
|
-
|
|
644
|
-
const updatedThread = await memory.getThreadById({ threadId: newThread.id });
|
|
645
|
-
if (!updatedThread) {
|
|
646
|
-
throw new Error('Thread not found');
|
|
647
|
-
}
|
|
648
|
-
// Update thread metadata with new working memory
|
|
649
|
-
await memory.saveThread({
|
|
650
|
-
thread: {
|
|
651
|
-
...updatedThread,
|
|
652
|
-
metadata: {
|
|
653
|
-
...(updatedThread.metadata || {}),
|
|
654
|
-
workingMemory: { city: 'Waterloo', temperature: 78 },
|
|
655
|
-
},
|
|
656
|
-
},
|
|
657
|
-
memoryConfig: generateOptions.memory.options,
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
// This should not update the working memory
|
|
661
|
-
await agent.generateVNext('how are you doing?', generateOptions);
|
|
662
|
-
|
|
663
|
-
const result = await agent.generateVNext('Can you tell me where I am?', generateOptions);
|
|
664
|
-
|
|
665
|
-
expect(result.text).toContain('Waterloo');
|
|
666
|
-
const secondWorkingMemory = await memory.getWorkingMemory({ threadId: newThread.id });
|
|
667
|
-
expect(secondWorkingMemory).toMatchObject({ city: 'Waterloo', temperature: 78 });
|
|
668
|
-
});
|
|
669
|
-
});
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
describe('Working Memory with JSONSchema7', () => {
|
|
673
|
-
let agent: Agent;
|
|
674
|
-
let thread: any;
|
|
675
|
-
let memory: Memory;
|
|
676
|
-
|
|
677
|
-
beforeEach(async () => {
|
|
678
|
-
const dbPath = join(await mkdtemp(join(tmpdir(), `memory-jsonschema-test-${Date.now()}`)), 'test.db');
|
|
679
|
-
storage = new LibSQLStore({
|
|
680
|
-
url: `file:${dbPath}`,
|
|
681
|
-
});
|
|
682
|
-
vector = new LibSQLVector({
|
|
683
|
-
connectionUrl: `file:${dbPath}`,
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
const jsonSchema: JSONSchema7 = {
|
|
687
|
-
type: 'object',
|
|
688
|
-
properties: {
|
|
689
|
-
name: { type: 'string' },
|
|
690
|
-
age: { type: 'number' },
|
|
691
|
-
city: { type: 'string' },
|
|
692
|
-
preferences: {
|
|
693
|
-
type: 'object',
|
|
694
|
-
properties: {
|
|
695
|
-
theme: { type: 'string' },
|
|
696
|
-
notifications: { type: 'boolean' },
|
|
697
|
-
},
|
|
698
|
-
},
|
|
699
|
-
},
|
|
700
|
-
required: ['name', 'city'],
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
memory = new Memory({
|
|
704
|
-
storage,
|
|
705
|
-
vector,
|
|
706
|
-
embedder: fastembed,
|
|
707
|
-
options: {
|
|
708
|
-
workingMemory: {
|
|
709
|
-
enabled: true,
|
|
710
|
-
schema: jsonSchema,
|
|
711
|
-
},
|
|
712
|
-
lastMessages: 10,
|
|
713
|
-
semanticRecall: {
|
|
714
|
-
topK: 3,
|
|
715
|
-
messageRange: 2,
|
|
716
|
-
},
|
|
717
|
-
threads: {
|
|
718
|
-
generateTitle: false,
|
|
719
|
-
},
|
|
720
|
-
},
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
// Reset message counter
|
|
724
|
-
messageCounter = 0;
|
|
725
|
-
|
|
726
|
-
// Create a new thread for each test
|
|
727
|
-
thread = await memory.saveThread({
|
|
728
|
-
thread: createTestThread('JSONSchema7 Working Memory Test Thread'),
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
// Verify initial working memory is empty
|
|
732
|
-
expect(await memory.getWorkingMemory({ threadId: thread.id })).toBeNull();
|
|
733
|
-
|
|
734
|
-
agent = new Agent({
|
|
735
|
-
name: 'JSONSchema Memory Test Agent',
|
|
736
|
-
instructions: 'You are a helpful AI agent. Always update working memory with user information.',
|
|
737
|
-
model: openai('gpt-4o'),
|
|
738
|
-
memory,
|
|
739
|
-
});
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
afterEach(async () => {
|
|
743
|
-
//@ts-ignore
|
|
744
|
-
await storage.client.close();
|
|
745
|
-
//@ts-ignore
|
|
746
|
-
await vector.turso.close();
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
it('should accept JSONSchema7 in working memory configuration', async () => {
|
|
750
|
-
// Test that we can create a Memory instance with JSONSchema7 schema
|
|
751
|
-
const jsonSchema: JSONSchema7 = {
|
|
752
|
-
type: 'object',
|
|
753
|
-
properties: {
|
|
754
|
-
testField: { type: 'string' },
|
|
755
|
-
},
|
|
756
|
-
required: ['testField'],
|
|
757
|
-
};
|
|
758
|
-
|
|
759
|
-
const testMemory = new Memory({
|
|
760
|
-
storage,
|
|
761
|
-
options: {
|
|
762
|
-
workingMemory: {
|
|
763
|
-
enabled: true,
|
|
764
|
-
schema: jsonSchema,
|
|
765
|
-
},
|
|
766
|
-
},
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
// Get the working memory template
|
|
770
|
-
const template = await testMemory.getWorkingMemoryTemplate({
|
|
771
|
-
memoryConfig: {
|
|
772
|
-
workingMemory: {
|
|
773
|
-
enabled: true,
|
|
774
|
-
schema: jsonSchema,
|
|
775
|
-
},
|
|
776
|
-
},
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
expect(template).not.toBeNull();
|
|
780
|
-
expect(template?.format).toBe('json');
|
|
781
|
-
expect(template?.content).toContain('testField');
|
|
782
|
-
expect(template?.content).toContain('string');
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
it('should accept valid working memory updates matching the JSONSchema7', async () => {
|
|
786
|
-
await agent.generateVNext(
|
|
787
|
-
'Hi, my name is John Doe, I am 30 years old and I live in Boston. I prefer dark theme and want notifications enabled.',
|
|
788
|
-
{
|
|
789
|
-
threadId: thread.id,
|
|
790
|
-
resourceId,
|
|
791
|
-
},
|
|
792
|
-
);
|
|
793
|
-
|
|
794
|
-
const wmRaw = await memory.getWorkingMemory({ threadId: thread.id });
|
|
795
|
-
const wm = typeof wmRaw === 'string' ? JSON.parse(wmRaw) : wmRaw;
|
|
796
|
-
const wmObj = typeof wm === 'string' ? JSON.parse(wm) : wm;
|
|
797
|
-
const userData = extractUserData(wmObj);
|
|
798
|
-
|
|
799
|
-
expect(userData.name).toBe('John Doe');
|
|
800
|
-
expect(userData.age).toBe(30);
|
|
801
|
-
expect(userData.city).toBe('Boston');
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
it('should handle required and optional fields correctly with JSONSchema7', async () => {
|
|
805
|
-
// Test with only required fields
|
|
806
|
-
await agent.generateVNext('My name is Jane Smith and I live in Portland.', {
|
|
807
|
-
threadId: thread.id,
|
|
808
|
-
resourceId,
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
const wmRaw = await memory.getWorkingMemory({ threadId: thread.id });
|
|
812
|
-
const wm = typeof wmRaw === 'string' ? JSON.parse(wmRaw) : wmRaw;
|
|
813
|
-
const wmObj = typeof wm === 'string' ? JSON.parse(wm) : wm;
|
|
814
|
-
const userData = extractUserData(wmObj);
|
|
815
|
-
|
|
816
|
-
expect(userData.name).toBe('Jane Smith');
|
|
817
|
-
expect(userData.city).toBe('Portland');
|
|
818
|
-
// Age is not required, so it might not be set
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
it('should update working memory progressively with JSONSchema7', async () => {
|
|
822
|
-
// First message with partial info
|
|
823
|
-
await agent.generateVNext('Hi, I am Alex and I live in Miami.', {
|
|
824
|
-
threadId: thread.id,
|
|
825
|
-
resourceId,
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
let wmRaw = await memory.getWorkingMemory({ threadId: thread.id });
|
|
829
|
-
let wm = typeof wmRaw === 'string' ? JSON.parse(wmRaw) : wmRaw;
|
|
830
|
-
let wmObj = typeof wm === 'string' ? JSON.parse(wm) : wm;
|
|
831
|
-
let userData = extractUserData(wmObj);
|
|
832
|
-
|
|
833
|
-
expect(userData.name).toBe('Alex');
|
|
834
|
-
expect(userData.city).toBe('Miami');
|
|
835
|
-
|
|
836
|
-
// Second message adding more info
|
|
837
|
-
await agent.generateVNext('I am 25 years old.', {
|
|
838
|
-
threadId: thread.id,
|
|
839
|
-
resourceId,
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
wmRaw = await memory.getWorkingMemory({ threadId: thread.id });
|
|
843
|
-
wm = typeof wmRaw === 'string' ? JSON.parse(wmRaw) : wmRaw;
|
|
844
|
-
wmObj = typeof wm === 'string' ? JSON.parse(wm) : wm;
|
|
845
|
-
userData = extractUserData(wmObj);
|
|
846
|
-
|
|
847
|
-
expect(userData.name).toBe('Alex');
|
|
848
|
-
expect(userData.city).toBe('Miami');
|
|
849
|
-
expect(userData.age).toBe(25);
|
|
850
|
-
});
|
|
851
|
-
|
|
852
|
-
it('should persist working memory across multiple interactions with JSONSchema7', async () => {
|
|
853
|
-
// Set initial data
|
|
854
|
-
await agent.generateVNext('My name is Sarah Wilson, I am 28 and live in Seattle.', {
|
|
855
|
-
threadId: thread.id,
|
|
856
|
-
resourceId,
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
// Verify working memory is set
|
|
860
|
-
let wmRaw = await memory.getWorkingMemory({ threadId: thread.id });
|
|
861
|
-
let wm = typeof wmRaw === 'string' ? JSON.parse(wmRaw) : wmRaw;
|
|
862
|
-
let wmObj = typeof wm === 'string' ? JSON.parse(wm) : wm;
|
|
863
|
-
let userData = extractUserData(wmObj);
|
|
864
|
-
expect(userData.name).toBe('Sarah Wilson');
|
|
865
|
-
|
|
866
|
-
// Ask a question that should use the working memory
|
|
867
|
-
const response = await agent.generateVNext('What is my name and where do I live?', {
|
|
868
|
-
threadId: thread.id,
|
|
869
|
-
resourceId,
|
|
870
|
-
});
|
|
871
|
-
|
|
872
|
-
// The response should contain the information from working memory
|
|
873
|
-
expect(response.text.toLowerCase()).toContain('sarah');
|
|
874
|
-
expect(response.text.toLowerCase()).toContain('seattle');
|
|
875
|
-
});
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
describe('Resource-Scoped Working Memory Tests', () => {
|
|
879
|
-
beforeEach(async () => {
|
|
880
|
-
// Create a new unique database file in the temp directory for each test
|
|
881
|
-
const dbPath = join(await mkdtemp(join(tmpdir(), `memory-resource-working-test-`)), 'test.db');
|
|
882
|
-
console.log('dbPath', dbPath);
|
|
883
|
-
|
|
884
|
-
storage = new LibSQLStore({
|
|
885
|
-
url: `file:${dbPath}`,
|
|
886
|
-
});
|
|
887
|
-
vector = new LibSQLVector({
|
|
888
|
-
connectionUrl: `file:${dbPath}`,
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
// Create memory instance with resource-scoped working memory enabled
|
|
892
|
-
memory = new Memory({
|
|
893
|
-
options: {
|
|
894
|
-
workingMemory: {
|
|
895
|
-
enabled: true,
|
|
896
|
-
scope: 'resource',
|
|
897
|
-
template: `# User Information
|
|
898
|
-
- **First Name**:
|
|
899
|
-
- **Last Name**:
|
|
900
|
-
- **Location**:
|
|
901
|
-
- **Interests**:
|
|
902
|
-
`,
|
|
903
|
-
},
|
|
904
|
-
lastMessages: 10,
|
|
905
|
-
semanticRecall: {
|
|
906
|
-
topK: 3,
|
|
907
|
-
messageRange: 2,
|
|
908
|
-
},
|
|
909
|
-
threads: {
|
|
910
|
-
generateTitle: false,
|
|
911
|
-
},
|
|
912
|
-
},
|
|
913
|
-
storage,
|
|
914
|
-
vector,
|
|
915
|
-
embedder: fastembed,
|
|
916
|
-
});
|
|
917
|
-
// Reset message counter
|
|
918
|
-
messageCounter = 0;
|
|
919
|
-
// Create a new thread for each test
|
|
920
|
-
thread = await memory.saveThread({
|
|
921
|
-
thread: createTestThread('Resource Working Memory Test Thread'),
|
|
922
|
-
});
|
|
923
|
-
});
|
|
924
|
-
|
|
925
|
-
afterEach(async () => {
|
|
926
|
-
//@ts-ignore
|
|
927
|
-
await storage.client.close();
|
|
928
|
-
//@ts-ignore
|
|
929
|
-
await vector.turso.close();
|
|
930
|
-
});
|
|
931
|
-
|
|
932
|
-
it('should store working memory at resource level', async () => {
|
|
933
|
-
// Update working memory using the updateWorkingMemory method
|
|
934
|
-
const workingMemoryData = `# User Information
|
|
935
|
-
- **First Name**: John
|
|
936
|
-
- **Last Name**: Doe
|
|
937
|
-
- **Location**: New York
|
|
938
|
-
- **Interests**: AI, Machine Learning
|
|
939
|
-
`;
|
|
940
|
-
|
|
941
|
-
await memory.updateWorkingMemory({
|
|
942
|
-
threadId: thread.id,
|
|
943
|
-
resourceId,
|
|
944
|
-
workingMemory: workingMemoryData,
|
|
945
|
-
});
|
|
946
|
-
|
|
947
|
-
// Get working memory and verify it's stored at resource level
|
|
948
|
-
const retrievedWorkingMemory = await memory.getWorkingMemory({
|
|
949
|
-
threadId: thread.id,
|
|
950
|
-
resourceId,
|
|
951
|
-
});
|
|
952
|
-
|
|
953
|
-
expect(retrievedWorkingMemory).toBe(workingMemoryData);
|
|
954
|
-
});
|
|
955
|
-
|
|
956
|
-
it('should share working memory across multiple threads for the same resource', async () => {
|
|
957
|
-
// Create a second thread for the same resource
|
|
958
|
-
const thread2 = await memory.saveThread({
|
|
959
|
-
thread: createTestThread('Second Resource Working Memory Test Thread'),
|
|
960
|
-
});
|
|
961
|
-
|
|
962
|
-
// Update working memory from first thread
|
|
963
|
-
const workingMemoryData = `# User Information
|
|
964
|
-
- **First Name**: Alice
|
|
965
|
-
- **Last Name**: Smith
|
|
966
|
-
- **Location**: California
|
|
967
|
-
- **Interests**: Data Science, Python
|
|
968
|
-
`;
|
|
969
|
-
|
|
970
|
-
await memory.updateWorkingMemory({
|
|
971
|
-
threadId: thread.id,
|
|
972
|
-
resourceId,
|
|
973
|
-
workingMemory: workingMemoryData,
|
|
974
|
-
});
|
|
975
|
-
|
|
976
|
-
// Retrieve working memory from second thread
|
|
977
|
-
const retrievedFromThread2 = await memory.getWorkingMemory({
|
|
978
|
-
threadId: thread2.id,
|
|
979
|
-
resourceId,
|
|
980
|
-
});
|
|
981
|
-
|
|
982
|
-
expect(retrievedFromThread2).toBe(workingMemoryData);
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
it('should update working memory across all threads when updated from any thread', async () => {
|
|
986
|
-
// Create multiple threads for the same resource
|
|
987
|
-
const thread2 = await memory.saveThread({
|
|
988
|
-
thread: createTestThread('Second Thread'),
|
|
989
|
-
});
|
|
990
|
-
const thread3 = await memory.saveThread({
|
|
991
|
-
thread: createTestThread('Third Thread'),
|
|
992
|
-
});
|
|
993
|
-
|
|
994
|
-
// Set initial working memory from thread1
|
|
995
|
-
const initialWorkingMemory = `# User Information
|
|
996
|
-
- **First Name**: Bob
|
|
997
|
-
- **Last Name**: Johnson
|
|
998
|
-
- **Location**: Texas
|
|
999
|
-
- **Interests**: Software Development
|
|
1000
|
-
`;
|
|
1001
|
-
|
|
1002
|
-
await memory.updateWorkingMemory({
|
|
1003
|
-
threadId: thread.id,
|
|
1004
|
-
resourceId,
|
|
1005
|
-
workingMemory: initialWorkingMemory,
|
|
1006
|
-
});
|
|
1007
|
-
|
|
1008
|
-
// Update working memory from thread2
|
|
1009
|
-
const updatedWorkingMemory = `# User Information
|
|
1010
|
-
- **First Name**: Bob
|
|
1011
|
-
- **Last Name**: Johnson
|
|
1012
|
-
- **Location**: Florida
|
|
1013
|
-
- **Interests**: Software Development, Travel
|
|
1014
|
-
`;
|
|
1015
|
-
|
|
1016
|
-
await memory.updateWorkingMemory({
|
|
1017
|
-
threadId: thread2.id,
|
|
1018
|
-
resourceId,
|
|
1019
|
-
workingMemory: updatedWorkingMemory,
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
// Verify all threads see the updated working memory
|
|
1023
|
-
const wmFromThread1 = await memory.getWorkingMemory({ threadId: thread.id, resourceId });
|
|
1024
|
-
const wmFromThread2 = await memory.getWorkingMemory({ threadId: thread2.id, resourceId });
|
|
1025
|
-
const wmFromThread3 = await memory.getWorkingMemory({ threadId: thread3.id, resourceId });
|
|
1026
|
-
|
|
1027
|
-
expect(wmFromThread1).toBe(updatedWorkingMemory);
|
|
1028
|
-
expect(wmFromThread2).toBe(updatedWorkingMemory);
|
|
1029
|
-
expect(wmFromThread3).toBe(updatedWorkingMemory);
|
|
1030
|
-
});
|
|
1031
|
-
|
|
1032
|
-
it('should handle JSON format correctly for resource-scoped working memory', async () => {
|
|
1033
|
-
const workingMemoryData = `{"name":"Charlie","age":30,"city":"Seattle"}`;
|
|
1034
|
-
|
|
1035
|
-
await memory.updateWorkingMemory({
|
|
1036
|
-
threadId: thread.id,
|
|
1037
|
-
resourceId,
|
|
1038
|
-
workingMemory: workingMemoryData,
|
|
1039
|
-
});
|
|
1040
|
-
|
|
1041
|
-
// Test JSON format retrieval
|
|
1042
|
-
const retrievedAsJson = await memory.getWorkingMemory({
|
|
1043
|
-
threadId: thread.id,
|
|
1044
|
-
resourceId,
|
|
1045
|
-
});
|
|
1046
|
-
|
|
1047
|
-
expect(retrievedAsJson).toBe(`{"name":"Charlie","age":30,"city":"Seattle"}`);
|
|
1048
|
-
|
|
1049
|
-
// Test default format retrieval
|
|
1050
|
-
const retrievedDefault = await memory.getWorkingMemory({
|
|
1051
|
-
threadId: thread.id,
|
|
1052
|
-
resourceId,
|
|
1053
|
-
});
|
|
1054
|
-
|
|
1055
|
-
expect(retrievedDefault).toBe(workingMemoryData);
|
|
1056
|
-
});
|
|
1057
|
-
|
|
1058
|
-
it('should verify storage adapter support for resource working memory', async () => {
|
|
1059
|
-
// This test would require a mock storage adapter that doesn't support resource working memory
|
|
1060
|
-
// For now, we'll just verify that LibSQL supports it
|
|
1061
|
-
expect(storage.supports.resourceWorkingMemory).toBe(true);
|
|
1062
|
-
});
|
|
1063
|
-
});
|
|
1064
|
-
});
|