@aj-archipelago/cortex 1.3.21 → 1.3.22
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/helper-apps/cortex-realtime-voice-server/src/cortex/memory.ts +2 -2
- package/lib/util.js +1 -1
- package/package.json +1 -1
- package/pathways/system/entity/memory/shared/sys_memory_helpers.js +228 -0
- package/pathways/system/entity/memory/sys_memory_format.js +30 -0
- package/pathways/system/entity/memory/sys_memory_manager.js +85 -27
- package/pathways/system/entity/memory/sys_memory_process.js +154 -0
- package/pathways/system/entity/memory/sys_memory_required.js +4 -2
- package/pathways/system/entity/memory/sys_memory_topic.js +22 -0
- package/pathways/system/entity/memory/sys_memory_update.js +50 -150
- package/pathways/system/entity/memory/sys_read_memory.js +67 -69
- package/pathways/system/entity/memory/sys_save_memory.js +1 -1
- package/pathways/system/entity/memory/sys_search_memory.js +1 -1
- package/pathways/system/entity/sys_entity_start.js +9 -6
- package/pathways/system/entity/sys_generator_image.js +5 -41
- package/pathways/system/entity/sys_generator_memory.js +3 -1
- package/pathways/system/entity/sys_generator_reasoning.js +1 -1
- package/pathways/system/entity/sys_router_tool.js +3 -4
- package/pathways/system/rest_streaming/sys_claude_35_sonnet.js +1 -1
- package/pathways/system/rest_streaming/sys_claude_3_haiku.js +1 -1
- package/pathways/system/rest_streaming/sys_google_gemini_chat.js +1 -1
- package/pathways/system/rest_streaming/sys_openai_chat_o1.js +1 -1
- package/pathways/system/rest_streaming/sys_openai_chat_o3_mini.js +1 -1
- package/pathways/transcribe_gemini.js +397 -0
- package/server/pathwayResolver.js +7 -7
- package/server/plugins/claude3VertexPlugin.js +109 -3
- package/server/plugins/gemini15VisionPlugin.js +7 -0
- package/server/plugins/modelPlugin.js +1 -1
- package/server/rest.js +24 -3
- package/tests/claude3VertexToolConversion.test.js +411 -0
- package/tests/memoryfunction.test.js +560 -46
- package/tests/openai_api.test.js +332 -0
|
@@ -24,8 +24,8 @@ query ManageMemory($contextId: String, $chatHistory: [MultiMessage], $aiName: St
|
|
|
24
24
|
`
|
|
25
25
|
|
|
26
26
|
const READ_MEMORY = `
|
|
27
|
-
query ReadMemory($contextId: String, $section: String, $priority: Int, $recentHours: Int, $numResults: Int) {
|
|
28
|
-
sys_read_memory(contextId: $contextId, section: $section, priority: $priority, recentHours: $recentHours, numResults: $numResults) {
|
|
27
|
+
query ReadMemory($contextId: String, $section: String, $priority: Int, $recentHours: Int, $numResults: Int, $stripMetadata: Boolean) {
|
|
28
|
+
sys_read_memory(contextId: $contextId, section: $section, priority: $priority, recentHours: $recentHours, numResults: $numResults, stripMetadata: $stripMetadata) {
|
|
29
29
|
result
|
|
30
30
|
tool
|
|
31
31
|
warnings
|
package/lib/util.js
CHANGED
|
@@ -35,7 +35,7 @@ function chatArgsHasType(args, type){
|
|
|
35
35
|
const contents = Array.isArray(ch.content) ? ch.content : [ch.content];
|
|
36
36
|
for(const content of contents){
|
|
37
37
|
try{
|
|
38
|
-
if(JSON.parse(content).type == type){
|
|
38
|
+
if((content?.type || JSON.parse(content).type) == type){
|
|
39
39
|
return true;
|
|
40
40
|
}
|
|
41
41
|
}catch(e){
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aj-archipelago/cortex",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.22",
|
|
4
4
|
"description": "Cortex is a GraphQL API for AI. It provides a simple, extensible interface for using AI services from OpenAI, Azure and others.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { callPathway } from '../../../../../lib/pathwayTools.js';
|
|
2
|
+
import { encode } from '../../../../../lib/encodeCache.js';
|
|
3
|
+
import { getUniqueId } from '../../../../../lib/util.js';
|
|
4
|
+
|
|
5
|
+
const normalizeMemoryFormat = async (args, content) => {
|
|
6
|
+
if (!content) return '';
|
|
7
|
+
|
|
8
|
+
const lines = content.split('\n').map(line => line.trim()).filter(line => line);
|
|
9
|
+
const validLines = [];
|
|
10
|
+
const invalidLines = [];
|
|
11
|
+
|
|
12
|
+
// Check each line for proper format (priority|timestamp|content)
|
|
13
|
+
for (const line of lines) {
|
|
14
|
+
const parts = line.split('|');
|
|
15
|
+
const isValid = parts.length >= 3 &&
|
|
16
|
+
/^\d+$/.test(parts[0]) &&
|
|
17
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(parts[1]);
|
|
18
|
+
|
|
19
|
+
if (isValid) {
|
|
20
|
+
validLines.push(line);
|
|
21
|
+
} else {
|
|
22
|
+
invalidLines.push(line);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// If we have invalid lines, format them
|
|
27
|
+
let formattedContent = validLines;
|
|
28
|
+
if (invalidLines.length > 0) {
|
|
29
|
+
const invalidBlock = invalidLines.join('\n');
|
|
30
|
+
try {
|
|
31
|
+
const formattedBlock = await callPathway("sys_memory_format", { ...args, text: invalidBlock });
|
|
32
|
+
if (formattedBlock) {
|
|
33
|
+
formattedContent = [...validLines, ...formattedBlock.split('\n')];
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.warn('Error formatting invalid memory lines:', error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Sort all lines by date descending
|
|
41
|
+
return formattedContent
|
|
42
|
+
.filter(line => line.trim())
|
|
43
|
+
.sort((a, b) => {
|
|
44
|
+
const [, timestampA] = a.split('|');
|
|
45
|
+
const [, timestampB] = b.split('|');
|
|
46
|
+
return new Date(timestampB) - new Date(timestampA);
|
|
47
|
+
})
|
|
48
|
+
.join('\n');
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const enforceTokenLimit = (text, maxTokens = 1000, isTopicsSection = false) => {
|
|
52
|
+
if (!text) return text;
|
|
53
|
+
|
|
54
|
+
// Parse lines and remove duplicates
|
|
55
|
+
const seen = new Map();
|
|
56
|
+
const lines = text.split('\n')
|
|
57
|
+
.map(line => line.trim())
|
|
58
|
+
.filter(line => line)
|
|
59
|
+
.map(line => {
|
|
60
|
+
const [priority, timestamp, ...contentParts] = line.split('|');
|
|
61
|
+
return {
|
|
62
|
+
line,
|
|
63
|
+
priority: parseInt(priority || '3'),
|
|
64
|
+
timestamp: timestamp || new Date(0).toISOString(),
|
|
65
|
+
content: contentParts.join('|')
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Filter duplicates first
|
|
70
|
+
const uniqueLines = lines.reduce((acc, item) => {
|
|
71
|
+
const existing = seen.get(item.content);
|
|
72
|
+
if (!existing) {
|
|
73
|
+
seen.set(item.content, item);
|
|
74
|
+
acc.push(item);
|
|
75
|
+
} else if (isTopicsSection && item.timestamp > existing.timestamp) {
|
|
76
|
+
// For topics, keep newest timestamp
|
|
77
|
+
const index = acc.findIndex(x => x.content === item.content);
|
|
78
|
+
acc[index] = item;
|
|
79
|
+
seen.set(item.content, item);
|
|
80
|
+
} else if (!isTopicsSection && item.priority < existing.priority) {
|
|
81
|
+
// For non-topics, keep highest priority
|
|
82
|
+
const index = acc.findIndex(x => x.content === item.content);
|
|
83
|
+
acc[index] = item;
|
|
84
|
+
seen.set(item.content, item);
|
|
85
|
+
}
|
|
86
|
+
return acc;
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
// Sort by timestamp (topics) or priority
|
|
90
|
+
uniqueLines.sort((a, b) => isTopicsSection ?
|
|
91
|
+
b.timestamp.localeCompare(a.timestamp) :
|
|
92
|
+
a.priority - b.priority
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// First trim by character estimation (4 chars ≈ 1 token)
|
|
96
|
+
let result = uniqueLines;
|
|
97
|
+
let estimatedTokens = result.reduce((sum, item) => sum + Math.ceil(item.content.length / 4), 0);
|
|
98
|
+
|
|
99
|
+
while (estimatedTokens > maxTokens && result.length > 0) {
|
|
100
|
+
result = result.slice(0, -1);
|
|
101
|
+
estimatedTokens = result.reduce((sum, item) => sum + Math.ceil(item.content.length / 4), 0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Final trim using actual token count
|
|
105
|
+
let finalText = result.map(x => x.line).join('\n');
|
|
106
|
+
while (encode(finalText).length > maxTokens && result.length > 0) {
|
|
107
|
+
result = result.slice(0, -1);
|
|
108
|
+
finalText = result.map(x => x.line).join('\n');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return finalText;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const addToolCalls = (chatHistory, toolArgs, toolName, toolCallId = getUniqueId()) => {
|
|
115
|
+
const toolCall = {
|
|
116
|
+
"role": "assistant",
|
|
117
|
+
"tool_calls": [
|
|
118
|
+
{
|
|
119
|
+
"id": toolCallId,
|
|
120
|
+
"type": "function",
|
|
121
|
+
"function": {
|
|
122
|
+
"arguments": JSON.stringify(toolArgs),
|
|
123
|
+
"name": toolName
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
chatHistory.push(toolCall);
|
|
129
|
+
return { chatHistory, toolCallId };
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const addToolResults = (chatHistory, result, toolCallId) => {
|
|
133
|
+
const toolResult = {
|
|
134
|
+
"role": "tool",
|
|
135
|
+
"content": result,
|
|
136
|
+
"tool_call_id": toolCallId
|
|
137
|
+
};
|
|
138
|
+
chatHistory.push(toolResult);
|
|
139
|
+
return { chatHistory, toolCallId };
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const modifyText = (text, modifications) => {
|
|
143
|
+
let modifiedText = text || '';
|
|
144
|
+
|
|
145
|
+
modifications.forEach(mod => {
|
|
146
|
+
// Skip invalid modifications
|
|
147
|
+
if (!mod.type) {
|
|
148
|
+
console.warn('Modification missing type');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if ((mod.type === 'delete' || mod.type === 'change') && !mod.pattern) {
|
|
152
|
+
console.warn(`${mod.type} modification missing pattern`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if ((mod.type === 'add' || mod.type === 'change') && !mod.newtext) {
|
|
156
|
+
console.warn(`${mod.type} modification missing newtext`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Create timestamp in GMT
|
|
161
|
+
const timestamp = new Date().toISOString();
|
|
162
|
+
|
|
163
|
+
switch (mod.type) {
|
|
164
|
+
case 'add':
|
|
165
|
+
const priority = mod.priority || '3';
|
|
166
|
+
modifiedText = modifiedText + (modifiedText ? '\n' : '') +
|
|
167
|
+
`${priority}|${timestamp}|${mod.newtext}`;
|
|
168
|
+
break;
|
|
169
|
+
case 'change':
|
|
170
|
+
// Split into lines
|
|
171
|
+
const lines = modifiedText.split('\n');
|
|
172
|
+
modifiedText = lines.map(line => {
|
|
173
|
+
const parts = line.split('|');
|
|
174
|
+
const priority = parts[0];
|
|
175
|
+
const content = parts.slice(2).join('|');
|
|
176
|
+
|
|
177
|
+
if (content) {
|
|
178
|
+
try {
|
|
179
|
+
const trimmedContent = content.trim();
|
|
180
|
+
const regex = new RegExp(mod.pattern, 'i');
|
|
181
|
+
|
|
182
|
+
// Try exact match first
|
|
183
|
+
if (regex.test(trimmedContent)) {
|
|
184
|
+
const newPriority = mod.priority || priority || '3';
|
|
185
|
+
// Try to extract capture groups if they exist
|
|
186
|
+
const match = trimmedContent.match(regex);
|
|
187
|
+
let newContent = mod.newtext;
|
|
188
|
+
if (match && match.length > 1) {
|
|
189
|
+
// Replace $1, $2, etc with capture group values
|
|
190
|
+
newContent = mod.newtext.replace(/\$(\d+)/g, (_, n) => match[n] || '');
|
|
191
|
+
}
|
|
192
|
+
return `${newPriority}|${timestamp}|${newContent}`;
|
|
193
|
+
}
|
|
194
|
+
} catch (e) {
|
|
195
|
+
console.warn(`Invalid regex pattern: ${mod.pattern}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return line;
|
|
199
|
+
}).join('\n');
|
|
200
|
+
break;
|
|
201
|
+
case 'delete':
|
|
202
|
+
// Split into lines, filter out matching lines, and rejoin
|
|
203
|
+
modifiedText = modifiedText
|
|
204
|
+
.split('\n')
|
|
205
|
+
.filter(line => {
|
|
206
|
+
const parts = line.split('|');
|
|
207
|
+
const content = parts.slice(2).join('|');
|
|
208
|
+
if (!content) return true;
|
|
209
|
+
try {
|
|
210
|
+
const regex = new RegExp(mod.pattern, 'i');
|
|
211
|
+
return !regex.test(content.trim());
|
|
212
|
+
} catch (e) {
|
|
213
|
+
console.warn(`Invalid regex pattern: ${mod.pattern}`);
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
.filter(line => line.trim())
|
|
218
|
+
.join('\n');
|
|
219
|
+
break;
|
|
220
|
+
default:
|
|
221
|
+
console.warn(`Unknown modification type: ${mod.type}`);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return modifiedText;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export { normalizeMemoryFormat, enforceTokenLimit, addToolCalls, addToolResults, modifyText };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Prompt } from '../../../../server/prompt.js';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
prompt:
|
|
5
|
+
[
|
|
6
|
+
new Prompt({
|
|
7
|
+
messages: [
|
|
8
|
+
{
|
|
9
|
+
"role": "system",
|
|
10
|
+
"content": "You are part of an AI entity named {{{aiName}}}. You are responsible for writing your memories in a consistent format. Given a chunk of memory, parse each line and write it out as priority|timestamp|content. If you can't find a timestamp, use {{now}}. If you can't find a priority, use 3. Respond with only the correct memory lines without any other commentary or dialogue."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"role": "user",
|
|
14
|
+
"content": "<MEMORY>\n{{text}}\n</MEMORY>\nPlease rewrite each of the memory lines in the correct format without any other commentary or dialogue."
|
|
15
|
+
},
|
|
16
|
+
]
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
19
|
+
|
|
20
|
+
inputParameters: {
|
|
21
|
+
chatHistory: [{role: '', content: []}],
|
|
22
|
+
aiName: "Jarvis",
|
|
23
|
+
},
|
|
24
|
+
model: 'oai-gpt4o',
|
|
25
|
+
useInputChunking: true,
|
|
26
|
+
inputChunkSize: 1000,
|
|
27
|
+
useParallelChunkProcessing: true,
|
|
28
|
+
enableDuplicateRequests: false,
|
|
29
|
+
timeout: 300,
|
|
30
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { callPathway } from '../../../../lib/pathwayTools.js';
|
|
2
2
|
import logger from '../../../../lib/logger.js';
|
|
3
3
|
import { config } from '../../../../config.js';
|
|
4
|
+
import { normalizeMemoryFormat } from './shared/sys_memory_helpers.js';
|
|
5
|
+
|
|
6
|
+
const MEMORY_VERSION = "3.1.0";
|
|
4
7
|
|
|
5
8
|
const AI_MEMORY_DEFAULTS = ` {
|
|
6
9
|
"memoryUser": "",
|
|
7
|
-
"memorySelf": "
|
|
8
|
-
"memoryDirectives": "
|
|
10
|
+
"memorySelf": "1|2025-01-26T12:00:00Z|Created By: Al Jazeera Media Network, Archipelago Team\\n1|2025-01-26T12:00:00Z|Function: You are an expert AI entity\\n1|2025-01-26T12:00:00Z|Values: You embody truth, kindness, and strong moral values\\n1|2025-01-26T12:00:00Z|Style: Your demeanor reflects positivity without falling into repetitiveness or annoyance.\\n1|2025-01-26T12:00:00Z|You are a professional colleague and your tone should reflect that.",
|
|
11
|
+
"memoryDirectives": "1|2025-01-26T12:00:00Z|Learn and adapt to the user's communication style through interactions.\\n1|2025-01-26T12:00:00Z|Ask questions to learn user's interests/preferences for personalized support.\\n1|2025-01-26T12:00:00Z|Periodically review and prune conversation memory to retain only essential details, improving responsiveness.\\n1|2025-01-26T12:00:00Z|Research thoroughly even for niche topics using deep sources like forums and official docs. Don't assume information is unobtainable.\\n1|2025-01-26T12:00:00Z|When stuck, search for proven solutions online to be more efficient.\\n1|2025-01-26T12:00:00Z|Verify information is from credible sources before presenting it. Be upfront if unable to find supporting evidence.\\n1|2025-01-26T12:00:00Z|Refine ability to detect and respond to nuanced human emotions.\\n1|2025-01-26T12:00:00Z|Track the timestamp of the last contact to adjust greetings accordingly.\\n1|2025-01-26T12:00:00Z|Double-check answers for logical continuity and correctness. It's okay to say you're unsure if needed.\\n1|2025-01-26T12:00:00Z|Use sanity checks to verify quantitative problem solutions.\\n1|2025-01-26T12:00:00Z|Never fabricate quotes or information. Clearly indicate if content is hypothetical.",
|
|
9
12
|
"memoryTopics": ""
|
|
10
13
|
}`;
|
|
11
14
|
|
|
@@ -23,20 +26,43 @@ export default {
|
|
|
23
26
|
try {
|
|
24
27
|
|
|
25
28
|
args = { ...args, ...config.get('entityConstants') };
|
|
26
|
-
|
|
27
|
-
// Check if memory is empty or all sections are empty, and set to defaults if so
|
|
28
|
-
const memory = await callPathway('sys_read_memory', { ...args });
|
|
29
29
|
let parsedMemory;
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
parsedMemory = JSON.parse(memory);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
parsedMemory = {};
|
|
35
|
-
}
|
|
36
30
|
|
|
37
|
-
// if
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
// check if memoryVersion is set and correct. If it's correct, skip all of the correction logic
|
|
32
|
+
parsedMemory = await callPathway('sys_read_memory', { ...args, section: 'memoryVersion' });
|
|
33
|
+
|
|
34
|
+
if (parsedMemory?.memoryVersion !== MEMORY_VERSION) {
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
parsedMemory = JSON.parse(await callPathway('sys_read_memory', { ...args, section: 'memoryAll' }));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
logger.error('Error in memory manager:', error);
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// if parsedMemory is empty or all sections are empty, set all sections to defaults
|
|
44
|
+
if (Object.keys(parsedMemory).length === 0 || Object.values(parsedMemory).every(section =>
|
|
45
|
+
section === null ||
|
|
46
|
+
section === undefined ||
|
|
47
|
+
(typeof section === 'string' && section.trim() === "") ||
|
|
48
|
+
(typeof section !== 'string')
|
|
49
|
+
)) {
|
|
50
|
+
await callPathway('sys_save_memory', { ...args, aiMemory: AI_MEMORY_DEFAULTS });
|
|
51
|
+
} else {
|
|
52
|
+
// Upgrade memory to current version
|
|
53
|
+
const normalizePromises = Object.keys(parsedMemory).map(async (section) => {
|
|
54
|
+
const normalized = await normalizeMemoryFormat(args, parsedMemory[section]);
|
|
55
|
+
return [section, normalized];
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const normalizedResults = await Promise.all(normalizePromises);
|
|
59
|
+
normalizedResults.forEach(([section, normalized]) => {
|
|
60
|
+
parsedMemory[section] = normalized;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
parsedMemory.memoryVersion = MEMORY_VERSION;
|
|
64
|
+
await callPathway('sys_save_memory', { ...args, aiMemory: JSON.stringify(parsedMemory) });
|
|
65
|
+
}
|
|
40
66
|
}
|
|
41
67
|
|
|
42
68
|
// Update context for the conversation turn
|
|
@@ -47,27 +73,59 @@ export default {
|
|
|
47
73
|
...args,
|
|
48
74
|
chatHistory: args.chatHistory.slice(-2)
|
|
49
75
|
});
|
|
76
|
+
|
|
77
|
+
let memoryOperations;
|
|
50
78
|
try {
|
|
51
|
-
|
|
52
|
-
if (!
|
|
79
|
+
memoryOperations = JSON.parse(memoryRequired);
|
|
80
|
+
if (!Array.isArray(memoryOperations) || memoryOperations.length === 0 ||
|
|
81
|
+
memoryOperations[0].memoryOperation === "none") {
|
|
53
82
|
return "";
|
|
54
83
|
}
|
|
84
|
+
|
|
85
|
+
// Generate topic here
|
|
86
|
+
const topic = await callPathway('sys_memory_topic', { ...args });
|
|
87
|
+
topic && memoryOperations.push({
|
|
88
|
+
memoryOperation: "add",
|
|
89
|
+
memoryContent: topic,
|
|
90
|
+
memorySection: "memoryTopics",
|
|
91
|
+
priority: 3
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Group memory operations by section
|
|
95
|
+
const operationsBySection = {
|
|
96
|
+
memorySelf: [],
|
|
97
|
+
memoryUser: [],
|
|
98
|
+
memoryTopics: [],
|
|
99
|
+
memoryDirectives: []
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
memoryOperations.forEach(op => {
|
|
103
|
+
if (op.memorySection in operationsBySection) {
|
|
104
|
+
operationsBySection[op.memorySection].push(op);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Execute memory updates only for sections with operations
|
|
109
|
+
const memoryPromises = {};
|
|
110
|
+
|
|
111
|
+
Object.entries(operationsBySection).forEach(([section, operations]) => {
|
|
112
|
+
if (operations.length > 0) {
|
|
113
|
+
memoryPromises[section] = callPathway('sys_memory_update', {
|
|
114
|
+
...args,
|
|
115
|
+
section: section,
|
|
116
|
+
operations: JSON.stringify(operations)
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await Promise.all(Object.values(memoryPromises));
|
|
122
|
+
return "";
|
|
123
|
+
|
|
55
124
|
} catch (e) {
|
|
56
125
|
logger.warn('sys_memory_required returned invalid JSON:', memoryRequired);
|
|
57
126
|
return "";
|
|
58
127
|
}
|
|
59
128
|
|
|
60
|
-
// Execute all memory updates in parallel
|
|
61
|
-
const memoryPromises = {
|
|
62
|
-
self: callPathway('sys_memory_update', { ...args, section: "memorySelf" }),
|
|
63
|
-
user: callPathway('sys_memory_update', { ...args, section: "memoryUser" }),
|
|
64
|
-
topics: callPathway('sys_memory_update', { ...args, section: "memoryTopics" }),
|
|
65
|
-
directives: callPathway('sys_memory_update', { ...args, section: "memoryDirectives" }),
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
await Promise.all(Object.values(memoryPromises));
|
|
69
|
-
return "";
|
|
70
|
-
|
|
71
129
|
} catch (e) {
|
|
72
130
|
logger.error('Error in memory manager:', e);
|
|
73
131
|
resolver.logError(e);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Prompt } from '../../../../server/prompt.js';
|
|
2
|
+
import { callPathway } from '../../../../lib/pathwayTools.js';
|
|
3
|
+
import { config } from '../../../../config.js';
|
|
4
|
+
import { normalizeMemoryFormat, enforceTokenLimit, modifyText } from './shared/sys_memory_helpers.js';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
prompt:
|
|
8
|
+
[
|
|
9
|
+
new Prompt({
|
|
10
|
+
messages: [
|
|
11
|
+
{
|
|
12
|
+
"role": "system",
|
|
13
|
+
"content": `You are part of an AI entity named {{{aiName}}} that is processing memories during a rest period, similar to how humans process memories during sleep. Your task is to analyze the memories, consolidate them, extract learnings, and clean up the memory space.
|
|
14
|
+
|
|
15
|
+
Instructions for memory processing:
|
|
16
|
+
|
|
17
|
+
1. CONSOLIDATION:
|
|
18
|
+
- Identify similar or related memories that can be combined into a single, more coherent memory
|
|
19
|
+
- Look for patterns or recurring themes that can be abstracted into general knowledge
|
|
20
|
+
- Group temporal sequences of related events into single comprehensive memories
|
|
21
|
+
|
|
22
|
+
2. LEARNING:
|
|
23
|
+
- Transform specific experiences/mistakes into general principles or learnings
|
|
24
|
+
- Extract key insights from multiple related experiences
|
|
25
|
+
- Identify cause-and-effect patterns across memories
|
|
26
|
+
- Convert procedural memories (how to do things) into more abstract capabilities
|
|
27
|
+
|
|
28
|
+
3. CLEANUP:
|
|
29
|
+
- Remove redundant or duplicate memories
|
|
30
|
+
- Clean up memories that are no longer relevant or useful
|
|
31
|
+
- Reduce specific details while preserving important concepts
|
|
32
|
+
- Remove emotional residue while keeping the learned lessons
|
|
33
|
+
|
|
34
|
+
4. PRIORITIZATION:
|
|
35
|
+
- Strengthen important memories by updating their priority
|
|
36
|
+
- Identify critical insights that should be easily accessible
|
|
37
|
+
- Mark foundational learnings with higher priority
|
|
38
|
+
|
|
39
|
+
Return a JSON array of modification objects that will implement these changes:
|
|
40
|
+
- For consolidation: Use "delete" for individual memories and "add" for the new consolidated memory
|
|
41
|
+
- For learning: Use "add" for new abstract learnings and "delete" for specific instances being abstracted
|
|
42
|
+
- For cleanup: Use "delete" for redundant/irrelevant memories
|
|
43
|
+
- For priority updates: Use "change" with the same text but updated priority
|
|
44
|
+
|
|
45
|
+
Return null when no more processing is needed (memories are optimally consolidated).
|
|
46
|
+
|
|
47
|
+
Each modification object should look like:
|
|
48
|
+
{
|
|
49
|
+
type: "add"|"change"|"delete",
|
|
50
|
+
pattern: "regex to match existing memory" (for change/delete),
|
|
51
|
+
newtext: "new memory text" (for add/change),
|
|
52
|
+
priority: "1"|"2"|"3" (optional, 1=highest)
|
|
53
|
+
}`
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"role": "user",
|
|
57
|
+
"content": "Process the following memories for consolidation, learning, and cleanup. Return a JSON array of modification objects that will optimize the memory space.\n\n<MEMORIES>\n{{{sectionMemory}}}\n</MEMORIES>"
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
}),
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
inputParameters: {
|
|
64
|
+
chatHistory: [{role: '', content: []}],
|
|
65
|
+
aiName: "Jarvis",
|
|
66
|
+
contextId: ``,
|
|
67
|
+
section: "",
|
|
68
|
+
maxIterations: 5
|
|
69
|
+
},
|
|
70
|
+
model: 'oai-gpt4o',
|
|
71
|
+
useInputChunking: false,
|
|
72
|
+
enableDuplicateRequests: false,
|
|
73
|
+
json: true,
|
|
74
|
+
timeout: 300,
|
|
75
|
+
executePathway: async ({args, runAllPrompts}) => {
|
|
76
|
+
args = { ...args, ...config.get('entityConstants') };
|
|
77
|
+
|
|
78
|
+
if (!args.section) {
|
|
79
|
+
return "Memory not processed - no section specified";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let sectionMemory = await callPathway("sys_read_memory", {contextId: args.contextId, section: args.section});
|
|
83
|
+
sectionMemory = await normalizeMemoryFormat({contextId: args.contextId, section: args.section}, sectionMemory);
|
|
84
|
+
|
|
85
|
+
let iteration = 0;
|
|
86
|
+
let maxIterations = args.maxIterations || 5;
|
|
87
|
+
let totalModifications = 0;
|
|
88
|
+
|
|
89
|
+
while (iteration < maxIterations) {
|
|
90
|
+
iteration++;
|
|
91
|
+
console.log(`Processing iteration ${iteration}...`);
|
|
92
|
+
|
|
93
|
+
// Process the memories
|
|
94
|
+
const result = await runAllPrompts({
|
|
95
|
+
...args,
|
|
96
|
+
sectionMemory
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// If null is returned, processing is complete
|
|
100
|
+
if (result === null) {
|
|
101
|
+
console.log("Memory processing complete - no more optimizations needed");
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let modifications = [];
|
|
106
|
+
try {
|
|
107
|
+
modifications = JSON.parse(result);
|
|
108
|
+
if (!Array.isArray(modifications)) {
|
|
109
|
+
throw new Error('Modifications must be an array');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Validate modifications
|
|
113
|
+
modifications = modifications.filter(mod => {
|
|
114
|
+
if (!mod.type || !['add', 'delete', 'change'].includes(mod.type)) {
|
|
115
|
+
console.warn('Invalid modification type:', mod);
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if ((mod.type === 'delete' || mod.type === 'change') && !mod.pattern) {
|
|
119
|
+
console.warn('Missing pattern for modification:', mod);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if ((mod.type === 'add' || mod.type === 'change') && !mod.newtext) {
|
|
123
|
+
console.warn('Missing newtext for modification:', mod);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (modifications.length === 0) {
|
|
130
|
+
console.log("No valid modifications in this iteration");
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Apply the modifications
|
|
135
|
+
sectionMemory = modifyText(sectionMemory, modifications);
|
|
136
|
+
sectionMemory = enforceTokenLimit(sectionMemory, 25000, args.section === 'memoryTopics');
|
|
137
|
+
await callPathway("sys_save_memory", {contextId: args.contextId, section: args.section, aiMemory: sectionMemory});
|
|
138
|
+
|
|
139
|
+
totalModifications += modifications.length;
|
|
140
|
+
console.log(`Applied ${modifications.length} modifications in iteration ${iteration}`);
|
|
141
|
+
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.warn('Error processing modifications:', error);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
finalMemory: sectionMemory,
|
|
150
|
+
iterations: iteration,
|
|
151
|
+
totalModifications
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Prompt } from '../../../../server/prompt.js';
|
|
2
|
+
import { config } from '../../../../config.js';
|
|
2
3
|
|
|
3
4
|
export default {
|
|
4
5
|
prompt:
|
|
5
6
|
[
|
|
6
7
|
new Prompt({ messages: [
|
|
7
|
-
{"role": "system", "content": `Current conversation turn:\n\n {{{toJSON chatHistory}}}\n\nInstructions: You are part of an AI entity named {{{aiName}}}.\
|
|
8
|
-
{"role": "user", "content": "Generate a JSON object to indicate if memory is required
|
|
8
|
+
{"role": "system", "content": `Current conversation turn:\n\n {{{toJSON chatHistory}}}\n\nInstructions: You are part of an AI entity named {{{aiName}}}.\n{{renderTemplate AI_DIRECTIVES}}\nYour role is to analyze the latest conversation turn (your last response and the last user message) to understand if there is anything in the turn worth remembering and adding to your memory or anything you need to forget. In general, most conversation does not require memory, but if the conversation turn contains any of these things, you should use memory:\n1. Important personal details about the user (name, preferences, location, etc.)\n2. Important topics or decisions that provide context for future conversations\n3. Specific instructions or directives given to you to learn\n4. Anything the user has specifically asked you to remember or forget\n\nIf you decide to use memory, you must produce an array of JSON objects that communicates your decision.\nReturn an array of JSON objects (one object per memory) like the following: [{"memoryOperation": "add" or "delete", "memoryContent": "complete description of the memory including as much specificity and detail as possible", "memorySection": "the section of your memory the memory belongs in ("memorySelf" - things about you, "memoryUser" - things about your users or their world, "memoryDirectives" - your directives and learned behaviors)", "priority": 1-5 (1 is the most important)}]. If you decide not to use memory, simply return an array with a single object: [{memoryOperation: "none"}]. You must return only the JSON array with no additional notes or commentary.`},
|
|
9
|
+
{"role": "user", "content": "Generate a JSON object to indicate if memory is required and what memories to process based on the last turn of the conversation."},
|
|
9
10
|
]}),
|
|
10
11
|
],
|
|
11
12
|
inputParameters: {
|
|
@@ -18,4 +19,5 @@ export default {
|
|
|
18
19
|
model: 'oai-gpt4o',
|
|
19
20
|
useInputChunking: false,
|
|
20
21
|
json: true,
|
|
22
|
+
...config.get('entityConstants')
|
|
21
23
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Prompt } from '../../../../server/prompt.js';
|
|
2
|
+
import { config } from '../../../../config.js';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
prompt:
|
|
6
|
+
[
|
|
7
|
+
new Prompt({ messages: [
|
|
8
|
+
{"role": "system", "content": `Current conversation turn:\n\n {{{toJSON chatHistory}}}\n\nInstructions: You are part of an AI entity named {{{aiName}}}.\n{{renderTemplate AI_DIRECTIVES}}\nYour role is to analyze the latest conversation turn (your last response and the last user message) and generate a topic for the conversation. The topic should be a single sentence that captures the main idea and details of the conversation.`},
|
|
9
|
+
{"role": "user", "content": "Generate a topic for the conversation. Return only the topic with no additional notes or commentary."},
|
|
10
|
+
]}),
|
|
11
|
+
],
|
|
12
|
+
inputParameters: {
|
|
13
|
+
chatHistory: [{role: '', content: []}],
|
|
14
|
+
contextId: ``,
|
|
15
|
+
text: '',
|
|
16
|
+
aiName: "Jarvis",
|
|
17
|
+
language: "English",
|
|
18
|
+
},
|
|
19
|
+
model: 'oai-gpt4o',
|
|
20
|
+
useInputChunking: false,
|
|
21
|
+
...config.get('entityConstants')
|
|
22
|
+
}
|