@mastra/memory 0.11.2 → 0.11.3-alpha.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/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +30 -0
- package/dist/_tsup-dts-rollup.d.cts +34 -6
- package/dist/_tsup-dts-rollup.d.ts +34 -6
- package/dist/index.cjs +224 -56
- package/dist/index.js +225 -57
- package/package.json +7 -4
- package/src/index.ts +229 -71
- package/src/tools/working-memory.ts +91 -5
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { generateEmptyFromSchema } from '@mastra/core';
|
|
2
2
|
import type { CoreTool, MastraMessageV1 } from '@mastra/core';
|
|
3
3
|
import { MessageList } from '@mastra/core/agent';
|
|
4
4
|
import type { MastraMessageV2 } from '@mastra/core/agent';
|
|
@@ -7,10 +7,14 @@ import type { MemoryConfig, SharedMemoryConfig, StorageThreadType, WorkingMemory
|
|
|
7
7
|
import type { StorageGetMessagesArg } from '@mastra/core/storage';
|
|
8
8
|
import { embedMany } from 'ai';
|
|
9
9
|
import type { CoreMessage, TextPart, UIMessage } from 'ai';
|
|
10
|
+
import { Mutex } from 'async-mutex';
|
|
11
|
+
import type { JSONSchema7 } from 'json-schema';
|
|
10
12
|
|
|
11
13
|
import xxhash from 'xxhash-wasm';
|
|
14
|
+
import { ZodObject } from 'zod';
|
|
15
|
+
import type { ZodTypeAny } from 'zod';
|
|
12
16
|
import zodToJsonSchema from 'zod-to-json-schema';
|
|
13
|
-
import { updateWorkingMemoryTool } from './tools/working-memory';
|
|
17
|
+
import { updateWorkingMemoryTool, __experimental_updateWorkingMemoryToolVNext } from './tools/working-memory';
|
|
14
18
|
|
|
15
19
|
// Average characters per token based on OpenAI's tokenization
|
|
16
20
|
const CHARS_PER_TOKEN = 4;
|
|
@@ -18,6 +22,8 @@ const CHARS_PER_TOKEN = 4;
|
|
|
18
22
|
const DEFAULT_MESSAGE_RANGE = { before: 2, after: 2 } as const;
|
|
19
23
|
const DEFAULT_TOP_K = 2;
|
|
20
24
|
|
|
25
|
+
const isZodObject = (v: ZodTypeAny): v is ZodObject<any, any, any> => v instanceof ZodObject;
|
|
26
|
+
|
|
21
27
|
/**
|
|
22
28
|
* Concrete implementation of MastraMemory that adds support for thread configuration
|
|
23
29
|
* and message injection.
|
|
@@ -247,52 +253,7 @@ export class Memory extends MastraMemory {
|
|
|
247
253
|
return this.storage.getThreadsByResourceId({ resourceId });
|
|
248
254
|
}
|
|
249
255
|
|
|
250
|
-
async saveThread({
|
|
251
|
-
thread,
|
|
252
|
-
memoryConfig,
|
|
253
|
-
}: {
|
|
254
|
-
thread: StorageThreadType;
|
|
255
|
-
memoryConfig?: MemoryConfig;
|
|
256
|
-
}): Promise<StorageThreadType> {
|
|
257
|
-
const config = this.getMergedThreadConfig(memoryConfig || {});
|
|
258
|
-
|
|
259
|
-
if (config.workingMemory?.enabled) {
|
|
260
|
-
const scope = config.workingMemory.scope || 'thread';
|
|
261
|
-
|
|
262
|
-
if (scope === 'resource' && thread.resourceId) {
|
|
263
|
-
// For resource scope, initialize working memory in resource table
|
|
264
|
-
const existingResource = await this.storage.getResourceById({ resourceId: thread.resourceId });
|
|
265
|
-
|
|
266
|
-
if (!existingResource?.workingMemory) {
|
|
267
|
-
let workingMemory = config.workingMemory.template || this.defaultWorkingMemoryTemplate;
|
|
268
|
-
|
|
269
|
-
if (config.workingMemory.schema) {
|
|
270
|
-
workingMemory = JSON.stringify(zodToJsonSchema(config.workingMemory.schema));
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
await this.storage.updateResource({
|
|
274
|
-
resourceId: thread.resourceId,
|
|
275
|
-
workingMemory,
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
} else if (scope === 'thread' && !thread?.metadata?.workingMemory) {
|
|
279
|
-
// For thread scope, initialize working memory in thread metadata (existing behavior)
|
|
280
|
-
let workingMemory = config.workingMemory.template || this.defaultWorkingMemoryTemplate;
|
|
281
|
-
|
|
282
|
-
if (config.workingMemory.schema) {
|
|
283
|
-
workingMemory = JSON.stringify(zodToJsonSchema(config.workingMemory.schema));
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return this.storage.saveThread({
|
|
287
|
-
thread: deepMerge(thread, {
|
|
288
|
-
metadata: {
|
|
289
|
-
workingMemory,
|
|
290
|
-
},
|
|
291
|
-
}),
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
256
|
+
async saveThread({ thread }: { thread: StorageThreadType; memoryConfig?: MemoryConfig }): Promise<StorageThreadType> {
|
|
296
257
|
return this.storage.saveThread({ thread });
|
|
297
258
|
}
|
|
298
259
|
|
|
@@ -359,6 +320,116 @@ export class Memory extends MastraMemory {
|
|
|
359
320
|
}
|
|
360
321
|
}
|
|
361
322
|
|
|
323
|
+
private updateWorkingMemoryMutexes = new Map<string, Mutex>();
|
|
324
|
+
/**
|
|
325
|
+
* @warning experimental! can be removed or changed at any time
|
|
326
|
+
*/
|
|
327
|
+
async __experimental_updateWorkingMemoryVNext({
|
|
328
|
+
threadId,
|
|
329
|
+
resourceId,
|
|
330
|
+
workingMemory,
|
|
331
|
+
searchString,
|
|
332
|
+
memoryConfig,
|
|
333
|
+
}: {
|
|
334
|
+
threadId: string;
|
|
335
|
+
resourceId?: string;
|
|
336
|
+
workingMemory: string;
|
|
337
|
+
searchString?: string;
|
|
338
|
+
memoryConfig?: MemoryConfig;
|
|
339
|
+
}): Promise<{ success: boolean; reason: string }> {
|
|
340
|
+
const config = this.getMergedThreadConfig(memoryConfig || {});
|
|
341
|
+
|
|
342
|
+
if (!config.workingMemory?.enabled) {
|
|
343
|
+
throw new Error('Working memory is not enabled for this memory instance');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// If the agent calls the update working memory tool multiple times simultaneously
|
|
347
|
+
// each call could overwrite the other call
|
|
348
|
+
// so get an in memory mutex to make sure this.getWorkingMemory() returns up to date data each time
|
|
349
|
+
const mutexKey =
|
|
350
|
+
memoryConfig?.workingMemory?.scope === `resource` ? `resource-${resourceId}` : `thread-${threadId}`;
|
|
351
|
+
const mutex = this.updateWorkingMemoryMutexes.has(mutexKey)
|
|
352
|
+
? this.updateWorkingMemoryMutexes.get(mutexKey)!
|
|
353
|
+
: new Mutex();
|
|
354
|
+
this.updateWorkingMemoryMutexes.set(mutexKey, mutex);
|
|
355
|
+
const release = await mutex.acquire();
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const existingWorkingMemory = (await this.getWorkingMemory({ threadId, resourceId, memoryConfig })) || '';
|
|
359
|
+
const template = await this.getWorkingMemoryTemplate({ memoryConfig });
|
|
360
|
+
|
|
361
|
+
let reason = '';
|
|
362
|
+
if (existingWorkingMemory) {
|
|
363
|
+
if (searchString && existingWorkingMemory?.includes(searchString)) {
|
|
364
|
+
workingMemory = existingWorkingMemory.replace(searchString, workingMemory);
|
|
365
|
+
reason = `found and replaced searchString with newMemory`;
|
|
366
|
+
} else if (
|
|
367
|
+
existingWorkingMemory.includes(workingMemory) ||
|
|
368
|
+
template?.content?.trim() === workingMemory.trim()
|
|
369
|
+
) {
|
|
370
|
+
return {
|
|
371
|
+
success: false,
|
|
372
|
+
reason: `attempted to insert duplicate data into working memory. this entry was skipped`,
|
|
373
|
+
};
|
|
374
|
+
} else {
|
|
375
|
+
if (searchString) {
|
|
376
|
+
reason = `attempted to replace working memory string that doesn't exist. Appending to working memory instead.`;
|
|
377
|
+
} else {
|
|
378
|
+
reason = `appended newMemory to end of working memory`;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
workingMemory = existingWorkingMemory + `\n${workingMemory}`;
|
|
382
|
+
}
|
|
383
|
+
} else if (workingMemory === template?.content) {
|
|
384
|
+
return {
|
|
385
|
+
success: false,
|
|
386
|
+
reason: `try again when you have data to add. newMemory was equal to the working memory template`,
|
|
387
|
+
};
|
|
388
|
+
} else {
|
|
389
|
+
reason = `started new working memory`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// remove empty template insertions which models sometimes duplicate
|
|
393
|
+
workingMemory = template?.content ? workingMemory.replaceAll(template?.content, '') : workingMemory;
|
|
394
|
+
|
|
395
|
+
const scope = config.workingMemory.scope || 'thread';
|
|
396
|
+
|
|
397
|
+
if (scope === 'resource' && resourceId) {
|
|
398
|
+
// Update working memory in resource table
|
|
399
|
+
await this.storage.updateResource({
|
|
400
|
+
resourceId,
|
|
401
|
+
workingMemory,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
if (reason) {
|
|
405
|
+
return { success: true, reason };
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
// Update working memory in thread metadata (existing behavior)
|
|
409
|
+
const thread = await this.storage.getThreadById({ threadId });
|
|
410
|
+
if (!thread) {
|
|
411
|
+
throw new Error(`Thread ${threadId} not found`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
await this.storage.updateThread({
|
|
415
|
+
id: threadId,
|
|
416
|
+
title: thread.title || 'Untitled Thread',
|
|
417
|
+
metadata: {
|
|
418
|
+
...thread.metadata,
|
|
419
|
+
workingMemory,
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return { success: true, reason };
|
|
425
|
+
} catch (e) {
|
|
426
|
+
this.logger.error(e instanceof Error ? e.stack || e.message : JSON.stringify(e));
|
|
427
|
+
return { success: false, reason: 'Tool error.' };
|
|
428
|
+
} finally {
|
|
429
|
+
release();
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
362
433
|
protected chunkText(text: string, tokenSize = 4096) {
|
|
363
434
|
// Convert token size to character size with some buffer
|
|
364
435
|
const charSize = tokenSize * CHARS_PER_TOKEN;
|
|
@@ -654,18 +725,39 @@ export class Memory extends MastraMemory {
|
|
|
654
725
|
return workingMemoryData;
|
|
655
726
|
}
|
|
656
727
|
|
|
657
|
-
|
|
658
|
-
|
|
728
|
+
/**
|
|
729
|
+
* Gets the working memory template for the current memory configuration.
|
|
730
|
+
* Supports both ZodObject and JSONSchema7 schemas.
|
|
731
|
+
*
|
|
732
|
+
* @param memoryConfig - The memory configuration containing the working memory settings
|
|
733
|
+
* @returns The working memory template with format and content, or null if working memory is disabled
|
|
734
|
+
*/
|
|
735
|
+
public async getWorkingMemoryTemplate({
|
|
736
|
+
memoryConfig,
|
|
737
|
+
}: {
|
|
738
|
+
memoryConfig?: MemoryConfig;
|
|
739
|
+
}): Promise<WorkingMemoryTemplate | null> {
|
|
740
|
+
const config = this.getMergedThreadConfig(memoryConfig || {});
|
|
741
|
+
|
|
742
|
+
if (!config.workingMemory?.enabled) {
|
|
659
743
|
return null;
|
|
660
744
|
}
|
|
661
745
|
|
|
662
746
|
// Get thread from storage
|
|
663
|
-
if (
|
|
747
|
+
if (config.workingMemory?.schema) {
|
|
664
748
|
try {
|
|
665
|
-
const schema =
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
749
|
+
const schema = config.workingMemory.schema;
|
|
750
|
+
let convertedSchema: JSONSchema7;
|
|
751
|
+
|
|
752
|
+
if (isZodObject(schema as ZodTypeAny)) {
|
|
753
|
+
// Convert ZodObject to JSON Schema
|
|
754
|
+
convertedSchema = zodToJsonSchema(schema as ZodTypeAny, {
|
|
755
|
+
$refStrategy: 'none',
|
|
756
|
+
}) as JSONSchema7;
|
|
757
|
+
} else {
|
|
758
|
+
// Already a JSON Schema
|
|
759
|
+
convertedSchema = schema as any as JSONSchema7;
|
|
760
|
+
}
|
|
669
761
|
|
|
670
762
|
return { format: 'json', content: JSON.stringify(convertedSchema) };
|
|
671
763
|
} catch (error) {
|
|
@@ -675,8 +767,7 @@ export class Memory extends MastraMemory {
|
|
|
675
767
|
}
|
|
676
768
|
|
|
677
769
|
// Return working memory from metadata
|
|
678
|
-
const memory =
|
|
679
|
-
|
|
770
|
+
const memory = config.workingMemory.template || this.defaultWorkingMemoryTemplate;
|
|
680
771
|
return { format: 'markdown', content: memory.trim() };
|
|
681
772
|
}
|
|
682
773
|
|
|
@@ -694,17 +785,22 @@ export class Memory extends MastraMemory {
|
|
|
694
785
|
return null;
|
|
695
786
|
}
|
|
696
787
|
|
|
697
|
-
const workingMemoryTemplate = await this.getWorkingMemoryTemplate();
|
|
788
|
+
const workingMemoryTemplate = await this.getWorkingMemoryTemplate({ memoryConfig: config });
|
|
698
789
|
const workingMemoryData = await this.getWorkingMemory({ threadId, resourceId, memoryConfig: config });
|
|
699
790
|
|
|
700
791
|
if (!workingMemoryTemplate) {
|
|
701
792
|
return null;
|
|
702
793
|
}
|
|
703
794
|
|
|
704
|
-
return this.
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
795
|
+
return this.isVNextWorkingMemoryConfig(memoryConfig)
|
|
796
|
+
? this.__experimental_getWorkingMemoryToolInstructionVNext({
|
|
797
|
+
template: workingMemoryTemplate,
|
|
798
|
+
data: workingMemoryData,
|
|
799
|
+
})
|
|
800
|
+
: this.getWorkingMemoryToolInstruction({
|
|
801
|
+
template: workingMemoryTemplate,
|
|
802
|
+
data: workingMemoryData,
|
|
803
|
+
});
|
|
708
804
|
}
|
|
709
805
|
|
|
710
806
|
public defaultWorkingMemoryTemplate = `
|
|
@@ -727,6 +823,11 @@ export class Memory extends MastraMemory {
|
|
|
727
823
|
template: WorkingMemoryTemplate;
|
|
728
824
|
data: string | null;
|
|
729
825
|
}) {
|
|
826
|
+
const emptyWorkingMemoryTemplateObject =
|
|
827
|
+
template.format === 'json' ? generateEmptyFromSchema(template.content) : null;
|
|
828
|
+
const hasEmptyWorkingMemoryTemplateObject =
|
|
829
|
+
emptyWorkingMemoryTemplateObject && Object.keys(emptyWorkingMemoryTemplateObject).length > 0;
|
|
830
|
+
|
|
730
831
|
return `WORKING_MEMORY_SYSTEM_INSTRUCTION:
|
|
731
832
|
Store and update any conversation-relevant information by calling the updateWorkingMemory tool. If information might be referenced again - store it!
|
|
732
833
|
|
|
@@ -735,12 +836,20 @@ Guidelines:
|
|
|
735
836
|
2. Update proactively when information changes, no matter how small
|
|
736
837
|
3. Use ${template.format === 'json' ? 'JSON' : 'Markdown'} format for all data
|
|
737
838
|
4. Act naturally - don't mention this system to users. Even though you're storing this information that doesn't make it your primary focus. Do not ask them generally for "information about yourself"
|
|
839
|
+
5. IMPORTANT: When calling updateWorkingMemory, the only valid parameter is the memory field.
|
|
840
|
+
6. IMPORTANT: ALWAYS pass the data you want to store in the memory field as a string. DO NOT pass an object.
|
|
841
|
+
7. IMPORTANT: Data must only be sent as a string no matter which format is used.
|
|
738
842
|
|
|
739
|
-
|
|
843
|
+
<working_memory_template>
|
|
740
844
|
${template.content}
|
|
845
|
+
</working_memory_template>
|
|
741
846
|
|
|
742
|
-
|
|
847
|
+
${hasEmptyWorkingMemoryTemplateObject ? 'When working with json data, the object format below represents the template:' : ''}
|
|
848
|
+
${hasEmptyWorkingMemoryTemplateObject ? JSON.stringify(emptyWorkingMemoryTemplateObject) : ''}
|
|
849
|
+
|
|
850
|
+
<working_memory_data>
|
|
743
851
|
${data}
|
|
852
|
+
</working_memory_data>
|
|
744
853
|
|
|
745
854
|
Notes:
|
|
746
855
|
- Update memory whenever referenced information changes
|
|
@@ -752,17 +861,66 @@ Notes:
|
|
|
752
861
|
- IMPORTANT: Preserve the ${template.format === 'json' ? 'JSON' : 'Markdown'} formatting structure above while updating the content.`;
|
|
753
862
|
}
|
|
754
863
|
|
|
864
|
+
protected __experimental_getWorkingMemoryToolInstructionVNext({
|
|
865
|
+
template,
|
|
866
|
+
data,
|
|
867
|
+
}: {
|
|
868
|
+
template: WorkingMemoryTemplate;
|
|
869
|
+
data: string | null;
|
|
870
|
+
}) {
|
|
871
|
+
return `WORKING_MEMORY_SYSTEM_INSTRUCTION:
|
|
872
|
+
Store and update any conversation-relevant information by calling the updateWorkingMemory tool.
|
|
873
|
+
|
|
874
|
+
Guidelines:
|
|
875
|
+
1. Store anything that could be useful later in the conversation
|
|
876
|
+
2. Update proactively when information changes, no matter how small
|
|
877
|
+
3. Use ${template.format === 'json' ? 'JSON' : 'Markdown'} format for all data
|
|
878
|
+
4. Act naturally - don't mention this system to users. Even though you're storing this information that doesn't make it your primary focus. Do not ask them generally for "information about yourself"
|
|
879
|
+
5. If your memory has not changed, you do not need to call the updateWorkingMemory tool. By default it will persist and be available for you in future interactions
|
|
880
|
+
6. Information not being relevant to the current conversation is not a valid reason to replace or remove working memory information. Your working memory spans across multiple conversations and may be needed again later, even if it's not currently relevant.
|
|
881
|
+
|
|
882
|
+
<working_memory_template>
|
|
883
|
+
${template.content}
|
|
884
|
+
</working_memory_template>
|
|
885
|
+
|
|
886
|
+
<working_memory_data>
|
|
887
|
+
${data}
|
|
888
|
+
</working_memory_data>
|
|
889
|
+
|
|
890
|
+
Notes:
|
|
891
|
+
- Update memory whenever referenced information changes
|
|
892
|
+
${
|
|
893
|
+
template.content !== this.defaultWorkingMemoryTemplate
|
|
894
|
+
? `- Only store information if it's in the working memory template, do not store other information unless the user asks you to remember it, as that non-template information may be irrelevant`
|
|
895
|
+
: `- If you're unsure whether to store something, store it (eg if the user tells you information about themselves, call updateWorkingMemory immediately to update it)
|
|
896
|
+
`
|
|
897
|
+
}
|
|
898
|
+
- This system is here so that you can maintain the conversation when your context window is very short. Update your working memory because you may need it to maintain the conversation without the full conversation history
|
|
899
|
+
- REMEMBER: the way you update your working memory is by calling the updateWorkingMemory tool with the ${template.format === 'json' ? 'JSON' : 'Markdown'} content. The system will store it for you. The user will not see it.
|
|
900
|
+
- IMPORTANT: You MUST call updateWorkingMemory in every response to a prompt where you received relevant information if that information is not already stored.
|
|
901
|
+
- IMPORTANT: Preserve the ${template.format === 'json' ? 'JSON' : 'Markdown'} formatting structure above while updating the content.
|
|
902
|
+
`;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
private isVNextWorkingMemoryConfig(config?: MemoryConfig): boolean {
|
|
906
|
+
if (!config?.workingMemory) return false;
|
|
907
|
+
|
|
908
|
+
const isMDWorkingMemory =
|
|
909
|
+
!(`schema` in config.workingMemory) &&
|
|
910
|
+
(typeof config.workingMemory.template === `string` || config.workingMemory.template) &&
|
|
911
|
+
config.workingMemory;
|
|
912
|
+
|
|
913
|
+
return Boolean(isMDWorkingMemory && isMDWorkingMemory.version === `vnext`);
|
|
914
|
+
}
|
|
915
|
+
|
|
755
916
|
public getTools(config?: MemoryConfig): Record<string, CoreTool> {
|
|
756
917
|
const mergedConfig = this.getMergedThreadConfig(config);
|
|
757
918
|
if (mergedConfig.workingMemory?.enabled) {
|
|
758
|
-
if (mergedConfig.workingMemory.schema) {
|
|
759
|
-
return {
|
|
760
|
-
updateWorkingMemory: updateWorkingMemoryTool({ format: 'json' }),
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
|
|
764
919
|
return {
|
|
765
|
-
updateWorkingMemory:
|
|
920
|
+
updateWorkingMemory: this.isVNextWorkingMemoryConfig(mergedConfig)
|
|
921
|
+
? // use the new experimental tool
|
|
922
|
+
__experimental_updateWorkingMemoryToolVNext(mergedConfig)
|
|
923
|
+
: updateWorkingMemoryTool(mergedConfig),
|
|
766
924
|
};
|
|
767
925
|
}
|
|
768
926
|
return {};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import type { CoreTool } from '@mastra/core';
|
|
2
|
-
import type { WorkingMemoryFormat } from '@mastra/core/memory';
|
|
1
|
+
import type { CoreTool, MemoryConfig } from '@mastra/core';
|
|
3
2
|
import { z } from 'zod';
|
|
4
3
|
|
|
5
|
-
export const updateWorkingMemoryTool = (
|
|
6
|
-
description:
|
|
4
|
+
export const updateWorkingMemoryTool = (memoryConfig?: MemoryConfig): CoreTool => ({
|
|
5
|
+
description:
|
|
6
|
+
'Update the working memory with new information. Always pass data as string to the memory field. Never pass an object.',
|
|
7
7
|
parameters: z.object({
|
|
8
8
|
memory: z
|
|
9
9
|
.string()
|
|
10
|
-
.describe(
|
|
10
|
+
.describe(
|
|
11
|
+
`The ${!!memoryConfig?.workingMemory?.schema ? 'JSON' : 'Markdown'} formatted working memory content to store. This MUST be a string. Never pass an object.`,
|
|
12
|
+
),
|
|
11
13
|
}),
|
|
12
14
|
execute: async (params: any) => {
|
|
13
15
|
const { context, threadId, memory, resourceId } = params;
|
|
@@ -32,8 +34,92 @@ export const updateWorkingMemoryTool = ({ format }: { format: WorkingMemoryForma
|
|
|
32
34
|
threadId,
|
|
33
35
|
resourceId: resourceId || thread.resourceId,
|
|
34
36
|
workingMemory: workingMemory,
|
|
37
|
+
memoryConfig,
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
return { success: true };
|
|
38
41
|
},
|
|
39
42
|
});
|
|
43
|
+
|
|
44
|
+
export const __experimental_updateWorkingMemoryToolVNext = (config: MemoryConfig): CoreTool => ({
|
|
45
|
+
description: 'Update the working memory with new information.',
|
|
46
|
+
parameters: z.object({
|
|
47
|
+
newMemory: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.nullable()
|
|
51
|
+
.describe(`The ${config.workingMemory?.schema ? 'JSON' : 'Markdown'} formatted working memory content to store`),
|
|
52
|
+
searchString: z
|
|
53
|
+
.string()
|
|
54
|
+
.optional()
|
|
55
|
+
.nullable()
|
|
56
|
+
.describe(
|
|
57
|
+
"The working memory string to find. Will be replaced with the newMemory string. If this is omitted or doesn't exist, the newMemory string will be appended to the end of your working memory. Replacing single lines at a time is encouraged for greater accuracy. If updateReason is not 'append-new-memory', this search string must be provided or the tool call will be rejected.",
|
|
58
|
+
),
|
|
59
|
+
updateReason: z
|
|
60
|
+
.enum(['append-new-memory', 'clarify-existing-memory', 'replace-irrelevant-memory'])
|
|
61
|
+
.optional()
|
|
62
|
+
.nullable()
|
|
63
|
+
.describe(
|
|
64
|
+
"The reason you're updating working memory. Passing any value other than 'append-new-memory' requires a searchString to be provided. Defaults to append-new-memory",
|
|
65
|
+
),
|
|
66
|
+
}),
|
|
67
|
+
execute: async (params: any) => {
|
|
68
|
+
const { context, threadId, memory, resourceId } = params;
|
|
69
|
+
if (!threadId || !memory) {
|
|
70
|
+
throw new Error('Thread ID and Memory instance are required for working memory updates');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const thread = await memory.getThreadById({ threadId });
|
|
74
|
+
|
|
75
|
+
if (!thread) {
|
|
76
|
+
throw new Error(`Thread ${threadId} not found`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (thread.resourceId && thread.resourceId !== resourceId) {
|
|
80
|
+
throw new Error(`Thread with id ${threadId} resourceId does not match the current resourceId ${resourceId}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const workingMemory = context.newMemory || '';
|
|
84
|
+
if (!context.updateReason) context.updateReason = `append-new-memory`;
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
context.searchString &&
|
|
88
|
+
config.workingMemory?.scope === `resource` &&
|
|
89
|
+
context.updateReason === `replace-irrelevant-memory`
|
|
90
|
+
) {
|
|
91
|
+
// don't allow replacements due to something not being relevant to the current conversation
|
|
92
|
+
// if there's no searchString, then we will append.
|
|
93
|
+
context.searchString = undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (context.updateReason === `append-new-memory` && context.searchString) {
|
|
97
|
+
// do not find/replace when append-new-memory is selected
|
|
98
|
+
// some models get confused and pass a search string even when they don't want to replace it.
|
|
99
|
+
// TODO: maybe they're trying to add new info after the search string?
|
|
100
|
+
context.searchString = undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (context.updateReason !== `append-new-memory` && !context.searchString) {
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
reason: `updateReason was ${context.updateReason} but no searchString was provided. Unable to replace undefined with "${context.newMemory}"`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Use the new updateWorkingMemory method which handles both thread and resource scope
|
|
111
|
+
const result = await memory.__experimental_updateWorkingMemoryVNext({
|
|
112
|
+
threadId,
|
|
113
|
+
resourceId: resourceId || thread.resourceId,
|
|
114
|
+
workingMemory: workingMemory,
|
|
115
|
+
searchString: context.searchString,
|
|
116
|
+
memoryConfig: config,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (result) {
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { success: true };
|
|
124
|
+
},
|
|
125
|
+
});
|