n8n-nodes-tembory 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -0
- package/dist/credentials/Mem0Api.credentials.d.ts +10 -0
- package/dist/credentials/Mem0Api.credentials.js +52 -0
- package/dist/credentials/Mem0SelfHostedApi.credentials.d.ts +10 -0
- package/dist/credentials/Mem0SelfHostedApi.credentials.js +45 -0
- package/dist/nodes/Mem0/GenericFunctions.d.ts +3 -0
- package/dist/nodes/Mem0/GenericFunctions.js +48 -0
- package/dist/nodes/Mem0/Mem0Memory.node.d.ts +15 -0
- package/dist/nodes/Mem0/Mem0Memory.node.js +2564 -0
- package/dist/nodes/Mem0/tembory-brain.svg +23 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +70 -0
- package/scripts/smoke-n8n-multiturn-tools.js +138 -0
|
@@ -0,0 +1,2564 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Mem0Memory = void 0;
|
|
4
|
+
const GenericFunctions_1 = require("./GenericFunctions");
|
|
5
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
6
|
+
let LangChainMessages = {};
|
|
7
|
+
try {
|
|
8
|
+
LangChainMessages = require("@langchain/core/messages");
|
|
9
|
+
}
|
|
10
|
+
catch { }
|
|
11
|
+
const MAX_TEXT = 12000;
|
|
12
|
+
const nowIso = () => new Date().toISOString();
|
|
13
|
+
const safeStringify = (value) => {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.stringify(value);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return String(value);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const snapshotJson = (value) => {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(JSON.stringify(value));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const truncate = (value, max = MAX_TEXT) => {
|
|
30
|
+
const text = typeof value === 'string' ? value : safeStringify(value);
|
|
31
|
+
if (text.length <= max)
|
|
32
|
+
return text;
|
|
33
|
+
return `${text.slice(0, max)}\n... [truncated ${text.length - max} chars]`;
|
|
34
|
+
};
|
|
35
|
+
const getConnectedEmbedding = async (ctx, itemIndex) => {
|
|
36
|
+
try {
|
|
37
|
+
const embedding = await ctx.getInputConnectionData(n8n_workflow_1.NodeConnectionTypes.AiEmbedding, itemIndex);
|
|
38
|
+
return embedding && typeof embedding.embedQuery === 'function' ? embedding : null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const createClientVectorMemory = async (embedding, text, metadata, ids = {}) => ({
|
|
45
|
+
text: truncate(text, 4000),
|
|
46
|
+
embedding: await embedding.embedQuery(String(text || '')),
|
|
47
|
+
user_id: ids.user_id,
|
|
48
|
+
agent_id: ids.agent_id,
|
|
49
|
+
run_id: ids.run_id,
|
|
50
|
+
metadata,
|
|
51
|
+
});
|
|
52
|
+
const saveClientVectorMemories = async (ctx, memories, ids = {}) => {
|
|
53
|
+
const valid = memories.filter((memory) => String(memory === null || memory === void 0 ? void 0 : memory.text || '').trim());
|
|
54
|
+
if (!valid.length)
|
|
55
|
+
return null;
|
|
56
|
+
return GenericFunctions_1.mem0ApiRequest.call(ctx, 'POST', '/elefai/v1/vector-memories', {
|
|
57
|
+
memories: valid,
|
|
58
|
+
user_id: ids.user_id,
|
|
59
|
+
agent_id: ids.agent_id,
|
|
60
|
+
run_id: ids.run_id,
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
const searchClientVectorMemories = async (ctx, embedding, query, body = {}) => {
|
|
64
|
+
const text = String(query || '').trim();
|
|
65
|
+
if (!text)
|
|
66
|
+
return { results: [] };
|
|
67
|
+
return GenericFunctions_1.mem0ApiRequest.call(ctx, 'POST', '/elefai/v1/vector-memories/search', {
|
|
68
|
+
...body,
|
|
69
|
+
query: text,
|
|
70
|
+
embedding: await embedding.embedQuery(text),
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
const stableStringify = (value) => {
|
|
74
|
+
if (value === null || typeof value !== 'object')
|
|
75
|
+
return JSON.stringify(value);
|
|
76
|
+
if (Array.isArray(value))
|
|
77
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
78
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
|
|
79
|
+
};
|
|
80
|
+
const hashString = (value) => {
|
|
81
|
+
const text = String(value || '');
|
|
82
|
+
let hash = 2166136261;
|
|
83
|
+
for (let i = 0; i < text.length; i++) {
|
|
84
|
+
hash ^= text.charCodeAt(i);
|
|
85
|
+
hash = Math.imul(hash, 16777619);
|
|
86
|
+
}
|
|
87
|
+
return (hash >>> 0).toString(36);
|
|
88
|
+
};
|
|
89
|
+
const stableHash = (value) => hashString(typeof value === 'string' ? value : stableStringify(value));
|
|
90
|
+
const pickText = (values, preferredKeys) => {
|
|
91
|
+
for (const key of preferredKeys) {
|
|
92
|
+
const value = values === null || values === void 0 ? void 0 : values[key];
|
|
93
|
+
if (typeof value === 'string' && value.trim())
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
for (const value of Object.values(values || {})) {
|
|
97
|
+
if (typeof value === 'string' && value.trim())
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
return '';
|
|
101
|
+
};
|
|
102
|
+
const asSearchQuery = (value) => {
|
|
103
|
+
if (typeof value === 'string')
|
|
104
|
+
return value;
|
|
105
|
+
if (value === undefined || value === null)
|
|
106
|
+
return '';
|
|
107
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
108
|
+
return String(value);
|
|
109
|
+
return pickText(value, ['query', 'input', 'chatInput', 'text', 'message', 'consolidated_text', 'lastUserMessage']);
|
|
110
|
+
};
|
|
111
|
+
const memoryText = (memory) => {
|
|
112
|
+
if (!memory)
|
|
113
|
+
return '';
|
|
114
|
+
if (typeof memory === 'string')
|
|
115
|
+
return memory;
|
|
116
|
+
return memory.memory || memory.text || memory.value || memory.content || memory.data || safeStringify(memory);
|
|
117
|
+
};
|
|
118
|
+
const normalizeResults = (value) => {
|
|
119
|
+
if (!value)
|
|
120
|
+
return [];
|
|
121
|
+
if (Array.isArray(value))
|
|
122
|
+
return value;
|
|
123
|
+
if (Array.isArray(value.results))
|
|
124
|
+
return value.results;
|
|
125
|
+
if (Array.isArray(value.memories))
|
|
126
|
+
return value.memories;
|
|
127
|
+
if (Array.isArray(value.data))
|
|
128
|
+
return value.data;
|
|
129
|
+
return [value];
|
|
130
|
+
};
|
|
131
|
+
const metadataOf = (item) => {
|
|
132
|
+
const meta = (item === null || item === void 0 ? void 0 : item.metadata) || (item === null || item === void 0 ? void 0 : item.raw) || {};
|
|
133
|
+
if (meta && typeof meta === 'object' && meta.metadata && typeof meta.metadata === 'object')
|
|
134
|
+
return meta.metadata;
|
|
135
|
+
return meta && typeof meta === 'object' ? meta : {};
|
|
136
|
+
};
|
|
137
|
+
const scoreOf = (m) => {
|
|
138
|
+
var _a, _b, _c, _d;
|
|
139
|
+
return (_d = (_c = (_b = (_a = m === null || m === void 0 ? void 0 : m.score) !== null && _a !== void 0 ? _a : m === null || m === void 0 ? void 0 : m.similarity) !== null && _b !== void 0 ? _b : m === null || m === void 0 ? void 0 : m.relevance) !== null && _c !== void 0 ? _c : m === null || m === void 0 ? void 0 : m.distance) !== null && _d !== void 0 ? _d : undefined;
|
|
140
|
+
};
|
|
141
|
+
const scoreMetaOf = (m) => (m && typeof m === 'object' && (m.elefaiScore || m._elefaiScore)) || {};
|
|
142
|
+
const withElefaiScore = (m, elefaiScore) => {
|
|
143
|
+
if (!m || typeof m !== 'object')
|
|
144
|
+
return m;
|
|
145
|
+
return { ...m, elefaiScore };
|
|
146
|
+
};
|
|
147
|
+
const normalizeFactValue = (value) => String(value || '').replace(/\*\*/g, '').replace(/\s+/g, ' ').replace(/[.,;:]+$/g, '').trim();
|
|
148
|
+
const profileSourceRank = (source = '') => {
|
|
149
|
+
if (/^user_message$/i.test(source))
|
|
150
|
+
return 100;
|
|
151
|
+
if (/recent_user_message/i.test(source))
|
|
152
|
+
return 90;
|
|
153
|
+
if (/recent_message/i.test(source))
|
|
154
|
+
return 70;
|
|
155
|
+
if (/vector_memory/i.test(source))
|
|
156
|
+
return 50;
|
|
157
|
+
if (/assistant_message/i.test(source))
|
|
158
|
+
return 10;
|
|
159
|
+
return 40;
|
|
160
|
+
};
|
|
161
|
+
const isNoisyProfileValue = (value) => {
|
|
162
|
+
const text = String(value || '').trim();
|
|
163
|
+
if (!text)
|
|
164
|
+
return true;
|
|
165
|
+
if (text.length > 180)
|
|
166
|
+
return true;
|
|
167
|
+
if (/(\{|\}|\[|\]|"\w+"\s*:|call_[A-Za-z0-9]|Calling\s+\w+|Used tools|tool=|input:|status|confirmation_id|reservation_id|resultado da ferramenta|returned:|CONF-[A-Z0-9-]+|RES-[A-Z0-9-]+)/i.test(text))
|
|
168
|
+
return true;
|
|
169
|
+
if (/\?/.test(text))
|
|
170
|
+
return true;
|
|
171
|
+
if (/\b(disponibilidade|hor[aá]rios?\s+dispon[ií]ve(?:is|l)|atualizar\s+os\s+hor[aá]rios|qual foi|o que voc[eê] lembra)\b/i.test(text))
|
|
172
|
+
return true;
|
|
173
|
+
if (/\b(confirmad[oa]s?|pre[- ]?reservad[oa]s?|pré[- ]?reservad[oa]s?|c[oó]digo de confirma[cç][aã]o)\b/i.test(text))
|
|
174
|
+
return true;
|
|
175
|
+
if (/\b(confirmar|reservar|agendar|marcar|cancelar|alterar)\b/i.test(text) && /\b(agenda|hor[aá]rio|pre[- ]?reserva|pré[- ]?reserva|disponibilidade|\d{1,2}[:h]\d{0,2})\b/i.test(text))
|
|
176
|
+
return true;
|
|
177
|
+
if (/\b(?:às|as|das|para)\s+\d{1,2}(?::\d{2}|h\d{0,2})\b/i.test(text))
|
|
178
|
+
return true;
|
|
179
|
+
if (/\b\d{1,2}:\d{2}\b/.test(text) || /\b\d{1,2}\/\d{1,2}(?:\/\d{2,4})?\b/.test(text))
|
|
180
|
+
return true;
|
|
181
|
+
if (/(tamb[eé]m\s+surg(?:em|iram)|refer[eê]ncias?\s+ao\s+contexto|contexto\s+do\s+agendamento|foi mencionado|h[aá]\s+men[cç][aã]o|o usu[aá]rio demonstrou)/i.test(text))
|
|
182
|
+
return true;
|
|
183
|
+
if (/^(agora|atual|pre[- ]?reservado|confirmado|3\.|with input|tamb[eé]m surg(?:iram|em)|pr[oó]ximo passo|(?:a|o|as|os|das|às)\s+\d{1,2}[:h]\d{0,2})$/i.test(text))
|
|
184
|
+
return true;
|
|
185
|
+
return false;
|
|
186
|
+
};
|
|
187
|
+
const isPlausibleCompanyValue = (value) => {
|
|
188
|
+
const text = normalizeFactValue(value);
|
|
189
|
+
if (isNoisyProfileValue(text))
|
|
190
|
+
return false;
|
|
191
|
+
if (/\b(contexto|agendamento|refer[eê]ncias?|interesse|reuni[aã]o|cliente|lead|assunto|hor[aá]rio|disponibilidade)\b/i.test(text))
|
|
192
|
+
return false;
|
|
193
|
+
if (text.split(/\s+/).length > 5)
|
|
194
|
+
return false;
|
|
195
|
+
return true;
|
|
196
|
+
};
|
|
197
|
+
const isWeakBusinessContextValue = (value) => {
|
|
198
|
+
const text = normalizeFactValue(value);
|
|
199
|
+
if (isNoisyProfileValue(text))
|
|
200
|
+
return true;
|
|
201
|
+
if (/^(consultation|consulta|agendamento|agenda|call|reuni[aã]o)$/i.test(text))
|
|
202
|
+
return true;
|
|
203
|
+
if (/^(?:da|do|de|na|no|pela|pelo)\s+[A-ZÀ-Ú][A-Za-zÀ-ÿ0-9&._ -]{1,60}$/i.test(text))
|
|
204
|
+
return true;
|
|
205
|
+
if (text.split(/\s+/).length < 3 && !/\b(CRM|ERP|SaaS|IA|AI)\b/i.test(text))
|
|
206
|
+
return true;
|
|
207
|
+
if (/\b(Tembory)\b/i.test(text) && !/\b(CRM|imobili[aá]rio|cliente|premium|agent|memory|lead|vendas|atendimento|autom[aá]?[cç][aã]o)\b/i.test(text))
|
|
208
|
+
return true;
|
|
209
|
+
return false;
|
|
210
|
+
};
|
|
211
|
+
const setProfileFact = (facts, key, value, source = 'message', at = nowIso()) => {
|
|
212
|
+
const normalized = normalizeFactValue(value);
|
|
213
|
+
if (!normalized || isNoisyProfileValue(normalized))
|
|
214
|
+
return;
|
|
215
|
+
const current = facts[key];
|
|
216
|
+
if (current && !Array.isArray(current) && typeof current === 'object' && current.value !== undefined) {
|
|
217
|
+
const currentRank = profileSourceRank(current.source);
|
|
218
|
+
const nextRank = profileSourceRank(source);
|
|
219
|
+
if (currentRank > nextRank)
|
|
220
|
+
return;
|
|
221
|
+
if (currentRank === nextRank && String(current.value || '').toLowerCase() !== normalized.toLowerCase() && current.at && at && String(current.at) > String(at))
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
facts[key] = { value: normalized, source, at };
|
|
225
|
+
};
|
|
226
|
+
const addProfileListFact = (facts, key, value, source = 'message', at = nowIso()) => {
|
|
227
|
+
const normalized = normalizeFactValue(value);
|
|
228
|
+
if (!normalized || isNoisyProfileValue(normalized))
|
|
229
|
+
return;
|
|
230
|
+
if ((key === 'business_context' || key === 'interests') && isWeakBusinessContextValue(normalized))
|
|
231
|
+
return;
|
|
232
|
+
const current = Array.isArray(facts[key]) ? facts[key] : [];
|
|
233
|
+
if (!current.some((item) => String(item.value || '').toLowerCase() === normalized.toLowerCase()))
|
|
234
|
+
current.push({ value: normalized, source, at });
|
|
235
|
+
facts[key] = current.slice(-12);
|
|
236
|
+
};
|
|
237
|
+
const extractProfileFactsFromText = (text, source = 'message', at = nowIso()) => {
|
|
238
|
+
const facts = {};
|
|
239
|
+
const content = String(text || '');
|
|
240
|
+
const sourceName = String(source || '').toLowerCase();
|
|
241
|
+
const canExtractStrongProfileFacts = sourceName !== 'assistant_message';
|
|
242
|
+
if (!content.trim())
|
|
243
|
+
return facts;
|
|
244
|
+
if (/\[Used tools:|Calling\s+agenda_|"tool"\s*:|"args"\s*:|confirmation_id|reservation_id/i.test(content))
|
|
245
|
+
return facts;
|
|
246
|
+
const email = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i.exec(content);
|
|
247
|
+
if (email && canExtractStrongProfileFacts)
|
|
248
|
+
setProfileFact(facts, 'email', email[0], source, at);
|
|
249
|
+
const phone = /(?:telefone|tel|celular|whatsapp|whats)\s*(?:e|é|:)?\s*(\+?\d[\d\s().-]{7,}\d)/i.exec(content) || /(?:^|\s)(\+?55\s*)?(?:\(?\d{2}\)?\s*)?\d{4,5}[-.\s]?\d{4}(?:\s|$)/.exec(content);
|
|
250
|
+
if (phone && canExtractStrongProfileFacts)
|
|
251
|
+
setProfileFact(facts, 'phone', (phone[1] || phone[0]).trim(), source, at);
|
|
252
|
+
const name = /(?:meu nome (?:e|é)|me chamo|sou o|sou a)\s+([A-ZÀ-Ú][A-Za-zÀ-ÿ]+(?:\s+[A-ZÀ-Ú][A-Za-zÀ-ÿ]+){0,3})/i.exec(content);
|
|
253
|
+
if (name && canExtractStrongProfileFacts)
|
|
254
|
+
setProfileFact(facts, 'name', name[1], source, at);
|
|
255
|
+
const company = /(?:sou|trabalho|falo|venho)\s+(?:da|do|na|no|pela|pelo)\s+([A-ZÀ-Ú][A-Za-zÀ-ÿ0-9&._ -]{1,60})/i.exec(content) || /(?:empresa|companhia)\s*(?:e|é|:)?\s*([A-ZÀ-Ú][A-Za-zÀ-ÿ0-9&._ -]{1,60})/i.exec(content);
|
|
256
|
+
const companyValue = company === null || company === void 0 ? void 0 : company[1].replace(/\s+e\s+(?:meu|minha|telefone|tel|email|e-mail)\b.*$/i, '').trim();
|
|
257
|
+
if (companyValue && canExtractStrongProfileFacts && isPlausibleCompanyValue(companyValue))
|
|
258
|
+
setProfileFact(facts, 'company', companyValue, source, at);
|
|
259
|
+
const role = /(?:sou|cargo|funcao|função|atuo como)\s+(?:o|a|um|uma)?\s*([A-Za-zÀ-ÿ ]{3,60})\s+(?:da|do|na|no|em)\s+/i.exec(content);
|
|
260
|
+
if (role && canExtractStrongProfileFacts)
|
|
261
|
+
setProfileFact(facts, 'role', role[1], source, at);
|
|
262
|
+
const timePref = /(?:prefiro|prefer[eê]ncia|gosto de)\s+(?:atendimento|hor[aá]rio|agenda)?\s*(?:no|na|de|por|para|:)?\s*(per[ií]odo da tarde|tarde|manh[aã]|noite|hor[aá]rio comercial|fim de semana)/i.exec(content);
|
|
263
|
+
if (timePref)
|
|
264
|
+
addProfileListFact(facts, 'preferences', `atendimento/horario: ${timePref[1]}`, source, at);
|
|
265
|
+
const stylePref = /(?:prefiro|prefer[eê]ncia|respostas?)\s+(.{0,80}?(?:curtas?|objetivas?|portugu[eê]s|detalhadas?|diretas?))/i.exec(content);
|
|
266
|
+
if (stylePref)
|
|
267
|
+
addProfileListFact(facts, 'preferences', `estilo: ${stylePref[1]}`, source, at);
|
|
268
|
+
const meeting = /(?:reuni[aã]o|agendamento|agenda|call)\s+(?:e|é|para|sobre|de|com)?\s*(.{4,140})/i.exec(content);
|
|
269
|
+
if (meeting) {
|
|
270
|
+
const meetingValue = meeting[1].replace(/^(?:sobre|de)\s+/i, '').replace(/^(?:uma\s+)?reuni[aã]o\s+sobre\s+/i, '').trim();
|
|
271
|
+
addProfileListFact(facts, 'business_context', meetingValue, source, at);
|
|
272
|
+
}
|
|
273
|
+
const interest = /(?:interesse|interessado|preciso|quero|busco)\s+(?:em|de|por)?\s*(.{4,120})/i.exec(content);
|
|
274
|
+
if (interest)
|
|
275
|
+
addProfileListFact(facts, 'interests', interest[1], source, at);
|
|
276
|
+
return facts;
|
|
277
|
+
};
|
|
278
|
+
const mergeProfileFacts = (...factSets) => {
|
|
279
|
+
const merged = {};
|
|
280
|
+
for (const facts of factSets || []) {
|
|
281
|
+
if (!facts || typeof facts !== 'object')
|
|
282
|
+
continue;
|
|
283
|
+
for (const [key, value] of Object.entries(facts)) {
|
|
284
|
+
if (Array.isArray(value)) {
|
|
285
|
+
for (const item of value)
|
|
286
|
+
addProfileListFact(merged, key, item.value || item, item.source || 'merged', item.at || nowIso());
|
|
287
|
+
}
|
|
288
|
+
else if (value && typeof value === 'object' && value.value !== undefined) {
|
|
289
|
+
setProfileFact(merged, key, value.value, value.source || 'merged', value.at || nowIso());
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
setProfileFact(merged, key, value, 'merged', nowIso());
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return merged;
|
|
297
|
+
};
|
|
298
|
+
const profileFactsFromMessages = (messages = [], source = 'recent_message') => messages.reduce((facts, msg) => {
|
|
299
|
+
const role = String(msg.role || '').toLowerCase();
|
|
300
|
+
if (role && role !== 'user' && role !== 'human')
|
|
301
|
+
return facts;
|
|
302
|
+
const factSource = source === 'recent_message' ? 'recent_user_message' : source;
|
|
303
|
+
return mergeProfileFacts(facts, extractProfileFactsFromText(msg.content || msg.memory || msg.text || '', factSource, msg.at || msg.created_at || msg.createdAt || nowIso()));
|
|
304
|
+
}, {});
|
|
305
|
+
const isOperationalProfileMemory = (memory) => {
|
|
306
|
+
const text = memoryText(memory);
|
|
307
|
+
const meta = metadataOf(memory);
|
|
308
|
+
const kind = String(meta.kind || meta.type || meta.source || '');
|
|
309
|
+
return text.startsWith(RECENT_MESSAGE_MARKER) || text.startsWith(TOOL_HISTORY_MARKER) || /tool_history|tool_facts|recent_message/i.test(kind);
|
|
310
|
+
};
|
|
311
|
+
const profileFactsFromMemories = (memories = []) => memories.reduce((facts, memory) => {
|
|
312
|
+
if (isOperationalProfileMemory(memory))
|
|
313
|
+
return facts;
|
|
314
|
+
return mergeProfileFacts(facts, extractProfileFactsFromText(memoryText(memory), 'vector_memory', memory.created_at || memory.createdAt || nowIso()));
|
|
315
|
+
}, {});
|
|
316
|
+
const renderProfileFacts = (facts = {}) => {
|
|
317
|
+
const out = {};
|
|
318
|
+
for (const [key, value] of Object.entries(facts || {})) {
|
|
319
|
+
if (Array.isArray(value))
|
|
320
|
+
out[key] = value.map((item) => item.value || item).filter(Boolean);
|
|
321
|
+
else if (value && typeof value === 'object')
|
|
322
|
+
out[key] = value.value;
|
|
323
|
+
else
|
|
324
|
+
out[key] = value;
|
|
325
|
+
}
|
|
326
|
+
return out;
|
|
327
|
+
};
|
|
328
|
+
const safeParseToolPayload = (value) => {
|
|
329
|
+
if (value === undefined || value === null)
|
|
330
|
+
return value;
|
|
331
|
+
if (typeof value !== 'string')
|
|
332
|
+
return value;
|
|
333
|
+
const text = value.trim();
|
|
334
|
+
if (!text)
|
|
335
|
+
return '';
|
|
336
|
+
try {
|
|
337
|
+
return JSON.parse(text);
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
return text;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
const compactToolPayload = (value) => truncate(typeof value === 'string' ? value : safeStringify(value), 900);
|
|
344
|
+
const maybeToolResult = (tool, includeResults = true) => includeResults === false ? undefined : compactToolPayload(safeParseToolPayload(tool === null || tool === void 0 ? void 0 : tool.result));
|
|
345
|
+
const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessages = [], includeResults = true) => {
|
|
346
|
+
const tools = Array.isArray(toolHistory) ? toolHistory : [];
|
|
347
|
+
const successfulTools = tools.filter((tool) => tool.ok !== false);
|
|
348
|
+
const byName = (name) => successfulTools.filter((tool) => tool.name === name);
|
|
349
|
+
const availability = byName('agenda_consultar_disponibilidade');
|
|
350
|
+
const reservations = byName('agenda_pre_reservar_horario');
|
|
351
|
+
const confirmations = byName('agenda_confirmar_agendamento');
|
|
352
|
+
const lastTool = tools[tools.length - 1] || null;
|
|
353
|
+
const lastReservation = reservations[reservations.length - 1] || null;
|
|
354
|
+
const lastConfirmation = confirmations[confirmations.length - 1] || null;
|
|
355
|
+
const hasPendingReservation = Boolean(lastReservation && (!lastConfirmation || String(lastReservation.at || '') > String(lastConfirmation.at || '')));
|
|
356
|
+
const blockedWithoutContext = [];
|
|
357
|
+
if (!availability.length)
|
|
358
|
+
blockedWithoutContext.push('agenda_pre_reservar_horario');
|
|
359
|
+
if (!reservations.length)
|
|
360
|
+
blockedWithoutContext.push('agenda_confirmar_agendamento');
|
|
361
|
+
const guidance = [];
|
|
362
|
+
if (!availability.length)
|
|
363
|
+
guidance.push('No availability result is known for this session; consult availability before reserving or confirming.');
|
|
364
|
+
else if (hasPendingReservation)
|
|
365
|
+
guidance.push('A pre-reservation exists after the latest confirmation; confirmation can use the latest pre-reservation context.');
|
|
366
|
+
else if (lastConfirmation)
|
|
367
|
+
guidance.push('The latest reservation appears confirmed; do not confirm again unless the user explicitly asks to repeat or change it.');
|
|
368
|
+
else if (availability.length)
|
|
369
|
+
guidance.push('Availability is known; if the user chooses one listed slot, reserve without repeating availability.');
|
|
370
|
+
return {
|
|
371
|
+
profile_complete: Boolean(profileFacts && profileFacts.name && profileFacts.company && profileFacts.email && profileFacts.phone),
|
|
372
|
+
last_tool: lastTool ? { name: lastTool.name, ok: lastTool.ok, at: lastTool.at } : null,
|
|
373
|
+
tool_counts: {
|
|
374
|
+
total: tools.length,
|
|
375
|
+
ok: successfulTools.length,
|
|
376
|
+
failed: tools.length - successfulTools.length,
|
|
377
|
+
agenda_consultar_disponibilidade: availability.length,
|
|
378
|
+
agenda_pre_reservar_horario: reservations.length,
|
|
379
|
+
agenda_confirmar_agendamento: confirmations.length,
|
|
380
|
+
},
|
|
381
|
+
agenda_state: {
|
|
382
|
+
has_availability: availability.length > 0,
|
|
383
|
+
has_pre_reservation: reservations.length > 0,
|
|
384
|
+
has_confirmation: confirmations.length > 0,
|
|
385
|
+
has_pending_pre_reservation: hasPendingReservation,
|
|
386
|
+
latest_availability_result: availability.length ? maybeToolResult(availability[availability.length - 1], includeResults) : null,
|
|
387
|
+
latest_pre_reservation_result: lastReservation ? maybeToolResult(lastReservation, includeResults) : null,
|
|
388
|
+
latest_confirmation_result: lastConfirmation ? maybeToolResult(lastConfirmation, includeResults) : null,
|
|
389
|
+
},
|
|
390
|
+
blocked_without_context: Array.from(new Set(blockedWithoutContext)),
|
|
391
|
+
guidance,
|
|
392
|
+
recent_message_count: Array.isArray(recentMessages) ? recentMessages.length : 0,
|
|
393
|
+
};
|
|
394
|
+
};
|
|
395
|
+
const deriveActionLedger = (toolHistory = [], maxItems = 20, includeResults = true) => {
|
|
396
|
+
const tools = sortToolHistory(Array.isArray(toolHistory) ? toolHistory : []);
|
|
397
|
+
return pruneByLimit(tools.map((tool, index) => ({
|
|
398
|
+
sequence: tool.sequence || index + 1,
|
|
399
|
+
action_id: tool.id || `${tool.name || 'tool'}-${index + 1}`,
|
|
400
|
+
kind: 'tool_call',
|
|
401
|
+
name: tool.name || 'unknown_tool',
|
|
402
|
+
status: tool.ok === false ? 'failed' : 'ok',
|
|
403
|
+
input: compactToolPayload(safeParseToolPayload(tool.input)),
|
|
404
|
+
result: maybeToolResult(tool, includeResults),
|
|
405
|
+
at: tool.at || null,
|
|
406
|
+
source: tool.source || 'unknown',
|
|
407
|
+
})), maxItems || 20);
|
|
408
|
+
};
|
|
409
|
+
const deriveEntityTimeline = (profileFacts = {}, graph = [], recentMessages = [], toolHistory = [], maxItems = 24) => {
|
|
410
|
+
const events = [];
|
|
411
|
+
const push = (entity, kind, value, meta = {}) => {
|
|
412
|
+
const normalizedEntity = normalizeFactValue(entity);
|
|
413
|
+
const normalizedValue = normalizeFactValue(value);
|
|
414
|
+
if (!normalizedEntity || !normalizedValue || isNoisyProfileValue(normalizedValue))
|
|
415
|
+
return;
|
|
416
|
+
if ((normalizedEntity === 'business_context' || normalizedEntity === 'interests') && isWeakBusinessContextValue(normalizedValue))
|
|
417
|
+
return;
|
|
418
|
+
events.push({
|
|
419
|
+
entity: normalizedEntity,
|
|
420
|
+
kind,
|
|
421
|
+
value: normalizedValue,
|
|
422
|
+
at: meta.at || null,
|
|
423
|
+
source: meta.source || 'derived',
|
|
424
|
+
});
|
|
425
|
+
};
|
|
426
|
+
for (const [key, value] of Object.entries(profileFacts || {})) {
|
|
427
|
+
if (Array.isArray(value)) {
|
|
428
|
+
for (const item of value)
|
|
429
|
+
push(key, 'profile_fact', item.value || item, { at: item.at, source: item.source || 'profile_fact' });
|
|
430
|
+
}
|
|
431
|
+
else if (value && typeof value === 'object' && value.value !== undefined) {
|
|
432
|
+
push(key, 'profile_fact', value.value, { at: value.at, source: value.source || 'profile_fact' });
|
|
433
|
+
}
|
|
434
|
+
else if (value !== undefined && value !== null) {
|
|
435
|
+
push(key, 'profile_fact', value, { source: 'profile_fact' });
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
for (const relation of graph || []) {
|
|
439
|
+
if (!relation || typeof relation !== 'object')
|
|
440
|
+
continue;
|
|
441
|
+
const from = relation.source || relation.from || relation.subject || relation.entity || relation.node || relation.name;
|
|
442
|
+
const to = relation.target || relation.to || relation.object || relation.related || relation.value;
|
|
443
|
+
const label = relation.relation || relation.relationship || relation.type || relation.label || 'related_to';
|
|
444
|
+
if (from && to)
|
|
445
|
+
push(from, `graph:${label}`, to, { at: relation.at || relation.created_at || relation.createdAt, source: 'graph' });
|
|
446
|
+
}
|
|
447
|
+
for (const msg of recentMessages || []) {
|
|
448
|
+
const role = String(msg.role || '').toLowerCase();
|
|
449
|
+
if (role && role !== 'user' && role !== 'human')
|
|
450
|
+
continue;
|
|
451
|
+
const facts = extractProfileFactsFromText(msg.content || msg.memory || msg.text || '', 'recent_user_message', msg.at || msg.created_at || msg.createdAt || nowIso());
|
|
452
|
+
for (const [key, value] of Object.entries(facts)) {
|
|
453
|
+
if (Array.isArray(value)) {
|
|
454
|
+
for (const item of value)
|
|
455
|
+
push(key, 'recent_fact', item.value || item, { at: item.at || msg.at, source: item.source || 'recent_message' });
|
|
456
|
+
}
|
|
457
|
+
else if (value && typeof value === 'object' && value.value !== undefined) {
|
|
458
|
+
push(key, 'recent_fact', value.value, { at: value.at || msg.at, source: value.source || 'recent_message' });
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
for (const tool of toolHistory || []) {
|
|
463
|
+
if (tool.name)
|
|
464
|
+
push(tool.name, 'tool_event', tool.ok === false ? 'failed' : 'called', { at: tool.at, source: tool.source || 'tool_history' });
|
|
465
|
+
}
|
|
466
|
+
const deduped = [];
|
|
467
|
+
const seen = new Set();
|
|
468
|
+
for (const event of events) {
|
|
469
|
+
const key = `${event.entity.toLowerCase()}|${event.kind.toLowerCase()}|${event.value.toLowerCase()}|${event.at || ''}`;
|
|
470
|
+
if (seen.has(key))
|
|
471
|
+
continue;
|
|
472
|
+
seen.add(key);
|
|
473
|
+
deduped.push(event);
|
|
474
|
+
}
|
|
475
|
+
return pruneByLimit(deduped.sort((a, b) => String(a.at || '').localeCompare(String(b.at || ''))), maxItems || 24);
|
|
476
|
+
};
|
|
477
|
+
const RECENT_MESSAGE_MARKER = '__elefai_recent_message_v1__';
|
|
478
|
+
const TOOL_HISTORY_MARKER = '__elefai_tool_history_v1__';
|
|
479
|
+
const encodeRecentMessage = (recent, threadId) => `${RECENT_MESSAGE_MARKER}${safeStringify({
|
|
480
|
+
role: recent.role || 'user',
|
|
481
|
+
content: recent.content || '',
|
|
482
|
+
at: recent.at || nowIso(),
|
|
483
|
+
thread_id: threadId,
|
|
484
|
+
})}`;
|
|
485
|
+
const encodeToolCall = (tool, threadId) => `${TOOL_HISTORY_MARKER}${safeStringify({
|
|
486
|
+
id: tool.id || tool.callId || '',
|
|
487
|
+
turn_id: tool.turnId || '',
|
|
488
|
+
sequence: tool.sequence || 0,
|
|
489
|
+
name: tool.name || '',
|
|
490
|
+
input: tool.input || '',
|
|
491
|
+
ok: tool.ok !== false,
|
|
492
|
+
result: tool.result || '',
|
|
493
|
+
at: tool.at || nowIso(),
|
|
494
|
+
source: tool.source || 'n8n',
|
|
495
|
+
thread_id: threadId,
|
|
496
|
+
})}`;
|
|
497
|
+
const parseRecentMessageMarker = (text) => {
|
|
498
|
+
if (!text || typeof text !== 'string' || !text.startsWith(RECENT_MESSAGE_MARKER))
|
|
499
|
+
return null;
|
|
500
|
+
try {
|
|
501
|
+
const parsed = JSON.parse(text.slice(RECENT_MESSAGE_MARKER.length));
|
|
502
|
+
if (!parsed || !parsed.content)
|
|
503
|
+
return null;
|
|
504
|
+
return {
|
|
505
|
+
role: parsed.role || 'user',
|
|
506
|
+
content: truncate(parsed.content, 2000),
|
|
507
|
+
at: parsed.at || nowIso(),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
const parseToolHistoryMarker = (text) => {
|
|
515
|
+
if (!text || typeof text !== 'string' || !text.startsWith(TOOL_HISTORY_MARKER))
|
|
516
|
+
return null;
|
|
517
|
+
try {
|
|
518
|
+
const parsed = JSON.parse(text.slice(TOOL_HISTORY_MARKER.length));
|
|
519
|
+
if (!parsed || !parsed.name)
|
|
520
|
+
return null;
|
|
521
|
+
return {
|
|
522
|
+
id: parsed.id || parsed.call_id || parsed.callId || '',
|
|
523
|
+
turnId: parsed.turn_id || parsed.turnId || '',
|
|
524
|
+
sequence: parsed.sequence || 0,
|
|
525
|
+
name: String(parsed.name),
|
|
526
|
+
input: parsed.input === undefined ? '' : String(parsed.input),
|
|
527
|
+
ok: parsed.ok !== false,
|
|
528
|
+
result: truncate(parsed.result || '', 1000),
|
|
529
|
+
at: parsed.at || nowIso(),
|
|
530
|
+
source: parsed.source || 'mem0_marker',
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
const recentMessageFromMemory = (item) => {
|
|
538
|
+
const meta = metadataOf(item);
|
|
539
|
+
const content = meta.content || item.memory || item.text || item.value || item.content || item.data;
|
|
540
|
+
const marked = parseRecentMessageMarker(content);
|
|
541
|
+
if (marked)
|
|
542
|
+
return marked;
|
|
543
|
+
if (meta.kind !== 'recent_message')
|
|
544
|
+
return null;
|
|
545
|
+
if (!content)
|
|
546
|
+
return null;
|
|
547
|
+
return {
|
|
548
|
+
role: meta.role || 'user',
|
|
549
|
+
content: truncate(content, 2000),
|
|
550
|
+
at: meta.at || item.created_at || item.createdAt || nowIso(),
|
|
551
|
+
};
|
|
552
|
+
};
|
|
553
|
+
const toolHistoryFromMemory = (item) => {
|
|
554
|
+
const meta = metadataOf(item);
|
|
555
|
+
const content = meta.content || item.memory || item.text || item.value || item.content || item.data;
|
|
556
|
+
const marked = parseToolHistoryMarker(content);
|
|
557
|
+
if (marked)
|
|
558
|
+
return marked;
|
|
559
|
+
if (meta.kind === 'recent_message') {
|
|
560
|
+
const toolBlock = extractToolCallsFromText(content, meta.at || item.created_at || item.createdAt || nowIso())[0];
|
|
561
|
+
if (toolBlock)
|
|
562
|
+
return toolBlock;
|
|
563
|
+
}
|
|
564
|
+
if (meta.kind !== 'tool_history' && meta.kind !== 'tool_facts')
|
|
565
|
+
return null;
|
|
566
|
+
const factMatch = /(?:^|\b)Tool\s+([A-Za-z0-9_.:-]+)\s+input=([\s\S]*?)(?:\s+result=([\s\S]*))?$/i.exec(String(content || ''));
|
|
567
|
+
if (factMatch) {
|
|
568
|
+
return {
|
|
569
|
+
id: meta.id || meta.call_id || meta.callId || '',
|
|
570
|
+
turnId: meta.turn_id || meta.turnId || '',
|
|
571
|
+
name: factMatch[1],
|
|
572
|
+
input: truncate((factMatch[2] || '').trim(), 1000),
|
|
573
|
+
ok: true,
|
|
574
|
+
result: truncate((factMatch[3] || '').trim(), 1000),
|
|
575
|
+
at: meta.at || item.created_at || item.createdAt || nowIso(),
|
|
576
|
+
source: 'semantic_fact',
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
const namedTool = meta.kind === 'tool_history' ? /(?:tool|ferramenta)\s+([A-Za-z0-9_.:-]*(?:agenda_[A-Za-z0-9_.:-]+)[A-Za-z0-9_.:-]*)/i.exec(String(content || '')) : null;
|
|
580
|
+
if (namedTool) {
|
|
581
|
+
return {
|
|
582
|
+
id: meta.id || meta.call_id || meta.callId || '',
|
|
583
|
+
turnId: meta.turn_id || meta.turnId || '',
|
|
584
|
+
name: namedTool[1],
|
|
585
|
+
input: '',
|
|
586
|
+
ok: true,
|
|
587
|
+
result: truncate(content || '', 1000),
|
|
588
|
+
at: meta.at || item.created_at || item.createdAt || nowIso(),
|
|
589
|
+
source: 'semantic_named_tool',
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
const text = meta.kind === 'tool_history' ? String(content || '').toLowerCase() : '';
|
|
593
|
+
const inferredAgendaTool = text.includes('confirm') && text.includes('agendamento')
|
|
594
|
+
? 'agenda_confirmar_agendamento'
|
|
595
|
+
: text.includes('pré-reserva') || text.includes('pre-reserva') || text.includes('pre reserva') || text.includes('pré reserva')
|
|
596
|
+
? 'agenda_pre_reservar_horario'
|
|
597
|
+
: (text.includes('horários disponíveis') || text.includes('horarios disponiveis') || text.includes('available') || text.includes('disponibilidade')) && text.includes('agenda')
|
|
598
|
+
? 'agenda_consultar_disponibilidade'
|
|
599
|
+
: '';
|
|
600
|
+
if (inferredAgendaTool) {
|
|
601
|
+
return {
|
|
602
|
+
id: meta.id || meta.call_id || meta.callId || '',
|
|
603
|
+
turnId: meta.turn_id || meta.turnId || '',
|
|
604
|
+
name: inferredAgendaTool,
|
|
605
|
+
input: '',
|
|
606
|
+
ok: true,
|
|
607
|
+
result: truncate(content || '', 1000),
|
|
608
|
+
at: meta.at || item.created_at || item.createdAt || nowIso(),
|
|
609
|
+
source: 'semantic_inference',
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
if (meta.kind !== 'tool_history')
|
|
613
|
+
return null;
|
|
614
|
+
const name = meta.name || meta.tool || meta.toolName;
|
|
615
|
+
if (!name)
|
|
616
|
+
return null;
|
|
617
|
+
return {
|
|
618
|
+
id: meta.id || meta.call_id || meta.callId || '',
|
|
619
|
+
turnId: meta.turn_id || meta.turnId || '',
|
|
620
|
+
name: String(name),
|
|
621
|
+
input: meta.input === undefined ? '' : String(meta.input),
|
|
622
|
+
ok: meta.ok !== false,
|
|
623
|
+
result: truncate(meta.result || content || '', 1000),
|
|
624
|
+
at: meta.at || item.created_at || item.createdAt || nowIso(),
|
|
625
|
+
source: meta.source || 'metadata',
|
|
626
|
+
};
|
|
627
|
+
};
|
|
628
|
+
const toolHistoryItemsFromMemory = (item) => {
|
|
629
|
+
const meta = metadataOf(item);
|
|
630
|
+
const content = meta.content || item.memory || item.text || item.value || item.content || item.data;
|
|
631
|
+
const marked = parseToolHistoryMarker(content);
|
|
632
|
+
if (marked)
|
|
633
|
+
return [marked];
|
|
634
|
+
if (meta.kind === 'recent_message') {
|
|
635
|
+
const textCalls = extractToolCallsFromText(content, meta.at || item.created_at || item.createdAt || nowIso());
|
|
636
|
+
if (textCalls.length)
|
|
637
|
+
return textCalls;
|
|
638
|
+
}
|
|
639
|
+
const single = toolHistoryFromMemory(item);
|
|
640
|
+
return single ? [single] : [];
|
|
641
|
+
};
|
|
642
|
+
const toBaseMessage = (message) => {
|
|
643
|
+
const role = String((message === null || message === void 0 ? void 0 : message.role) || 'system').toLowerCase();
|
|
644
|
+
const content = typeof message === 'string' ? message : String((message === null || message === void 0 ? void 0 : message.content) || '');
|
|
645
|
+
const MessageClass = role === 'human' || role === 'user'
|
|
646
|
+
? LangChainMessages.HumanMessage
|
|
647
|
+
: role === 'ai' || role === 'assistant'
|
|
648
|
+
? LangChainMessages.AIMessage
|
|
649
|
+
: LangChainMessages.SystemMessage;
|
|
650
|
+
if (MessageClass)
|
|
651
|
+
return new MessageClass(content);
|
|
652
|
+
return {
|
|
653
|
+
content,
|
|
654
|
+
role,
|
|
655
|
+
_getType: () => role === 'user' ? 'human' : role === 'assistant' ? 'ai' : role,
|
|
656
|
+
toJSON: () => ({ type: role, data: { content } }),
|
|
657
|
+
};
|
|
658
|
+
};
|
|
659
|
+
const summarizeToolResult = (value, max = 600) => {
|
|
660
|
+
if (value === undefined || value === null)
|
|
661
|
+
return '';
|
|
662
|
+
const text = typeof value === 'string' ? value : safeStringify(value);
|
|
663
|
+
const title = /<title[^>]*>([^<]+)<\/title>/i.exec(text);
|
|
664
|
+
if (/^\s*</.test(text))
|
|
665
|
+
return truncate(`html(len=${text.length}${title ? `, title="${title[1].trim()}"` : ''})`, max);
|
|
666
|
+
return truncate(text, max);
|
|
667
|
+
};
|
|
668
|
+
const makeToolEventId = (tool, sequence = 0) => {
|
|
669
|
+
const base = `${tool.at || ''}|${sequence}|${tool.name || ''}|${tool.input || ''}|${tool.result || ''}`;
|
|
670
|
+
return `tool_${hashString(base)}`;
|
|
671
|
+
};
|
|
672
|
+
const canonicalToolInput = (value) => {
|
|
673
|
+
const text = String(value === undefined || value === null ? '' : value).trim();
|
|
674
|
+
if (text === '""' || text === "''")
|
|
675
|
+
return '';
|
|
676
|
+
return text;
|
|
677
|
+
};
|
|
678
|
+
const normalizeToolCall = (tool, sequence = 0, defaults = {}) => {
|
|
679
|
+
const at = tool.at || defaults.at || nowIso();
|
|
680
|
+
const input = canonicalToolInput(tool.input);
|
|
681
|
+
const result = summarizeToolResult(tool.result || tool.output || tool.observation || '');
|
|
682
|
+
const normalized = {
|
|
683
|
+
id: tool.id || tool.callId || tool.call_id || '',
|
|
684
|
+
call_id: tool.id || tool.callId || tool.call_id || '',
|
|
685
|
+
turnId: tool.turnId || tool.turn_id || defaults.turnId || '',
|
|
686
|
+
turn_id: tool.turnId || tool.turn_id || defaults.turnId || '',
|
|
687
|
+
sequence: Number(tool.sequence || defaults.sequence || sequence || 0),
|
|
688
|
+
name: String(tool.name || tool.tool || tool.toolName || ''),
|
|
689
|
+
tool_name: String(tool.name || tool.tool || tool.toolName || ''),
|
|
690
|
+
input,
|
|
691
|
+
tool_args: input,
|
|
692
|
+
normalized_args: input,
|
|
693
|
+
ok: tool.ok !== false,
|
|
694
|
+
result,
|
|
695
|
+
result_summary: result,
|
|
696
|
+
at,
|
|
697
|
+
timestamp: at,
|
|
698
|
+
source: tool.source || defaults.source || 'n8n',
|
|
699
|
+
status: tool.ok === false ? 'failed' : 'ok',
|
|
700
|
+
};
|
|
701
|
+
if (!normalized.id)
|
|
702
|
+
normalized.id = makeToolEventId(normalized, normalized.sequence);
|
|
703
|
+
if (!normalized.call_id)
|
|
704
|
+
normalized.call_id = normalized.id;
|
|
705
|
+
normalized.result_hash = stableHash(normalized.result || '');
|
|
706
|
+
normalized.args_hash = stableHash(normalized.normalized_args || '');
|
|
707
|
+
normalized.dedupe_key = toolEventKey(normalized);
|
|
708
|
+
return normalized;
|
|
709
|
+
};
|
|
710
|
+
const toolEventKey = (tool) => {
|
|
711
|
+
const generatedId = String(tool.id || '').startsWith('tool_');
|
|
712
|
+
if (tool.id && !generatedId)
|
|
713
|
+
return String(tool.id);
|
|
714
|
+
const atMs = new Date(tool.at || 0).getTime();
|
|
715
|
+
const timeBucket = Number.isFinite(atMs) && atMs > 0 ? Math.floor(atMs / 2000) : '';
|
|
716
|
+
return `${timeBucket}:${tool.name || ''}:${hashString(canonicalToolInput(tool.input))}:${hashString(tool.result || '')}`;
|
|
717
|
+
};
|
|
718
|
+
const readDeep = (value, visitor, seen = new Set()) => {
|
|
719
|
+
if (!value || typeof value !== 'object' || seen.has(value))
|
|
720
|
+
return;
|
|
721
|
+
seen.add(value);
|
|
722
|
+
visitor(value);
|
|
723
|
+
if (Array.isArray(value)) {
|
|
724
|
+
for (const item of value)
|
|
725
|
+
readDeep(item, visitor, seen);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
for (const item of Object.values(value))
|
|
729
|
+
readDeep(item, visitor, seen);
|
|
730
|
+
};
|
|
731
|
+
const extractToolCallsFromText = (text, at = nowIso()) => {
|
|
732
|
+
const calls = [];
|
|
733
|
+
const source = String(text || '');
|
|
734
|
+
const toolBlock = /\[Used tools:\s*([\s\S]*)\]/i.exec(source);
|
|
735
|
+
if (!toolBlock)
|
|
736
|
+
return calls;
|
|
737
|
+
const segments = toolBlock[1].split(/;\s*(?=Tool:)/i);
|
|
738
|
+
for (const segment of segments) {
|
|
739
|
+
const match = /Tool:\s*([^,\n]+)\s*,\s*Input:\s*([\s\S]*?)\s*,\s*Result:\s*([\s\S]*)$/i.exec(segment.trim());
|
|
740
|
+
if (!match)
|
|
741
|
+
continue;
|
|
742
|
+
const name = match[1].trim();
|
|
743
|
+
if (!name)
|
|
744
|
+
continue;
|
|
745
|
+
calls.push(normalizeToolCall({
|
|
746
|
+
name,
|
|
747
|
+
input: match[2].trim(),
|
|
748
|
+
ok: true,
|
|
749
|
+
result: summarizeToolResult(match[3].trim()),
|
|
750
|
+
at,
|
|
751
|
+
source: 'used_tools_text',
|
|
752
|
+
}, calls.length + 1));
|
|
753
|
+
}
|
|
754
|
+
return calls;
|
|
755
|
+
};
|
|
756
|
+
const extractToolCalls = (outputValues = {}) => {
|
|
757
|
+
const calls = [];
|
|
758
|
+
const at = nowIso();
|
|
759
|
+
const push = (name, input, result, ok = true, meta = {}) => {
|
|
760
|
+
if (!name)
|
|
761
|
+
return;
|
|
762
|
+
calls.push(normalizeToolCall({
|
|
763
|
+
id: meta.id || meta.callId || meta.call_id,
|
|
764
|
+
turnId: meta.turnId || meta.turn_id,
|
|
765
|
+
sequence: meta.sequence || calls.length + 1,
|
|
766
|
+
name: String(name),
|
|
767
|
+
input: input === undefined ? '' : (typeof input === 'string' ? input : stableStringify(input)),
|
|
768
|
+
ok,
|
|
769
|
+
result: summarizeToolResult(result),
|
|
770
|
+
at,
|
|
771
|
+
source: meta.source || 'intermediate_steps',
|
|
772
|
+
}, calls.length + 1, { at }));
|
|
773
|
+
};
|
|
774
|
+
readDeep(outputValues, (obj) => {
|
|
775
|
+
const name = obj.tool || obj.toolName || obj.name;
|
|
776
|
+
const hasInput = obj.toolInput !== undefined || obj.input !== undefined || obj.args !== undefined;
|
|
777
|
+
const hasResult = obj.observation !== undefined || obj.result !== undefined || obj.output !== undefined || obj.error !== undefined;
|
|
778
|
+
const hasExplicitId = obj.toolCallId || obj.tool_call_id || obj.callId || obj.call_id || obj.id;
|
|
779
|
+
if (name && hasInput && (hasResult || hasExplicitId)) {
|
|
780
|
+
push(name, obj.toolInput !== undefined ? obj.toolInput : obj.input !== undefined ? obj.input : obj.args, obj.observation || obj.result || obj.output, obj.error ? false : true, {
|
|
781
|
+
id: obj.toolCallId || obj.tool_call_id || obj.callId || obj.call_id || obj.id,
|
|
782
|
+
turnId: obj.turnId || obj.turn_id,
|
|
783
|
+
sequence: obj.sequence,
|
|
784
|
+
source: 'tool_object',
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
if (Array.isArray(obj.intermediateSteps)) {
|
|
788
|
+
for (let index = 0; index < obj.intermediateSteps.length; index++) {
|
|
789
|
+
const step = obj.intermediateSteps[index];
|
|
790
|
+
if (Array.isArray(step) && step.length >= 1) {
|
|
791
|
+
const action = step[0] || {};
|
|
792
|
+
push(action.tool || action.name, action.toolInput || action.input || action.args, step[1], true, {
|
|
793
|
+
id: action.toolCallId || action.tool_call_id || action.callId || action.call_id || action.id,
|
|
794
|
+
turnId: action.turnId || action.turn_id,
|
|
795
|
+
sequence: index + 1,
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
else if (step && typeof step === 'object') {
|
|
799
|
+
const action = step.action || step;
|
|
800
|
+
push(action.tool || action.name, action.toolInput || action.input || action.args, step.observation || step.result || step.output, step.error ? false : true, {
|
|
801
|
+
id: action.toolCallId || action.tool_call_id || action.callId || action.call_id || action.id || step.toolCallId || step.tool_call_id || step.callId || step.call_id || step.id,
|
|
802
|
+
turnId: action.turnId || action.turn_id || step.turnId || step.turn_id,
|
|
803
|
+
sequence: index + 1,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
const text = `${pickText(outputValues, ['output', 'response', 'text', 'answer'])}`;
|
|
810
|
+
for (const tool of extractToolCallsFromText(text))
|
|
811
|
+
push(tool.name, tool.input, tool.result, tool.ok, { id: tool.id, sequence: tool.sequence, source: tool.source });
|
|
812
|
+
const byKey = new Map();
|
|
813
|
+
for (const call of calls) {
|
|
814
|
+
const key = toolEventKey(call);
|
|
815
|
+
const prev = byKey.get(key);
|
|
816
|
+
if (!prev || (call.result || '').length > (prev.result || '').length)
|
|
817
|
+
byKey.set(key, call);
|
|
818
|
+
}
|
|
819
|
+
return Array.from(byKey.values());
|
|
820
|
+
};
|
|
821
|
+
const getMemoryStore = (ctx) => {
|
|
822
|
+
try {
|
|
823
|
+
const data = ctx.getWorkflowStaticData('global');
|
|
824
|
+
data.elefaiBrain = data.elefaiBrain || {};
|
|
825
|
+
data.elefaiBrain.toolHistory = data.elefaiBrain.toolHistory || {};
|
|
826
|
+
data.elefaiBrain.recentMessages = data.elefaiBrain.recentMessages || {};
|
|
827
|
+
data.elefaiBrain.profileFacts = data.elefaiBrain.profileFacts || {};
|
|
828
|
+
data.elefaiBrain.workingMemory = data.elefaiBrain.workingMemory || {};
|
|
829
|
+
data.elefaiBrain.decisionState = data.elefaiBrain.decisionState || {};
|
|
830
|
+
data.elefaiBrain.memoryCompression = data.elefaiBrain.memoryCompression || {};
|
|
831
|
+
return data.elefaiBrain;
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
global.__elefaiBrainMemory = global.__elefaiBrainMemory || { toolHistory: {}, recentMessages: {}, profileFacts: {}, workingMemory: {}, decisionState: {}, memoryCompression: {} };
|
|
835
|
+
global.__elefaiBrainMemory.workingMemory = global.__elefaiBrainMemory.workingMemory || {};
|
|
836
|
+
global.__elefaiBrainMemory.decisionState = global.__elefaiBrainMemory.decisionState || {};
|
|
837
|
+
global.__elefaiBrainMemory.memoryCompression = global.__elefaiBrainMemory.memoryCompression || {};
|
|
838
|
+
return global.__elefaiBrainMemory;
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
const namespacePart = (value) => String(value || '').trim().replace(/\s+/g, '_').replace(/[:|]+/g, '-');
|
|
842
|
+
const userKeyFrom = (threadId, adv, project = '') => {
|
|
843
|
+
const base = namespacePart((adv === null || adv === void 0 ? void 0 : adv.userId) || threadId || 'default');
|
|
844
|
+
const namespace = namespacePart(project || (adv === null || adv === void 0 ? void 0 : adv.project) || (adv === null || adv === void 0 ? void 0 : adv.projectId));
|
|
845
|
+
return namespace ? `${namespace}::${base}` : base;
|
|
846
|
+
};
|
|
847
|
+
const applyOperationalPreset = (advanced = {}) => {
|
|
848
|
+
const preset = String(advanced.operationPreset || 'custom');
|
|
849
|
+
const presets = {
|
|
850
|
+
diagnostic: {
|
|
851
|
+
includeContextHeader: true,
|
|
852
|
+
includeSummary: true,
|
|
853
|
+
includeScores: true,
|
|
854
|
+
includeDiagnostics: true,
|
|
855
|
+
includeRelations: true,
|
|
856
|
+
includeToolHistory: true,
|
|
857
|
+
includeToolResults: true,
|
|
858
|
+
persistToolFactsToMem0: true,
|
|
859
|
+
includeToolHistorySemanticFallback: true,
|
|
860
|
+
includeProfileFacts: true,
|
|
861
|
+
includeOperationalState: true,
|
|
862
|
+
includeActionLedger: true,
|
|
863
|
+
includeEntityTimeline: true,
|
|
864
|
+
includeWorkingMemory: true,
|
|
865
|
+
includeDecisionState: true,
|
|
866
|
+
includeMemoryCompression: true,
|
|
867
|
+
includeRecentMessages: true,
|
|
868
|
+
includeRecentHighlights: true,
|
|
869
|
+
topK: 8,
|
|
870
|
+
lastN: 12,
|
|
871
|
+
toolHistoryLastN: 15,
|
|
872
|
+
recentMessagesLastN: 8,
|
|
873
|
+
},
|
|
874
|
+
productionBalanced: {
|
|
875
|
+
includeContextHeader: true,
|
|
876
|
+
includeSummary: true,
|
|
877
|
+
includeScores: false,
|
|
878
|
+
includeDiagnostics: false,
|
|
879
|
+
includeRelations: true,
|
|
880
|
+
includeToolHistory: true,
|
|
881
|
+
includeToolResults: true,
|
|
882
|
+
persistToolFactsToMem0: false,
|
|
883
|
+
includeToolHistorySemanticFallback: false,
|
|
884
|
+
includeProfileFacts: true,
|
|
885
|
+
includeOperationalState: true,
|
|
886
|
+
includeActionLedger: true,
|
|
887
|
+
includeEntityTimeline: true,
|
|
888
|
+
includeWorkingMemory: true,
|
|
889
|
+
includeDecisionState: true,
|
|
890
|
+
includeMemoryCompression: true,
|
|
891
|
+
includeRecentMessages: true,
|
|
892
|
+
includeRecentHighlights: true,
|
|
893
|
+
topK: 6,
|
|
894
|
+
lastN: 8,
|
|
895
|
+
toolHistoryLastN: 10,
|
|
896
|
+
recentMessagesLastN: 6,
|
|
897
|
+
},
|
|
898
|
+
productionCheap: {
|
|
899
|
+
includeContextHeader: true,
|
|
900
|
+
includeSummary: false,
|
|
901
|
+
includeScores: false,
|
|
902
|
+
includeDiagnostics: false,
|
|
903
|
+
includeRelations: false,
|
|
904
|
+
includeToolHistory: true,
|
|
905
|
+
includeToolResults: false,
|
|
906
|
+
persistToolFactsToMem0: false,
|
|
907
|
+
includeToolHistorySemanticFallback: false,
|
|
908
|
+
includeProfileFacts: true,
|
|
909
|
+
includeOperationalState: true,
|
|
910
|
+
includeActionLedger: true,
|
|
911
|
+
includeEntityTimeline: false,
|
|
912
|
+
includeWorkingMemory: true,
|
|
913
|
+
includeDecisionState: true,
|
|
914
|
+
includeMemoryCompression: true,
|
|
915
|
+
includeRecentMessages: true,
|
|
916
|
+
includeRecentHighlights: false,
|
|
917
|
+
topK: 4,
|
|
918
|
+
lastN: 4,
|
|
919
|
+
toolHistoryLastN: 5,
|
|
920
|
+
recentMessagesLastN: 4,
|
|
921
|
+
},
|
|
922
|
+
audit: {
|
|
923
|
+
includeContextHeader: true,
|
|
924
|
+
includeSummary: true,
|
|
925
|
+
includeScores: true,
|
|
926
|
+
includeDiagnostics: true,
|
|
927
|
+
includeRelations: true,
|
|
928
|
+
includeToolHistory: true,
|
|
929
|
+
includeToolResults: true,
|
|
930
|
+
persistToolFactsToMem0: true,
|
|
931
|
+
includeToolHistorySemanticFallback: true,
|
|
932
|
+
includeProfileFacts: true,
|
|
933
|
+
includeOperationalState: true,
|
|
934
|
+
includeActionLedger: true,
|
|
935
|
+
includeEntityTimeline: true,
|
|
936
|
+
includeWorkingMemory: true,
|
|
937
|
+
includeDecisionState: true,
|
|
938
|
+
includeMemoryCompression: true,
|
|
939
|
+
includeRecentMessages: true,
|
|
940
|
+
includeRecentHighlights: true,
|
|
941
|
+
topK: 10,
|
|
942
|
+
lastN: 20,
|
|
943
|
+
toolHistoryLastN: 20,
|
|
944
|
+
recentMessagesLastN: 12,
|
|
945
|
+
payloadFormat: 'auditJson',
|
|
946
|
+
},
|
|
947
|
+
};
|
|
948
|
+
return { ...(presets[preset === 'lab' ? 'diagnostic' : preset] || {}), ...advanced };
|
|
949
|
+
};
|
|
950
|
+
const pruneByLimit = (items, limit) => items.slice(Math.max(0, items.length - Math.max(1, Number(limit) || 1)));
|
|
951
|
+
const dedupeRecentMessages = (items) => {
|
|
952
|
+
const seen = new Set();
|
|
953
|
+
const out = [];
|
|
954
|
+
for (const item of items || []) {
|
|
955
|
+
const key = `${item.role}:${item.at}:${item.content}`;
|
|
956
|
+
if (seen.has(key))
|
|
957
|
+
continue;
|
|
958
|
+
seen.add(key);
|
|
959
|
+
out.push(item);
|
|
960
|
+
}
|
|
961
|
+
return out;
|
|
962
|
+
};
|
|
963
|
+
const dedupeToolHistory = (items) => {
|
|
964
|
+
const seen = new Set();
|
|
965
|
+
const out = [];
|
|
966
|
+
for (const item of [...(items || [])].reverse()) {
|
|
967
|
+
const key = toolEventKey(item);
|
|
968
|
+
if (seen.has(key))
|
|
969
|
+
continue;
|
|
970
|
+
seen.add(key);
|
|
971
|
+
out.push(item);
|
|
972
|
+
}
|
|
973
|
+
return out.reverse();
|
|
974
|
+
};
|
|
975
|
+
const sortToolHistory = (items) => [...(items || [])].sort((a, b) => {
|
|
976
|
+
const atA = new Date(a.at || 0).getTime();
|
|
977
|
+
const atB = new Date(b.at || 0).getTime();
|
|
978
|
+
if (atA !== atB)
|
|
979
|
+
return atA - atB;
|
|
980
|
+
return Number(a.sequence || 0) - Number(b.sequence || 0);
|
|
981
|
+
});
|
|
982
|
+
const applyToolHistoryWindow = (items, ttlSeconds, limit) => {
|
|
983
|
+
const ttl = Number(ttlSeconds || 0);
|
|
984
|
+
const filtered = ttl > 0
|
|
985
|
+
? (items || []).filter((tool) => !tool.at || ((Date.now() - new Date(tool.at).getTime()) / 1000) <= ttl)
|
|
986
|
+
: (items || []);
|
|
987
|
+
return pruneByLimit(sortToolHistory(dedupeToolHistory(filtered)), limit || 15);
|
|
988
|
+
};
|
|
989
|
+
const getRecentHighlights = (recentMessages, tools, maxItems = 6) => {
|
|
990
|
+
const lines = [];
|
|
991
|
+
for (const msg of recentMessages || []) {
|
|
992
|
+
const text = String(msg.content || '');
|
|
993
|
+
const match = /(meu nome e|me chamo|sou|email|telefone|prefiro|gosto de|quero|preciso|empresa|cliente|cpf|cnpj)\s+(.{2,120})/i.exec(text);
|
|
994
|
+
if (match)
|
|
995
|
+
lines.push(`${msg.role}: ${truncate(text, 180)}`);
|
|
996
|
+
}
|
|
997
|
+
for (const tool of tools || []) {
|
|
998
|
+
if (tool.result)
|
|
999
|
+
lines.push(`tool ${tool.name}: ${truncate(tool.result, 180)}`);
|
|
1000
|
+
}
|
|
1001
|
+
return pruneByLimit(Array.from(new Set(lines)), maxItems);
|
|
1002
|
+
};
|
|
1003
|
+
const inferToolGuard = ({ query, recentMessages, toolHistory, vectorMemories }) => {
|
|
1004
|
+
const text = String(query || '').toLowerCase();
|
|
1005
|
+
const names = new Set((toolHistory || []).map((tool) => String(tool.name || '')));
|
|
1006
|
+
const hasRecentContext = (recentMessages || []).some((msg) => /reserva|pré-reserva|pre-reserva|agend|confirm|hor[aá]rio|dispon/i.test(String(msg.content || '')));
|
|
1007
|
+
const hasVectorContext = (vectorMemories || []).some((memory) => /reserva|pré-reserva|pre-reserva|agend|confirm|hor[aá]rio|dispon/i.test(memoryText(memory)));
|
|
1008
|
+
const hasAnyContext = names.size > 0 || hasRecentContext || hasVectorContext;
|
|
1009
|
+
const blocked = [];
|
|
1010
|
+
const reasons = [];
|
|
1011
|
+
if (!hasAnyContext) {
|
|
1012
|
+
blocked.push('agenda_pre_reservar_horario', 'agenda_confirmar_agendamento');
|
|
1013
|
+
reasons.push('no prior availability or pre-reservation context was found for this session');
|
|
1014
|
+
}
|
|
1015
|
+
if ((/confirm|confirma|confirmar/.test(text)) && !names.has('agenda_pre_reservar_horario') && !hasRecentContext) {
|
|
1016
|
+
blocked.push('agenda_confirmar_agendamento');
|
|
1017
|
+
reasons.push('confirmation requested but no prior pre-reservation was found in tool_history or recent_messages');
|
|
1018
|
+
}
|
|
1019
|
+
if ((/pre.?reserv|pré.?reserv|reservar/.test(text)) && !names.has('agenda_consultar_disponibilidade') && !hasRecentContext) {
|
|
1020
|
+
blocked.push('agenda_pre_reservar_horario');
|
|
1021
|
+
reasons.push('reservation requested but no prior availability result was found in tool_history or recent_messages');
|
|
1022
|
+
}
|
|
1023
|
+
return { blockedTools: Array.from(new Set(blocked)), reasons };
|
|
1024
|
+
};
|
|
1025
|
+
const inferUserIntent = (query = '', recentMessages = []) => {
|
|
1026
|
+
const latestUser = [...(recentMessages || [])].reverse().find((msg) => /^(user|human)$/i.test(String(msg.role || '')));
|
|
1027
|
+
const text = `${query || ''}\n${latestUser?.content || ''}`.toLowerCase();
|
|
1028
|
+
if (/\b(confirm|confirma|confirmar|fechar|pode confirmar)\b/.test(text))
|
|
1029
|
+
return 'confirm';
|
|
1030
|
+
if (/\b(pre.?reserv|pré.?reserv|reservar|segurar|marcar)\b/.test(text))
|
|
1031
|
+
return 'pre_reserve';
|
|
1032
|
+
if (/\b(disponibilidade|hor[aá]rios?|agenda|quando pode|tem vaga)\b/.test(text))
|
|
1033
|
+
return 'check_availability';
|
|
1034
|
+
if (/\b(cancel|desmarcar|remarcar|alterar|mudar)\b/.test(text))
|
|
1035
|
+
return 'change_or_cancel';
|
|
1036
|
+
if (/\b(meu nome|email|telefone|empresa|prefiro|prefer[eê]ncia)\b/.test(text))
|
|
1037
|
+
return 'profile_update';
|
|
1038
|
+
if (text.trim())
|
|
1039
|
+
return 'general_message';
|
|
1040
|
+
return 'unknown';
|
|
1041
|
+
};
|
|
1042
|
+
const deriveNextExpectedAction = (intent, operationalState = {}) => {
|
|
1043
|
+
const agenda = operationalState.agenda_state || {};
|
|
1044
|
+
if (intent === 'confirm') {
|
|
1045
|
+
if (agenda.has_pending_pre_reservation)
|
|
1046
|
+
return 'call agenda_confirmar_agendamento using the latest pre-reservation context';
|
|
1047
|
+
return 'ask for or create a pre-reservation before confirming';
|
|
1048
|
+
}
|
|
1049
|
+
if (intent === 'pre_reserve') {
|
|
1050
|
+
if (agenda.has_availability)
|
|
1051
|
+
return 'call agenda_pre_reservar_horario using one of the known available slots';
|
|
1052
|
+
return 'call agenda_consultar_disponibilidade before pre-reserving';
|
|
1053
|
+
}
|
|
1054
|
+
if (intent === 'check_availability')
|
|
1055
|
+
return agenda.has_availability ? 'reuse known availability unless the user asks to refresh' : 'call agenda_consultar_disponibilidade';
|
|
1056
|
+
if (intent === 'change_or_cancel')
|
|
1057
|
+
return 'inspect current reservation state before changing or cancelling';
|
|
1058
|
+
if (intent === 'profile_update')
|
|
1059
|
+
return 'save stable profile facts and continue the conversation';
|
|
1060
|
+
return 'answer using retrieved context and avoid unnecessary tool calls';
|
|
1061
|
+
};
|
|
1062
|
+
const deriveWorkingMemory = ({ query = '', profileFacts = {}, recentMessages = [], toolHistory = [], operationalState = {}, previous = {} }) => {
|
|
1063
|
+
const intent = inferUserIntent(query, recentMessages);
|
|
1064
|
+
const lastUser = [...(recentMessages || [])].reverse().find((msg) => /^(user|human)$/i.test(String(msg.role || '')));
|
|
1065
|
+
const activeEntities = [];
|
|
1066
|
+
for (const key of ['name', 'company', 'email', 'phone']) {
|
|
1067
|
+
if (profileFacts && profileFacts[key])
|
|
1068
|
+
activeEntities.push({ type: key, value: profileFacts[key] });
|
|
1069
|
+
}
|
|
1070
|
+
const lastTool = toolHistory && toolHistory.length ? toolHistory[toolHistory.length - 1] : null;
|
|
1071
|
+
return {
|
|
1072
|
+
current_goal: previous.current_goal || deriveNextExpectedAction(intent, operationalState),
|
|
1073
|
+
current_task: deriveNextExpectedAction(intent, operationalState),
|
|
1074
|
+
last_user_intent: intent,
|
|
1075
|
+
last_user_message: lastUser ? truncate(lastUser.content, 500) : truncate(query, 500),
|
|
1076
|
+
active_entities: activeEntities,
|
|
1077
|
+
open_decisions: previous.open_decisions || [],
|
|
1078
|
+
last_error: lastTool && lastTool.ok === false ? { tool: lastTool.name, at: lastTool.at, result: lastTool.result } : null,
|
|
1079
|
+
next_expected_action: deriveNextExpectedAction(intent, operationalState),
|
|
1080
|
+
updated_at: nowIso(),
|
|
1081
|
+
};
|
|
1082
|
+
};
|
|
1083
|
+
const deriveDecisionState = ({ query = '', toolHistory = [], operationalState = {}, workingMemory = {} }) => {
|
|
1084
|
+
const agenda = operationalState.agenda_state || {};
|
|
1085
|
+
const decisions = [];
|
|
1086
|
+
const doNotRepeatTools = [];
|
|
1087
|
+
const intent = workingMemory.last_user_intent || inferUserIntent(query, []);
|
|
1088
|
+
const now = nowIso();
|
|
1089
|
+
const pushDecision = (id, decision, reason, appliesTo, extra = {}) => {
|
|
1090
|
+
decisions.push({
|
|
1091
|
+
id,
|
|
1092
|
+
decision,
|
|
1093
|
+
reason,
|
|
1094
|
+
applies_to: appliesTo,
|
|
1095
|
+
status: extra.status || 'active',
|
|
1096
|
+
confidence: extra.confidence ?? 0.85,
|
|
1097
|
+
source: extra.source || 'super_memory_derived',
|
|
1098
|
+
created_at: extra.created_at || now,
|
|
1099
|
+
valid_until: extra.valid_until || null,
|
|
1100
|
+
superseded_by: extra.superseded_by || null,
|
|
1101
|
+
});
|
|
1102
|
+
};
|
|
1103
|
+
if (agenda.has_availability) {
|
|
1104
|
+
doNotRepeatTools.push('agenda_consultar_disponibilidade');
|
|
1105
|
+
pushDecision('agenda_availability_known', 'do not consult availability again unless the user asks to refresh', 'availability result already exists in tool_history', 'agenda_flow', { confidence: 0.9 });
|
|
1106
|
+
}
|
|
1107
|
+
if (agenda.has_pending_pre_reservation) {
|
|
1108
|
+
doNotRepeatTools.push('agenda_pre_reservar_horario');
|
|
1109
|
+
pushDecision('agenda_pre_reservation_pending', 'confirmation should use the latest pre-reservation context', 'a pre-reservation exists after the latest confirmation', 'agenda_flow', { confidence: 0.92 });
|
|
1110
|
+
}
|
|
1111
|
+
if (agenda.has_confirmation && !agenda.has_pending_pre_reservation) {
|
|
1112
|
+
doNotRepeatTools.push('agenda_confirmar_agendamento');
|
|
1113
|
+
pushDecision('agenda_confirmation_done', 'do not confirm again unless the user explicitly asks to repeat or change it', 'latest reservation appears confirmed', 'agenda_flow', { confidence: 0.9 });
|
|
1114
|
+
}
|
|
1115
|
+
const reservationStatus = agenda.has_confirmation && !agenda.has_pending_pre_reservation
|
|
1116
|
+
? 'confirmed'
|
|
1117
|
+
: agenda.has_pending_pre_reservation
|
|
1118
|
+
? 'pending_pre_reservation'
|
|
1119
|
+
: agenda.has_pre_reservation
|
|
1120
|
+
? 'pre_reserved'
|
|
1121
|
+
: agenda.has_availability
|
|
1122
|
+
? 'availability_known'
|
|
1123
|
+
: 'none';
|
|
1124
|
+
return {
|
|
1125
|
+
active_decisions: decisions,
|
|
1126
|
+
do_not_repeat_tools: Array.from(new Set(doNotRepeatTools)),
|
|
1127
|
+
current_intent: intent,
|
|
1128
|
+
agenda_decision_state: {
|
|
1129
|
+
pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
|
|
1130
|
+
active_reservation: Boolean(agenda.has_pre_reservation || agenda.has_confirmation),
|
|
1131
|
+
confirmed_reservation: Boolean(agenda.has_confirmation && !agenda.has_pending_pre_reservation),
|
|
1132
|
+
superseded_reservation: Boolean(agenda.has_confirmation && agenda.has_pending_pre_reservation),
|
|
1133
|
+
reschedule_requested: intent === 'change_or_cancel' && Boolean(agenda.has_confirmation || agenda.has_pre_reservation),
|
|
1134
|
+
cancel_requested: /\b(cancelar|cancele|desmarcar|remover)\b/i.test(String(query || '')),
|
|
1135
|
+
reservation_status: reservationStatus,
|
|
1136
|
+
evaluated_at: now,
|
|
1137
|
+
},
|
|
1138
|
+
conflict_policy: 'prefer active decisions with newer timestamps; ignore superseded or expired memories unless auditing',
|
|
1139
|
+
decision_count: decisions.length,
|
|
1140
|
+
latest_tool: toolHistory && toolHistory.length ? {
|
|
1141
|
+
name: toolHistory[toolHistory.length - 1].name,
|
|
1142
|
+
ok: toolHistory[toolHistory.length - 1].ok,
|
|
1143
|
+
at: toolHistory[toolHistory.length - 1].at,
|
|
1144
|
+
} : null,
|
|
1145
|
+
};
|
|
1146
|
+
};
|
|
1147
|
+
const memoryStatusOf = (memory) => {
|
|
1148
|
+
const meta = metadataOf(memory);
|
|
1149
|
+
const validUntil = meta.valid_until || meta.validUntil;
|
|
1150
|
+
if (validUntil && new Date(validUntil).getTime() < Date.now())
|
|
1151
|
+
return 'expired';
|
|
1152
|
+
return meta.status || (meta.superseded_by || meta.supersededBy ? 'superseded' : 'active');
|
|
1153
|
+
};
|
|
1154
|
+
const confidenceOf = (memory) => {
|
|
1155
|
+
const meta = metadataOf(memory);
|
|
1156
|
+
if (meta.confidence !== undefined)
|
|
1157
|
+
return Number(meta.confidence);
|
|
1158
|
+
if (meta.kind === 'tool_history' || meta.kind === 'tool_facts')
|
|
1159
|
+
return 0.9;
|
|
1160
|
+
if (meta.kind === 'recent_message')
|
|
1161
|
+
return 0.75;
|
|
1162
|
+
return 0.65;
|
|
1163
|
+
};
|
|
1164
|
+
const enrichContextMemory = (memory) => {
|
|
1165
|
+
const meta = metadataOf(memory);
|
|
1166
|
+
return {
|
|
1167
|
+
memory: memoryText(memory),
|
|
1168
|
+
status: memoryStatusOf(memory),
|
|
1169
|
+
confidence: confidenceOf(memory),
|
|
1170
|
+
source: meta.source || scoreMetaOf(memory).source || 'memory',
|
|
1171
|
+
created_at: memory.created_at || memory.createdAt || meta.at || null,
|
|
1172
|
+
valid_until: meta.valid_until || meta.validUntil || null,
|
|
1173
|
+
superseded_by: meta.superseded_by || meta.supersededBy || null,
|
|
1174
|
+
};
|
|
1175
|
+
};
|
|
1176
|
+
const deriveMemoryCompression = ({ recentMessages = [], toolHistory = [], profileFacts = {}, operationalState = {}, vectorMemories = [], maxItems = 6 }) => {
|
|
1177
|
+
const lastMessages = pruneByLimit(recentMessages || [], maxItems).map((msg) => `${msg.role || 'message'}: ${truncate(msg.content || '', 160)}`);
|
|
1178
|
+
const lastTools = pruneByLimit(toolHistory || [], maxItems).map((tool) => `${tool.name || 'tool'}:${tool.ok === false ? 'failed' : 'ok'}${tool.at ? `@${tool.at}` : ''}`);
|
|
1179
|
+
const profile = renderProfileFacts(profileFacts);
|
|
1180
|
+
const activeMemories = (vectorMemories || []).map(enrichContextMemory).filter((item) => item.status === 'active').slice(0, maxItems);
|
|
1181
|
+
return {
|
|
1182
|
+
turn_summary: lastMessages.length ? lastMessages : [],
|
|
1183
|
+
session_summary: {
|
|
1184
|
+
messages: lastMessages.length,
|
|
1185
|
+
tools: lastTools,
|
|
1186
|
+
agenda_state: operationalState.agenda_state || {},
|
|
1187
|
+
},
|
|
1188
|
+
entity_summary: profile,
|
|
1189
|
+
workflow_summary: {
|
|
1190
|
+
active_memory_count: activeMemories.length,
|
|
1191
|
+
top_active_memories: activeMemories.map((item) => ({
|
|
1192
|
+
memory: truncate(item.memory, 220),
|
|
1193
|
+
confidence: item.confidence,
|
|
1194
|
+
source: item.source,
|
|
1195
|
+
})),
|
|
1196
|
+
},
|
|
1197
|
+
};
|
|
1198
|
+
};
|
|
1199
|
+
const deriveContextHealth = ({ userId = '', project = '', vectorMemories = [], recentMessages = [], toolHistory = [], workingMemory = {}, decisionState = {}, memoryCompression = {}, operationalState = {}, diagnostics = {} }) => {
|
|
1200
|
+
const checks = {
|
|
1201
|
+
namespace_stable: Boolean(userId && !/^mcp-session-\d+$/i.test(String(userId))),
|
|
1202
|
+
has_recent_messages: Array.isArray(recentMessages) && recentMessages.length > 0,
|
|
1203
|
+
has_tool_history: Array.isArray(toolHistory) && toolHistory.length > 0,
|
|
1204
|
+
has_vector_memories: Array.isArray(vectorMemories) && vectorMemories.length > 0,
|
|
1205
|
+
has_working_memory: Boolean(workingMemory && Object.keys(workingMemory).length),
|
|
1206
|
+
has_decision_state: Boolean(decisionState && Object.keys(decisionState).length),
|
|
1207
|
+
has_memory_compression: Boolean(memoryCompression && Object.keys(memoryCompression).length),
|
|
1208
|
+
has_operational_state: Boolean(operationalState && Object.keys(operationalState).length),
|
|
1209
|
+
connected_language_model: Boolean(diagnostics.connectedAi && diagnostics.connectedAi.languageModel),
|
|
1210
|
+
connected_embedding: Boolean(diagnostics.connectedAi && diagnostics.connectedAi.embedding),
|
|
1211
|
+
no_connected_ai_errors: !((diagnostics.connectedAi && diagnostics.connectedAi.errors) || []).length,
|
|
1212
|
+
};
|
|
1213
|
+
const weights = {
|
|
1214
|
+
namespace_stable: 170,
|
|
1215
|
+
has_recent_messages: 80,
|
|
1216
|
+
has_tool_history: 140,
|
|
1217
|
+
has_vector_memories: 110,
|
|
1218
|
+
has_working_memory: 110,
|
|
1219
|
+
has_decision_state: 110,
|
|
1220
|
+
has_memory_compression: 90,
|
|
1221
|
+
has_operational_state: 80,
|
|
1222
|
+
connected_language_model: 40,
|
|
1223
|
+
connected_embedding: 40,
|
|
1224
|
+
no_connected_ai_errors: 30,
|
|
1225
|
+
};
|
|
1226
|
+
let score = 0;
|
|
1227
|
+
for (const [key, ok] of Object.entries(checks)) {
|
|
1228
|
+
if (ok)
|
|
1229
|
+
score += weights[key] || 0;
|
|
1230
|
+
}
|
|
1231
|
+
const missing = Object.entries(checks).filter(([, ok]) => !ok).map(([key]) => key);
|
|
1232
|
+
const agenda = operationalState.agenda_state || {};
|
|
1233
|
+
return {
|
|
1234
|
+
kind: 'elefai.brain.context_health.v1',
|
|
1235
|
+
namespace: userId || null,
|
|
1236
|
+
project: project || null,
|
|
1237
|
+
quality_score: Math.max(0, Math.min(1000, score)),
|
|
1238
|
+
status: score >= 970 ? 'excellent' : score >= 900 ? 'strong' : score >= 750 ? 'usable' : 'needs_attention',
|
|
1239
|
+
checks,
|
|
1240
|
+
missing,
|
|
1241
|
+
counts: {
|
|
1242
|
+
vector_memories: Array.isArray(vectorMemories) ? vectorMemories.length : 0,
|
|
1243
|
+
recent_messages: Array.isArray(recentMessages) ? recentMessages.length : 0,
|
|
1244
|
+
tool_history: Array.isArray(toolHistory) ? toolHistory.length : 0,
|
|
1245
|
+
active_decisions: Array.isArray(decisionState.active_decisions) ? decisionState.active_decisions.length : 0,
|
|
1246
|
+
},
|
|
1247
|
+
agenda_state: {
|
|
1248
|
+
has_availability: Boolean(agenda.has_availability),
|
|
1249
|
+
has_pre_reservation: Boolean(agenda.has_pre_reservation),
|
|
1250
|
+
has_confirmation: Boolean(agenda.has_confirmation),
|
|
1251
|
+
has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
|
|
1252
|
+
},
|
|
1253
|
+
generated_at: nowIso(),
|
|
1254
|
+
};
|
|
1255
|
+
};
|
|
1256
|
+
const wrapElefaiMemory = (memory, ctx, memoryKey) => new Proxy(memory, {
|
|
1257
|
+
get(target, prop) {
|
|
1258
|
+
if (prop === 'loadMemoryVariables') {
|
|
1259
|
+
return async (values = {}) => {
|
|
1260
|
+
const { index } = ctx.addInputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, [
|
|
1261
|
+
[{ json: { action: 'loadMemoryVariables', values } }],
|
|
1262
|
+
]);
|
|
1263
|
+
try {
|
|
1264
|
+
const response = await target.loadMemoryVariables(values);
|
|
1265
|
+
const chatHistory = snapshotJson(response[memoryKey] || response.chatHistory || []);
|
|
1266
|
+
ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, index, [
|
|
1267
|
+
[{
|
|
1268
|
+
json: {
|
|
1269
|
+
action: 'loadMemoryVariables',
|
|
1270
|
+
chatHistory,
|
|
1271
|
+
context: response.elefaiBrainContext,
|
|
1272
|
+
contextText: response.elefaiBrainContextText,
|
|
1273
|
+
summary: response.elefaiBrainSummary,
|
|
1274
|
+
connectedModelSummary: response.elefaiBrainConnectedModelSummary,
|
|
1275
|
+
workingMemory: response.elefaiBrainWorkingMemory,
|
|
1276
|
+
decisionState: response.elefaiBrainDecisionState,
|
|
1277
|
+
memoryCompression: response.elefaiBrainMemoryCompression,
|
|
1278
|
+
profileFacts: response.elefaiBrainProfileFacts,
|
|
1279
|
+
operationalState: response.elefaiBrainOperationalState,
|
|
1280
|
+
actionLedger: response.elefaiBrainActionLedger,
|
|
1281
|
+
entityTimeline: response.elefaiBrainEntityTimeline,
|
|
1282
|
+
vectorMemories: response.elefaiBrainVectorMemories,
|
|
1283
|
+
graph: response.elefaiBrainGraph,
|
|
1284
|
+
recentMessages: response.elefaiBrainRecentMessages,
|
|
1285
|
+
recentHighlights: response.elefaiBrainRecentHighlights,
|
|
1286
|
+
toolHistory: response.elefaiBrainToolHistory,
|
|
1287
|
+
diagnostics: response.elefaiBrainDiagnostics,
|
|
1288
|
+
},
|
|
1289
|
+
}],
|
|
1290
|
+
]);
|
|
1291
|
+
return response;
|
|
1292
|
+
}
|
|
1293
|
+
catch (error) {
|
|
1294
|
+
ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, index, error);
|
|
1295
|
+
throw error;
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
if (prop === 'saveContext') {
|
|
1300
|
+
return async (input = {}, output = {}) => {
|
|
1301
|
+
const { index } = ctx.addInputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, [
|
|
1302
|
+
[{ json: { action: 'saveContext', input, output } }],
|
|
1303
|
+
]);
|
|
1304
|
+
try {
|
|
1305
|
+
const response = await target.saveContext(input, output);
|
|
1306
|
+
const chatHistory = snapshotJson(await target.chatHistory.getMessages());
|
|
1307
|
+
ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, index, [
|
|
1308
|
+
[{ json: { action: 'saveContext', input, output, chatHistory, saved: true } }],
|
|
1309
|
+
]);
|
|
1310
|
+
return response;
|
|
1311
|
+
}
|
|
1312
|
+
catch (error) {
|
|
1313
|
+
ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, index, error);
|
|
1314
|
+
throw error;
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
return target[prop];
|
|
1319
|
+
},
|
|
1320
|
+
});
|
|
1321
|
+
const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, workingMemory, decisionState, memoryCompression, operationalState, actionLedger, entityTimeline, vectorMemories, recentMessages, toolHistory, highlights, graph, diagnostics, adv }) => {
|
|
1322
|
+
const includeHeader = adv.includeContextHeader !== false;
|
|
1323
|
+
const includeSummary = adv.includeSummary !== false;
|
|
1324
|
+
const includeScores = adv.includeScores !== false;
|
|
1325
|
+
const sections = [];
|
|
1326
|
+
if (includeHeader) {
|
|
1327
|
+
sections.push({
|
|
1328
|
+
section: 'context_header',
|
|
1329
|
+
title: 'Tembory context',
|
|
1330
|
+
value: 'Use this context as read-only memory. Prefer it over guessing. Do not mention internal section names to the user. If the user asks to continue, choose, reserve, confirm, update, cancel, or perform any downstream action that depends on a prior tool result, first verify the required prior result in tool_history, recent_messages, or vector memories. If the required prior result is absent, do not call the downstream tool; ask for the missing context or call the appropriate prerequisite tool.',
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
if (includeSummary) {
|
|
1334
|
+
const summary = vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map(memoryText).filter(Boolean);
|
|
1335
|
+
sections.push({ section: 'summary', title: 'Summary', value: summary.length ? summary : null, why_null: summary.length ? undefined : 'no vector memories to summarize' });
|
|
1336
|
+
}
|
|
1337
|
+
sections.push({
|
|
1338
|
+
section: 'working_memory',
|
|
1339
|
+
title: 'Working memory',
|
|
1340
|
+
value: adv.includeWorkingMemory === false ? null : (workingMemory || {}),
|
|
1341
|
+
why_null: adv.includeWorkingMemory === false ? 'working memory disabled' : undefined,
|
|
1342
|
+
});
|
|
1343
|
+
sections.push({
|
|
1344
|
+
section: 'profile_facts',
|
|
1345
|
+
title: 'Profile facts',
|
|
1346
|
+
value: adv.includeProfileFacts === false ? null : renderProfileFacts(profileFacts),
|
|
1347
|
+
why_null: adv.includeProfileFacts === false ? 'profile facts disabled' : undefined,
|
|
1348
|
+
});
|
|
1349
|
+
const guard = inferToolGuard({ query, recentMessages, toolHistory, vectorMemories });
|
|
1350
|
+
sections.push({
|
|
1351
|
+
section: 'tool_guard',
|
|
1352
|
+
title: 'Tool guard',
|
|
1353
|
+
value: {
|
|
1354
|
+
blocked_tools: guard.blockedTools,
|
|
1355
|
+
reasons: guard.reasons,
|
|
1356
|
+
instruction: guard.blockedTools.length
|
|
1357
|
+
? `Do not call these tools now: ${guard.blockedTools.join(', ')}. Ask for the missing context or execute the prerequisite step first.`
|
|
1358
|
+
: 'No downstream tool is blocked by missing memory prerequisites.',
|
|
1359
|
+
},
|
|
1360
|
+
});
|
|
1361
|
+
sections.push({
|
|
1362
|
+
section: 'decision_state',
|
|
1363
|
+
title: 'Decision state',
|
|
1364
|
+
value: adv.includeDecisionState === false ? null : (decisionState || {}),
|
|
1365
|
+
why_null: adv.includeDecisionState === false ? 'decision state disabled' : undefined,
|
|
1366
|
+
});
|
|
1367
|
+
sections.push({
|
|
1368
|
+
section: 'operational_state',
|
|
1369
|
+
title: 'Operational state',
|
|
1370
|
+
value: adv.includeOperationalState === false ? null : operationalState,
|
|
1371
|
+
why_null: adv.includeOperationalState === false ? 'operational state disabled' : undefined,
|
|
1372
|
+
});
|
|
1373
|
+
sections.push({
|
|
1374
|
+
section: 'action_ledger',
|
|
1375
|
+
title: 'Action ledger',
|
|
1376
|
+
value: adv.includeActionLedger === false ? null : (actionLedger || []),
|
|
1377
|
+
why_null: adv.includeActionLedger === false ? 'action ledger disabled' : undefined,
|
|
1378
|
+
});
|
|
1379
|
+
sections.push({
|
|
1380
|
+
section: 'entity_timeline',
|
|
1381
|
+
title: 'Entity timeline',
|
|
1382
|
+
value: adv.includeEntityTimeline === false ? null : (entityTimeline || []),
|
|
1383
|
+
why_null: adv.includeEntityTimeline === false ? 'entity timeline disabled' : undefined,
|
|
1384
|
+
});
|
|
1385
|
+
sections.push({
|
|
1386
|
+
section: 'vector',
|
|
1387
|
+
title: 'Vector memories',
|
|
1388
|
+
value: vectorMemories.map((m) => {
|
|
1389
|
+
const scoreMeta = scoreMetaOf(m);
|
|
1390
|
+
const rawScore = scoreOf(m);
|
|
1391
|
+
return {
|
|
1392
|
+
memory: memoryText(m),
|
|
1393
|
+
score: includeScores ? (rawScore !== undefined ? rawScore : scoreMeta.hybridScore ?? scoreMeta.recencyScore ?? 'unavailable') : undefined,
|
|
1394
|
+
semantic_score: includeScores ? scoreMeta.semanticScore : undefined,
|
|
1395
|
+
recency_score: includeScores ? scoreMeta.recencyScore : undefined,
|
|
1396
|
+
hybrid_score: includeScores ? scoreMeta.hybridScore : undefined,
|
|
1397
|
+
source: scoreMeta.source,
|
|
1398
|
+
id: m.id || m.uuid || m.memory_id,
|
|
1399
|
+
created_at: m.created_at || m.createdAt,
|
|
1400
|
+
};
|
|
1401
|
+
}),
|
|
1402
|
+
});
|
|
1403
|
+
sections.push({
|
|
1404
|
+
section: 'memory_compression',
|
|
1405
|
+
title: 'Memory compression',
|
|
1406
|
+
value: adv.includeMemoryCompression === false ? null : (memoryCompression || {}),
|
|
1407
|
+
why_null: adv.includeMemoryCompression === false ? 'memory compression disabled' : undefined,
|
|
1408
|
+
});
|
|
1409
|
+
sections.push({
|
|
1410
|
+
section: 'graph',
|
|
1411
|
+
title: 'Graph',
|
|
1412
|
+
value: graph && graph.length ? graph : null,
|
|
1413
|
+
why_null: graph && graph.length ? undefined : 'graph endpoint did not return relations or is not enabled',
|
|
1414
|
+
});
|
|
1415
|
+
sections.push({
|
|
1416
|
+
section: 'recent_messages',
|
|
1417
|
+
title: 'Recent messages',
|
|
1418
|
+
value: recentMessages || [],
|
|
1419
|
+
});
|
|
1420
|
+
sections.push({
|
|
1421
|
+
section: 'recent_highlights',
|
|
1422
|
+
title: 'Recent highlights',
|
|
1423
|
+
value: highlights || [],
|
|
1424
|
+
});
|
|
1425
|
+
sections.push({
|
|
1426
|
+
section: 'tool_history',
|
|
1427
|
+
title: 'Tool history',
|
|
1428
|
+
value: {
|
|
1429
|
+
enabled: adv.includeToolHistory !== false,
|
|
1430
|
+
items: adv.includeToolHistory === false ? [] : toolHistory.map((tool) => ({
|
|
1431
|
+
id: tool.id,
|
|
1432
|
+
turnId: tool.turnId,
|
|
1433
|
+
sequence: tool.sequence,
|
|
1434
|
+
name: tool.name,
|
|
1435
|
+
input: tool.input,
|
|
1436
|
+
ok: tool.ok,
|
|
1437
|
+
at: tool.at,
|
|
1438
|
+
source: tool.source,
|
|
1439
|
+
result: adv.includeToolResults === false ? undefined : tool.result,
|
|
1440
|
+
})),
|
|
1441
|
+
},
|
|
1442
|
+
});
|
|
1443
|
+
if (adv.includeDiagnostics) {
|
|
1444
|
+
sections.push({
|
|
1445
|
+
section: 'diagnostics',
|
|
1446
|
+
title: 'Diagnostics',
|
|
1447
|
+
value: diagnostics,
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
const audit = {
|
|
1451
|
+
kind: 'elefai.brain.context.v1',
|
|
1452
|
+
meta: { user_id: userId, payloadFormat, query },
|
|
1453
|
+
sections,
|
|
1454
|
+
diagnostics,
|
|
1455
|
+
};
|
|
1456
|
+
const renderSection = (section) => {
|
|
1457
|
+
if (section.value === null || section.value === undefined)
|
|
1458
|
+
return `## ${section.title}\n${section.why_null || 'empty'}`;
|
|
1459
|
+
if (typeof section.value === 'string')
|
|
1460
|
+
return `## ${section.title}\n${section.value}`;
|
|
1461
|
+
if (Array.isArray(section.value))
|
|
1462
|
+
return `## ${section.title}\n${section.value.length ? section.value.map((item) => typeof item === 'string' ? `- ${item}` : `- ${truncate(safeStringify(item), 900)}`).join('\n') : '(empty)'}`;
|
|
1463
|
+
return `## ${section.title}\n${truncate(safeStringify(section.value), 1600)}`;
|
|
1464
|
+
};
|
|
1465
|
+
if (payloadFormat === 'auditJson') {
|
|
1466
|
+
return [{ role: 'system', content: JSON.stringify(audit, null, 2) }];
|
|
1467
|
+
}
|
|
1468
|
+
if (payloadFormat === 'auditBlocks') {
|
|
1469
|
+
return sections.map((section) => ({ role: 'system', content: renderSection(section) }));
|
|
1470
|
+
}
|
|
1471
|
+
const text = sections.map(renderSection).join('\n\n');
|
|
1472
|
+
if (payloadFormat === 'auditText' || payloadFormat === 'singleSystemMessage' || payloadFormat === 'structured') {
|
|
1473
|
+
return [{ role: 'system', content: truncate(text, 16000) }];
|
|
1474
|
+
}
|
|
1475
|
+
return [{ role: 'system', content: truncate(text, 16000) }];
|
|
1476
|
+
};
|
|
1477
|
+
class Mem0Memory {
|
|
1478
|
+
constructor() {
|
|
1479
|
+
this.description = {
|
|
1480
|
+
displayName: 'Tembory',
|
|
1481
|
+
name: 'temboryMemory',
|
|
1482
|
+
icon: 'file:tembory-brain.svg',
|
|
1483
|
+
group: ['transform'],
|
|
1484
|
+
version: 1,
|
|
1485
|
+
description: 'Advanced memory for AI agents with profile, tools, timeline, graph and semantic context',
|
|
1486
|
+
defaults: {
|
|
1487
|
+
name: 'Tembory',
|
|
1488
|
+
},
|
|
1489
|
+
inputs: [
|
|
1490
|
+
{
|
|
1491
|
+
displayName: 'LLM',
|
|
1492
|
+
maxConnections: 1,
|
|
1493
|
+
type: n8n_workflow_1.NodeConnectionTypes.AiLanguageModel,
|
|
1494
|
+
required: false,
|
|
1495
|
+
},
|
|
1496
|
+
{
|
|
1497
|
+
displayName: 'Embed',
|
|
1498
|
+
maxConnections: 1,
|
|
1499
|
+
type: n8n_workflow_1.NodeConnectionTypes.AiEmbedding,
|
|
1500
|
+
required: false,
|
|
1501
|
+
},
|
|
1502
|
+
],
|
|
1503
|
+
outputs: [n8n_workflow_1.NodeConnectionTypes.AiMemory],
|
|
1504
|
+
outputNames: ['Mem'],
|
|
1505
|
+
builderHint: {
|
|
1506
|
+
inputs: {
|
|
1507
|
+
ai_languageModel: { required: false },
|
|
1508
|
+
ai_embedding: { required: false },
|
|
1509
|
+
},
|
|
1510
|
+
},
|
|
1511
|
+
credentials: [
|
|
1512
|
+
{
|
|
1513
|
+
name: 'mem0Api',
|
|
1514
|
+
required: true,
|
|
1515
|
+
displayOptions: {
|
|
1516
|
+
show: {
|
|
1517
|
+
authType: ['cloud'],
|
|
1518
|
+
},
|
|
1519
|
+
},
|
|
1520
|
+
},
|
|
1521
|
+
{
|
|
1522
|
+
name: 'mem0SelfHostedApi',
|
|
1523
|
+
required: true,
|
|
1524
|
+
displayOptions: {
|
|
1525
|
+
show: {
|
|
1526
|
+
authType: ['selfHosted'],
|
|
1527
|
+
},
|
|
1528
|
+
},
|
|
1529
|
+
},
|
|
1530
|
+
],
|
|
1531
|
+
codex: {
|
|
1532
|
+
categories: ['AI', 'Memory'],
|
|
1533
|
+
subcategories: {
|
|
1534
|
+
AI: ['Memory', 'Agents & LLMs'],
|
|
1535
|
+
Memory: ['AI Memory'],
|
|
1536
|
+
},
|
|
1537
|
+
},
|
|
1538
|
+
properties: [
|
|
1539
|
+
{
|
|
1540
|
+
displayName: 'Autenticação',
|
|
1541
|
+
name: 'authType',
|
|
1542
|
+
type: 'options',
|
|
1543
|
+
options: [
|
|
1544
|
+
{ name: 'Tembory Cloud', value: 'cloud' },
|
|
1545
|
+
{ name: 'Self-hosted', value: 'selfHosted' },
|
|
1546
|
+
],
|
|
1547
|
+
default: 'cloud',
|
|
1548
|
+
},
|
|
1549
|
+
{
|
|
1550
|
+
displayName: 'ID da Thread',
|
|
1551
|
+
name: 'threadId',
|
|
1552
|
+
type: 'string',
|
|
1553
|
+
default: '={{ $json.threadId || $json.sessionId || $json.body?.messages?.[0]?.contactId || $json.body?.contactId || $json.contactId || $json.from || $json.sender || $executionId }}',
|
|
1554
|
+
description: 'Identificador estável desta conversa/thread. Usado como user_id se nenhum User ID explícito for informado.',
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
displayName: 'Projeto',
|
|
1558
|
+
name: 'project',
|
|
1559
|
+
type: 'string',
|
|
1560
|
+
default: '={{ $json.project || $json.projectId || $json.accountId || $json.body?.account?.id || "" }}',
|
|
1561
|
+
description: 'Namespace estável do cliente/projeto. Quando preenchido, isola memórias mesmo que duas conversas usem o mesmo ID.',
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
displayName: 'Modo de Recuperação de Contexto',
|
|
1565
|
+
name: 'retrievalMode',
|
|
1566
|
+
type: 'options',
|
|
1567
|
+
options: [
|
|
1568
|
+
{ name: 'Básico', value: 'basic', description: 'Retorna memórias brutas (recentes ou todas)' },
|
|
1569
|
+
{ name: 'Resumo', value: 'summary', description: 'Retorna um resumo simples em texto' },
|
|
1570
|
+
{ name: 'Semântico (v1)', value: 'semantic', description: 'Busca semântica usando o endpoint v1 com opção de rerank' },
|
|
1571
|
+
{ name: 'Semântico (v2)', value: 'semanticV2', description: 'Busca semântica avançada com filtros (v2)' },
|
|
1572
|
+
{ name: 'Híbrido', value: 'hybrid', description: 'Combina memórias recentes com busca semântica (v2) usando time-decay e pontuação híbrida' },
|
|
1573
|
+
],
|
|
1574
|
+
default: 'hybrid',
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
displayName: 'Consulta',
|
|
1578
|
+
name: 'query',
|
|
1579
|
+
type: 'string',
|
|
1580
|
+
default: '={{ $json.query || $json.lastUserMessage || $json.chatInput || $json.body?.consolidated_text || $json.body?.message || $json.body?.text || "" }}',
|
|
1581
|
+
description: 'Consulta em linguagem natural para recuperar memórias relevantes',
|
|
1582
|
+
displayOptions: {
|
|
1583
|
+
show: {
|
|
1584
|
+
retrievalMode: ['semantic', 'semanticV2', 'hybrid'],
|
|
1585
|
+
},
|
|
1586
|
+
},
|
|
1587
|
+
},
|
|
1588
|
+
{
|
|
1589
|
+
displayName: 'Chave de Memória',
|
|
1590
|
+
name: 'memoryKey',
|
|
1591
|
+
type: 'string',
|
|
1592
|
+
default: 'chat_history',
|
|
1593
|
+
description: 'Chave sob a qual a memória será retornada',
|
|
1594
|
+
},
|
|
1595
|
+
{
|
|
1596
|
+
displayName: 'Formato do Contexto',
|
|
1597
|
+
name: 'payloadFormat',
|
|
1598
|
+
type: 'options',
|
|
1599
|
+
options: [
|
|
1600
|
+
{ name: 'Produção Estruturado', value: 'structured' },
|
|
1601
|
+
{ name: 'Auditoria Texto', value: 'auditText' },
|
|
1602
|
+
{ name: 'Auditoria JSON', value: 'auditJson' },
|
|
1603
|
+
{ name: 'Blocos de Auditoria', value: 'auditBlocks' },
|
|
1604
|
+
{ name: 'Uma System Message', value: 'singleSystemMessage' },
|
|
1605
|
+
],
|
|
1606
|
+
default: 'structured',
|
|
1607
|
+
description: 'Como o contexto recuperado será entregue ao agente antes da LLM.',
|
|
1608
|
+
},
|
|
1609
|
+
{
|
|
1610
|
+
displayName: 'Avançado',
|
|
1611
|
+
name: 'advanced',
|
|
1612
|
+
type: 'collection',
|
|
1613
|
+
placeholder: 'Opções',
|
|
1614
|
+
default: {},
|
|
1615
|
+
options: [
|
|
1616
|
+
{ displayName: 'User ID', name: 'userId', type: 'string', default: '' },
|
|
1617
|
+
{
|
|
1618
|
+
displayName: 'Preset Operacional',
|
|
1619
|
+
name: 'operationPreset',
|
|
1620
|
+
type: 'options',
|
|
1621
|
+
options: [
|
|
1622
|
+
{ name: 'Custom', value: 'custom' },
|
|
1623
|
+
{ name: 'Diagnóstico Completo', value: 'diagnostic' },
|
|
1624
|
+
{ name: 'Produção Balanceada', value: 'productionBalanced' },
|
|
1625
|
+
{ name: 'Produção Econômica', value: 'productionCheap' },
|
|
1626
|
+
{ name: 'Auditoria', value: 'audit' },
|
|
1627
|
+
],
|
|
1628
|
+
default: 'custom',
|
|
1629
|
+
description: 'Preenche defaults seguros para contexto, diagnostico, historico de tools e mensagens recentes. Valores definidos manualmente continuam tendo prioridade.',
|
|
1630
|
+
},
|
|
1631
|
+
{ displayName: 'Agent ID', name: 'agentId', type: 'string', default: '' },
|
|
1632
|
+
{ displayName: 'App ID', name: 'appId', type: 'string', default: '' },
|
|
1633
|
+
{ displayName: 'Run ID', name: 'runId', type: 'string', default: '' },
|
|
1634
|
+
{ displayName: 'Top K', name: 'topK', type: 'number', typeOptions: { minValue: 1 }, default: 25, description: 'Quantidade de memórias a recuperar (modos semânticos e limites de recentes)' },
|
|
1635
|
+
{ displayName: 'Rerank', name: 'rerank', type: 'boolean', default: true, description: 'Reordenação por relevância (modos semânticos)', displayOptions: { show: { '/retrievalMode': ['semantic', 'semanticV2', 'hybrid'] } } },
|
|
1636
|
+
{ displayName: 'Fields (lista separada por vírgula)', name: 'fields', type: 'string', default: '', description: 'Campos específicos a retornar da API (modos semânticos)', displayOptions: { show: { '/retrievalMode': ['semantic', 'semanticV2', 'hybrid'] } } },
|
|
1637
|
+
{ displayName: 'Filters (JSON)', name: 'filters', type: 'json', default: '{}', description: 'Objeto de filtros avançados para busca v2', displayOptions: { show: { '/retrievalMode': ['semanticV2', 'hybrid'] } } },
|
|
1638
|
+
{ displayName: 'Last N (recentes)', name: 'lastN', type: 'number', default: 20, description: 'Se > 0, retorna apenas as últimas N memórias em Básico/Resumo e compõe a parte de recentes no Híbrido', displayOptions: { show: { '/retrievalMode': ['basic', 'summary', 'hybrid'] } } },
|
|
1639
|
+
{ displayName: 'Alpha (peso semântico)', name: 'alpha', type: 'number', typeOptions: { minValue: 0, maxValue: 1, numberPrecision: 2 }, default: 0.65, description: 'Peso da relevância semântica na pontuação híbrida', displayOptions: { show: { '/retrievalMode': ['hybrid'] } } },
|
|
1640
|
+
{ displayName: 'Half-life (horas)', name: 'halfLifeHours', type: 'number', typeOptions: { minValue: 1 }, default: 48, description: 'Meia-vida (em horas) usada no decaimento temporal', displayOptions: { show: { '/retrievalMode': ['hybrid'] } } },
|
|
1641
|
+
{ displayName: 'Máximo a retornar', name: 'maxReturn', type: 'number', typeOptions: { minValue: 1 }, default: 30, description: 'Quantidade final de memórias retornadas ao agente', displayOptions: { show: { '/retrievalMode': ['hybrid'] } } },
|
|
1642
|
+
{ displayName: 'MMR (diversidade)', name: 'mmr', type: 'boolean', default: true, description: 'Aplicar diversidade de resultados (Maximal Marginal Relevance)', displayOptions: { show: { '/retrievalMode': ['hybrid'] } } },
|
|
1643
|
+
{ displayName: 'MMR Lambda', name: 'mmrLambda', type: 'number', typeOptions: { minValue: 0, maxValue: 1, numberPrecision: 2 }, default: 0.5, description: 'Equilíbrio entre relevância e diversidade no MMR', displayOptions: { show: { '/retrievalMode': ['hybrid'] } } },
|
|
1644
|
+
{ displayName: 'Incluir Cabeçalho de Contexto', name: 'includeContextHeader', type: 'boolean', default: true },
|
|
1645
|
+
{ displayName: 'Incluir Resumo', name: 'includeSummary', type: 'boolean', default: true },
|
|
1646
|
+
{ displayName: 'Máximo de Fatos no Resumo', name: 'summaryMaxFacts', type: 'number', default: 4 },
|
|
1647
|
+
{ displayName: 'Incluir Scores', name: 'includeScores', type: 'boolean', default: true },
|
|
1648
|
+
{ displayName: 'Incluir Diagnóstico', name: 'includeDiagnostics', type: 'boolean', default: false },
|
|
1649
|
+
{ displayName: 'Incluir Grafo', name: 'includeRelations', type: 'boolean', default: true },
|
|
1650
|
+
{ displayName: 'Máximo de Relações', name: 'maxRelations', type: 'number', default: 10 },
|
|
1651
|
+
{ displayName: 'Incluir Tool History', name: 'includeToolHistory', type: 'boolean', default: true },
|
|
1652
|
+
{ displayName: 'Incluir Resultado Das Tools', name: 'includeToolResults', type: 'boolean', default: true },
|
|
1653
|
+
{ displayName: 'Últimas N Tools', name: 'toolHistoryLastN', type: 'number', default: 15 },
|
|
1654
|
+
{ displayName: 'TTL Das Tools (Segundos)', name: 'toolHistoryTTLSeconds', type: 'number', default: 3600 },
|
|
1655
|
+
{ displayName: 'Persistir Tool Facts no Backend', name: 'persistToolFactsToMem0', type: 'boolean', default: false },
|
|
1656
|
+
{ displayName: 'Fallback Semântico Para Tools', name: 'includeToolHistorySemanticFallback', type: 'boolean', default: false, description: 'Usa memórias vetoriais para tentar inferir histórico de tools quando não houver eventos estruturados. Útil em laboratório, não recomendado como fonte primária em produção.' },
|
|
1657
|
+
{ displayName: 'Incluir Profile Facts', name: 'includeProfileFacts', type: 'boolean', default: true, description: 'Extrai e injeta fatos estáveis do lead/cliente como nome, empresa, email, telefone, preferências, interesses e contexto comercial.' },
|
|
1658
|
+
{ displayName: 'Incluir Working Memory', name: 'includeWorkingMemory', type: 'boolean', default: true, description: 'Injeta objetivo atual, intenção recente, entidades ativas, último erro e próxima ação esperada.' },
|
|
1659
|
+
{ displayName: 'Incluir Decision State', name: 'includeDecisionState', type: 'boolean', default: true, description: 'Injeta decisões ativas, ferramentas que não devem ser repetidas e política de resolução de conflitos.' },
|
|
1660
|
+
{ displayName: 'Incluir Estado Operacional', name: 'includeOperationalState', type: 'boolean', default: true, description: 'Injeta um resumo determinístico do estado das tools, pré-requisitos, reservas pendentes e próximo passo seguro.' },
|
|
1661
|
+
{ displayName: 'Incluir Action Ledger', name: 'includeActionLedger', type: 'boolean', default: true, description: 'Injeta um ledger cronológico e compacto das ações/tools executadas, com input, resultado e status.' },
|
|
1662
|
+
{ displayName: 'Incluir Entity Timeline', name: 'includeEntityTimeline', type: 'boolean', default: true, description: 'Injeta uma timeline compacta de entidades, fatos de perfil, relações do grafo e eventos importantes da sessão.' },
|
|
1663
|
+
{ displayName: 'Incluir Compressão de Memória', name: 'includeMemoryCompression', type: 'boolean', default: true, description: 'Injeta resumos compactos de turno, sessão, entidades e workflow para reduzir ruído.' },
|
|
1664
|
+
{ displayName: 'Incluir Mensagens Recentes', name: 'includeRecentMessages', type: 'boolean', default: true },
|
|
1665
|
+
{ displayName: 'Últimas N Mensagens', name: 'recentMessagesLastN', type: 'number', default: 8 },
|
|
1666
|
+
{ displayName: 'Incluir Highlights Recentes', name: 'includeRecentHighlights', type: 'boolean', default: true },
|
|
1667
|
+
{ displayName: 'Máximo de Highlights', name: 'recentHighlightsMaxItems', type: 'number', default: 6 },
|
|
1668
|
+
],
|
|
1669
|
+
},
|
|
1670
|
+
],
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
// For AI connections, n8n reads from supplyData. The AI Agent expects a
|
|
1674
|
+
// memory-like object, not raw JSON, so expose the LangChain memory contract.
|
|
1675
|
+
async supplyData(itemIndex) {
|
|
1676
|
+
const memoryKey = this.getNodeParameter('memoryKey', itemIndex);
|
|
1677
|
+
let currentMessages = [];
|
|
1678
|
+
const memory = {
|
|
1679
|
+
memoryKeys: [memoryKey],
|
|
1680
|
+
inputKey: 'input',
|
|
1681
|
+
outputKey: 'output',
|
|
1682
|
+
returnMessages: true,
|
|
1683
|
+
chatHistory: {
|
|
1684
|
+
getMessages: async () => currentMessages,
|
|
1685
|
+
addMessage: async (message) => {
|
|
1686
|
+
currentMessages.push(message);
|
|
1687
|
+
await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, [message]);
|
|
1688
|
+
},
|
|
1689
|
+
addUserMessage: async (message) => {
|
|
1690
|
+
const baseMessage = toBaseMessage({ role: 'user', content: message });
|
|
1691
|
+
currentMessages.push(baseMessage);
|
|
1692
|
+
await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, [baseMessage]);
|
|
1693
|
+
},
|
|
1694
|
+
addAIChatMessage: async (message) => {
|
|
1695
|
+
const baseMessage = toBaseMessage({ role: 'assistant', content: message });
|
|
1696
|
+
currentMessages.push(baseMessage);
|
|
1697
|
+
await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, [baseMessage]);
|
|
1698
|
+
},
|
|
1699
|
+
addMessages: async (messages) => {
|
|
1700
|
+
currentMessages.push(...messages);
|
|
1701
|
+
await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, messages);
|
|
1702
|
+
},
|
|
1703
|
+
clear: async () => {
|
|
1704
|
+
currentMessages = [];
|
|
1705
|
+
},
|
|
1706
|
+
},
|
|
1707
|
+
loadMemoryVariables: async (inputValues = {}) => {
|
|
1708
|
+
const result = await Mem0Memory.prototype.loadMemoryVariablesForItem.call(this, itemIndex, inputValues);
|
|
1709
|
+
const chatHistory = (result.response[memoryKey] || []).map(toBaseMessage);
|
|
1710
|
+
const extra = { ...(result.response || {}) };
|
|
1711
|
+
delete extra[memoryKey];
|
|
1712
|
+
currentMessages = chatHistory;
|
|
1713
|
+
return {
|
|
1714
|
+
...extra,
|
|
1715
|
+
[memoryKey]: chatHistory,
|
|
1716
|
+
chatHistory,
|
|
1717
|
+
};
|
|
1718
|
+
},
|
|
1719
|
+
saveContext: async (inputValues = {}, outputValues = {}) => {
|
|
1720
|
+
const input = pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']);
|
|
1721
|
+
const output = pickText(outputValues, ['output', 'response', 'text', 'answer']);
|
|
1722
|
+
if (input)
|
|
1723
|
+
currentMessages.push(toBaseMessage({ role: 'user', content: input }));
|
|
1724
|
+
if (output)
|
|
1725
|
+
currentMessages.push(toBaseMessage({ role: 'assistant', content: output }));
|
|
1726
|
+
await Mem0Memory.prototype.saveContextForItem.call(this, itemIndex, inputValues, outputValues);
|
|
1727
|
+
},
|
|
1728
|
+
};
|
|
1729
|
+
return { response: wrapElefaiMemory(memory, this, memoryKey) };
|
|
1730
|
+
}
|
|
1731
|
+
async saveMessagesForItem(itemIndex, messages = []) {
|
|
1732
|
+
const inputValues = {};
|
|
1733
|
+
const outputValues = {};
|
|
1734
|
+
const toolCalls = [];
|
|
1735
|
+
const inputParts = [];
|
|
1736
|
+
const outputParts = [];
|
|
1737
|
+
for (const message of messages || []) {
|
|
1738
|
+
const type = typeof (message === null || message === void 0 ? void 0 : message._getType) === 'function' ? message._getType() : String((message === null || message === void 0 ? void 0 : message.role) || '');
|
|
1739
|
+
const content = typeof (message === null || message === void 0 ? void 0 : message.content) === 'string' ? message.content : safeStringify(message === null || message === void 0 ? void 0 : message.content);
|
|
1740
|
+
if (type === 'human' || type === 'user')
|
|
1741
|
+
inputParts.push(content);
|
|
1742
|
+
else if (type === 'ai' || type === 'assistant')
|
|
1743
|
+
outputParts.push(content);
|
|
1744
|
+
else if (type === 'tool') {
|
|
1745
|
+
toolCalls.push({
|
|
1746
|
+
name: message.name || message.toolName || message.tool || (message.additional_kwargs && (message.additional_kwargs.name || message.additional_kwargs.tool_name)) || 'tool',
|
|
1747
|
+
input: '',
|
|
1748
|
+
ok: true,
|
|
1749
|
+
result: summarizeToolResult(content),
|
|
1750
|
+
at: nowIso(),
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
if (inputParts.length)
|
|
1755
|
+
inputValues.input = inputParts.join('\n');
|
|
1756
|
+
const toolContext = toolCalls.length
|
|
1757
|
+
? `[Used tools: ${toolCalls.map((tool) => `Tool: ${tool.name}, Input: ${tool.input}, Result: ${tool.result}`).join('; ')}] `
|
|
1758
|
+
: '';
|
|
1759
|
+
if (outputParts.length || toolContext)
|
|
1760
|
+
outputValues.output = `${toolContext}${outputParts.join('\n')}`.trim();
|
|
1761
|
+
if (inputValues.input || outputValues.output)
|
|
1762
|
+
await Mem0Memory.prototype.saveContextForItem.call(this, itemIndex, inputValues, outputValues);
|
|
1763
|
+
}
|
|
1764
|
+
async saveContextForItem(itemIndex, inputValues = {}, outputValues = {}) {
|
|
1765
|
+
const threadId = this.getNodeParameter('threadId', itemIndex);
|
|
1766
|
+
const project = this.getNodeParameter('project', itemIndex, '');
|
|
1767
|
+
const adv = applyOperationalPreset(this.getNodeParameter('advanced', itemIndex, {}) || {});
|
|
1768
|
+
const store = getMemoryStore(this);
|
|
1769
|
+
const key = userKeyFrom(threadId, adv, project);
|
|
1770
|
+
const input = pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']);
|
|
1771
|
+
const output = pickText(outputValues, ['output', 'response', 'text', 'answer']);
|
|
1772
|
+
const toolCalls = extractToolCalls(outputValues);
|
|
1773
|
+
const recentForMem0 = [];
|
|
1774
|
+
const profileFromTurn = extractProfileFactsFromText(input, 'user_message', nowIso());
|
|
1775
|
+
if (Object.keys(profileFromTurn).length) {
|
|
1776
|
+
store.profileFacts[key] = mergeProfileFacts(store.profileFacts[key], profileFromTurn);
|
|
1777
|
+
try {
|
|
1778
|
+
const globalData = this.getWorkflowStaticData('global');
|
|
1779
|
+
globalData.__dataChanged = true;
|
|
1780
|
+
}
|
|
1781
|
+
catch { }
|
|
1782
|
+
}
|
|
1783
|
+
if (toolCalls.length) {
|
|
1784
|
+
const existing = store.toolHistory[key] || [];
|
|
1785
|
+
store.toolHistory[key] = applyToolHistoryWindow(existing.concat(toolCalls), adv.toolHistoryTTLSeconds, adv.toolHistoryLastN || 15);
|
|
1786
|
+
try {
|
|
1787
|
+
const globalData = this.getWorkflowStaticData('global');
|
|
1788
|
+
globalData.__dataChanged = true;
|
|
1789
|
+
}
|
|
1790
|
+
catch { }
|
|
1791
|
+
}
|
|
1792
|
+
if (adv.includeRecentMessages !== false) {
|
|
1793
|
+
const recent = store.recentMessages[key] || [];
|
|
1794
|
+
if (input) {
|
|
1795
|
+
const item = { role: 'user', content: truncate(input, 2000), at: nowIso() };
|
|
1796
|
+
recent.push(item);
|
|
1797
|
+
recentForMem0.push(item);
|
|
1798
|
+
}
|
|
1799
|
+
if (output) {
|
|
1800
|
+
const item = { role: 'assistant', content: truncate(output, 2000), at: nowIso() };
|
|
1801
|
+
recent.push(item);
|
|
1802
|
+
recentForMem0.push(item);
|
|
1803
|
+
}
|
|
1804
|
+
store.recentMessages[key] = pruneByLimit(recent, adv.recentMessagesLastN || 8);
|
|
1805
|
+
try {
|
|
1806
|
+
const globalData = this.getWorkflowStaticData('global');
|
|
1807
|
+
globalData.__dataChanged = true;
|
|
1808
|
+
}
|
|
1809
|
+
catch { }
|
|
1810
|
+
}
|
|
1811
|
+
const renderedProfileForTurn = renderProfileFacts(store.profileFacts[key] || {});
|
|
1812
|
+
const recentForTurn = store.recentMessages[key] || [];
|
|
1813
|
+
const toolHistoryForTurn = store.toolHistory[key] || [];
|
|
1814
|
+
const operationalStateForTurn = deriveOperationalState(toolHistoryForTurn, renderedProfileForTurn, recentForTurn, adv.includeToolResults !== false);
|
|
1815
|
+
const workingMemoryForTurn = deriveWorkingMemory({
|
|
1816
|
+
query: input,
|
|
1817
|
+
profileFacts: renderedProfileForTurn,
|
|
1818
|
+
recentMessages: recentForTurn,
|
|
1819
|
+
toolHistory: toolHistoryForTurn,
|
|
1820
|
+
operationalState: operationalStateForTurn,
|
|
1821
|
+
previous: store.workingMemory[key] || {},
|
|
1822
|
+
});
|
|
1823
|
+
const decisionStateForTurn = deriveDecisionState({
|
|
1824
|
+
query: input,
|
|
1825
|
+
toolHistory: toolHistoryForTurn,
|
|
1826
|
+
operationalState: operationalStateForTurn,
|
|
1827
|
+
workingMemory: workingMemoryForTurn,
|
|
1828
|
+
});
|
|
1829
|
+
const compressionForTurn = deriveMemoryCompression({
|
|
1830
|
+
recentMessages: recentForTurn,
|
|
1831
|
+
toolHistory: toolHistoryForTurn,
|
|
1832
|
+
profileFacts: store.profileFacts[key] || {},
|
|
1833
|
+
operationalState: operationalStateForTurn,
|
|
1834
|
+
vectorMemories: [],
|
|
1835
|
+
maxItems: adv.compressionMaxItems || 6,
|
|
1836
|
+
});
|
|
1837
|
+
store.workingMemory[key] = workingMemoryForTurn;
|
|
1838
|
+
store.decisionState[key] = decisionStateForTurn;
|
|
1839
|
+
store.memoryCompression[key] = compressionForTurn;
|
|
1840
|
+
try {
|
|
1841
|
+
const globalData = this.getWorkflowStaticData('global');
|
|
1842
|
+
globalData.__dataChanged = true;
|
|
1843
|
+
}
|
|
1844
|
+
catch { }
|
|
1845
|
+
const messages = [];
|
|
1846
|
+
if (input)
|
|
1847
|
+
messages.push({ role: 'user', content: input });
|
|
1848
|
+
if (output)
|
|
1849
|
+
messages.push({ role: 'assistant', content: output });
|
|
1850
|
+
if (!messages.length)
|
|
1851
|
+
return;
|
|
1852
|
+
const body = { messages };
|
|
1853
|
+
body.user_id = key;
|
|
1854
|
+
if (adv.agentId)
|
|
1855
|
+
body.agent_id = String(adv.agentId);
|
|
1856
|
+
if (adv.appId)
|
|
1857
|
+
body.app_id = String(adv.appId);
|
|
1858
|
+
if (adv.runId)
|
|
1859
|
+
body.run_id = String(adv.runId);
|
|
1860
|
+
const connectedEmbedding = await getConnectedEmbedding(this, itemIndex);
|
|
1861
|
+
if (connectedEmbedding) {
|
|
1862
|
+
const ids = { user_id: body.user_id, agent_id: body.agent_id, run_id: body.run_id };
|
|
1863
|
+
const clientMemories = [];
|
|
1864
|
+
for (const message of messages) {
|
|
1865
|
+
clientMemories.push(await createClientVectorMemory(connectedEmbedding, message.content, {
|
|
1866
|
+
kind: 'conversation_message',
|
|
1867
|
+
role: message.role,
|
|
1868
|
+
thread_id: threadId,
|
|
1869
|
+
project: project || undefined,
|
|
1870
|
+
at: nowIso(),
|
|
1871
|
+
source: 'n8n_connected_embedding',
|
|
1872
|
+
}, ids));
|
|
1873
|
+
}
|
|
1874
|
+
if (adv.includeRecentMessages !== false && recentForMem0.length) {
|
|
1875
|
+
for (const recent of recentForMem0) {
|
|
1876
|
+
clientMemories.push(await createClientVectorMemory(connectedEmbedding, encodeRecentMessage(recent, threadId), {
|
|
1877
|
+
kind: 'recent_message',
|
|
1878
|
+
role: recent.role,
|
|
1879
|
+
content: recent.content,
|
|
1880
|
+
at: recent.at,
|
|
1881
|
+
thread_id: threadId,
|
|
1882
|
+
project: project || undefined,
|
|
1883
|
+
source: 'n8n_connected_embedding',
|
|
1884
|
+
}, ids));
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
if (adv.persistToolFactsToMem0 && toolCalls.length) {
|
|
1888
|
+
const facts = toolCalls.map((tool) => `Tool ${tool.name} input=${tool.input}${tool.result ? ` result=${tool.result}` : ''}`).join('\n');
|
|
1889
|
+
clientMemories.push(await createClientVectorMemory(connectedEmbedding, `Tool facts (read-only):\n${truncate(facts, 2000)}`, {
|
|
1890
|
+
kind: 'tool_facts',
|
|
1891
|
+
source: 'n8n_connected_embedding',
|
|
1892
|
+
}, ids));
|
|
1893
|
+
for (const tool of toolCalls) {
|
|
1894
|
+
clientMemories.push(await createClientVectorMemory(connectedEmbedding, encodeToolCall(tool, threadId), {
|
|
1895
|
+
kind: 'tool_history',
|
|
1896
|
+
id: tool.id,
|
|
1897
|
+
sequence: tool.sequence,
|
|
1898
|
+
turn_id: tool.turnId,
|
|
1899
|
+
name: tool.name,
|
|
1900
|
+
input: tool.input,
|
|
1901
|
+
ok: tool.ok,
|
|
1902
|
+
result: tool.result,
|
|
1903
|
+
at: tool.at,
|
|
1904
|
+
source: tool.source || 'n8n_connected_embedding',
|
|
1905
|
+
thread_id: threadId,
|
|
1906
|
+
}, ids));
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
await saveClientVectorMemories(this, clientMemories, ids);
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
await GenericFunctions_1.mem0ApiRequest.call(this, 'POST', '/v1/memories/', body);
|
|
1913
|
+
if (adv.includeRecentMessages !== false && recentForMem0.length) {
|
|
1914
|
+
for (const recent of recentForMem0) {
|
|
1915
|
+
await GenericFunctions_1.mem0ApiRequest.call(this, 'POST', '/v1/memories/', {
|
|
1916
|
+
messages: [{ role: 'system', content: encodeRecentMessage(recent, threadId) }],
|
|
1917
|
+
infer: false,
|
|
1918
|
+
user_id: body.user_id,
|
|
1919
|
+
agent_id: body.agent_id,
|
|
1920
|
+
run_id: body.run_id,
|
|
1921
|
+
metadata: {
|
|
1922
|
+
kind: 'recent_message',
|
|
1923
|
+
role: recent.role,
|
|
1924
|
+
content: recent.content,
|
|
1925
|
+
at: recent.at,
|
|
1926
|
+
thread_id: threadId,
|
|
1927
|
+
project: project || undefined,
|
|
1928
|
+
},
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
if (adv.persistToolFactsToMem0 && toolCalls.length) {
|
|
1933
|
+
const facts = toolCalls.map((tool) => `Tool ${tool.name} input=${tool.input}${tool.result ? ` result=${tool.result}` : ''}`).join('\n');
|
|
1934
|
+
await GenericFunctions_1.mem0ApiRequest.call(this, 'POST', '/v1/memories/', {
|
|
1935
|
+
messages: [{ role: 'system', content: `Tool facts (read-only):\n${truncate(facts, 2000)}` }],
|
|
1936
|
+
infer: false,
|
|
1937
|
+
user_id: body.user_id,
|
|
1938
|
+
metadata: { kind: 'tool_facts' },
|
|
1939
|
+
});
|
|
1940
|
+
for (const tool of toolCalls) {
|
|
1941
|
+
await GenericFunctions_1.mem0ApiRequest.call(this, 'POST', '/v1/memories/', {
|
|
1942
|
+
messages: [{ role: 'system', content: encodeToolCall(tool, threadId) }],
|
|
1943
|
+
infer: false,
|
|
1944
|
+
user_id: body.user_id,
|
|
1945
|
+
agent_id: body.agent_id,
|
|
1946
|
+
run_id: body.run_id,
|
|
1947
|
+
metadata: {
|
|
1948
|
+
kind: 'tool_history',
|
|
1949
|
+
id: tool.id,
|
|
1950
|
+
sequence: tool.sequence,
|
|
1951
|
+
turn_id: tool.turnId,
|
|
1952
|
+
name: tool.name,
|
|
1953
|
+
input: tool.input,
|
|
1954
|
+
ok: tool.ok,
|
|
1955
|
+
result: tool.result,
|
|
1956
|
+
at: tool.at,
|
|
1957
|
+
source: tool.source,
|
|
1958
|
+
thread_id: threadId,
|
|
1959
|
+
},
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
async loadMemoryVariablesForItem(itemIndex, inputValues = {}) {
|
|
1965
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1966
|
+
const memoryKey = this.getNodeParameter('memoryKey', itemIndex);
|
|
1967
|
+
const retrievalMode = this.getNodeParameter('retrievalMode', itemIndex);
|
|
1968
|
+
let payloadFormat = this.getNodeParameter('payloadFormat', itemIndex, 'structured');
|
|
1969
|
+
const threadId = this.getNodeParameter('threadId', itemIndex);
|
|
1970
|
+
const project = this.getNodeParameter('project', itemIndex, '');
|
|
1971
|
+
const adv = applyOperationalPreset(this.getNodeParameter('advanced', itemIndex, {}) || {});
|
|
1972
|
+
payloadFormat = adv.payloadFormat || payloadFormat;
|
|
1973
|
+
const store = getMemoryStore(this);
|
|
1974
|
+
const key = userKeyFrom(threadId, adv, project);
|
|
1975
|
+
const queryParam = this.getNodeParameter('query', itemIndex, '');
|
|
1976
|
+
const query = asSearchQuery(queryParam) || asSearchQuery(inputValues.query) || asSearchQuery(inputValues.input) || asSearchQuery(inputValues.chatInput);
|
|
1977
|
+
let connectedLanguageModel;
|
|
1978
|
+
let connectedEmbedding;
|
|
1979
|
+
const connectedAi = {
|
|
1980
|
+
languageModel: false,
|
|
1981
|
+
embedding: false,
|
|
1982
|
+
embeddingQuery: false,
|
|
1983
|
+
languageModelSummary: false,
|
|
1984
|
+
errors: [],
|
|
1985
|
+
};
|
|
1986
|
+
try {
|
|
1987
|
+
connectedLanguageModel = await this.getInputConnectionData(n8n_workflow_1.NodeConnectionTypes.AiLanguageModel, itemIndex);
|
|
1988
|
+
connectedAi.languageModel = true;
|
|
1989
|
+
}
|
|
1990
|
+
catch (error) {
|
|
1991
|
+
connectedAi.errors.push(`languageModel: ${error.message || String(error)}`);
|
|
1992
|
+
}
|
|
1993
|
+
try {
|
|
1994
|
+
connectedEmbedding = await this.getInputConnectionData(n8n_workflow_1.NodeConnectionTypes.AiEmbedding, itemIndex);
|
|
1995
|
+
connectedAi.embedding = true;
|
|
1996
|
+
}
|
|
1997
|
+
catch (error) {
|
|
1998
|
+
connectedAi.errors.push(`embedding: ${error.message || String(error)}`);
|
|
1999
|
+
}
|
|
2000
|
+
if (connectedEmbedding && typeof connectedEmbedding.embedQuery === 'function' && String(query || '').trim()) {
|
|
2001
|
+
try {
|
|
2002
|
+
await connectedEmbedding.embedQuery(String(query));
|
|
2003
|
+
connectedAi.embeddingQuery = true;
|
|
2004
|
+
}
|
|
2005
|
+
catch (error) {
|
|
2006
|
+
connectedAi.errors.push(`embedding.embedQuery: ${error.message || String(error)}`);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
try {
|
|
2010
|
+
this.logger?.debug('Tembory loading memory variables before model invocation', {
|
|
2011
|
+
itemIndex,
|
|
2012
|
+
retrievalMode,
|
|
2013
|
+
hasQuery: String(query || '').trim().length > 0,
|
|
2014
|
+
connectedAi,
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
catch { }
|
|
2018
|
+
let payload;
|
|
2019
|
+
let vectorMemories = [];
|
|
2020
|
+
if (retrievalMode === 'semantic' || retrievalMode === 'semanticV2' || retrievalMode === 'hybrid') {
|
|
2021
|
+
const body = { query: String(query || '') };
|
|
2022
|
+
// IDs
|
|
2023
|
+
body.user_id = key;
|
|
2024
|
+
if (adv.agentId)
|
|
2025
|
+
body.agent_id = String(adv.agentId);
|
|
2026
|
+
if (adv.appId)
|
|
2027
|
+
body.app_id = String(adv.appId);
|
|
2028
|
+
if (adv.runId)
|
|
2029
|
+
body.run_id = String(adv.runId);
|
|
2030
|
+
// Options
|
|
2031
|
+
if (adv.topK)
|
|
2032
|
+
body.top_k = Number(adv.topK);
|
|
2033
|
+
if (adv.rerank !== undefined)
|
|
2034
|
+
body.rerank = Boolean(adv.rerank);
|
|
2035
|
+
if (typeof adv.fields === 'string' && adv.fields)
|
|
2036
|
+
body.fields = String(adv.fields).split(',').map((f) => f.trim());
|
|
2037
|
+
if (retrievalMode === 'semanticV2' || retrievalMode === 'hybrid') {
|
|
2038
|
+
try {
|
|
2039
|
+
const filters = typeof adv.filters === 'string' ? JSON.parse(adv.filters) : (adv.filters || {});
|
|
2040
|
+
body.filters = filters;
|
|
2041
|
+
}
|
|
2042
|
+
catch (error) {
|
|
2043
|
+
connectedAi.errors.push(`filters.parse: ${error.message || String(error)}`);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
const hasQuery = String(query || '').trim().length > 0;
|
|
2047
|
+
const url = retrievalMode === 'semanticV2' || retrievalMode === 'hybrid' ? '/v2/memories/search/' : '/v1/memories/search/';
|
|
2048
|
+
const semRes = hasQuery && connectedEmbedding
|
|
2049
|
+
? await searchClientVectorMemories(this, connectedEmbedding, query, body)
|
|
2050
|
+
: hasQuery
|
|
2051
|
+
? await GenericFunctions_1.mem0ApiRequest.call(this, 'POST', url, body)
|
|
2052
|
+
: { results: [] };
|
|
2053
|
+
let semMemories = normalizeResults(semRes);
|
|
2054
|
+
if (retrievalMode === 'hybrid' || !hasQuery) {
|
|
2055
|
+
// coletar recentes
|
|
2056
|
+
const qs = {};
|
|
2057
|
+
qs.user_id = key;
|
|
2058
|
+
if (adv.agentId)
|
|
2059
|
+
qs.agent_id = String(adv.agentId);
|
|
2060
|
+
if (adv.appId)
|
|
2061
|
+
qs.app_id = String(adv.appId);
|
|
2062
|
+
if (adv.runId)
|
|
2063
|
+
qs.run_id = String(adv.runId);
|
|
2064
|
+
const recRes = await GenericFunctions_1.mem0ApiRequest.call(this, 'GET', '/v1/memories/', {}, qs);
|
|
2065
|
+
let recents = normalizeResults(recRes);
|
|
2066
|
+
const lastN = Number((_a = adv.lastN) !== null && _a !== void 0 ? _a : 20);
|
|
2067
|
+
if (lastN > 0)
|
|
2068
|
+
recents = recents.slice(-lastN);
|
|
2069
|
+
// scoring híbrido
|
|
2070
|
+
const alpha = Number((_b = adv.alpha) !== null && _b !== void 0 ? _b : 0.65);
|
|
2071
|
+
const halfLife = Number((_c = adv.halfLifeHours) !== null && _c !== void 0 ? _c : 48);
|
|
2072
|
+
const maxReturn = Number((_d = adv.maxReturn) !== null && _d !== void 0 ? _d : 30);
|
|
2073
|
+
const mmr = adv.mmr !== undefined ? Boolean(adv.mmr) : true;
|
|
2074
|
+
const mmrLambda = Number((_e = adv.mmrLambda) !== null && _e !== void 0 ? _e : 0.5);
|
|
2075
|
+
const LN2 = Math.log(2);
|
|
2076
|
+
const now = Date.now();
|
|
2077
|
+
const idOf = (m) => m.id || m.uuid || m._id || m.memory_id || m.pk || JSON.stringify(m).slice(0, 1000);
|
|
2078
|
+
const createdOf = (m) => m.created_at || m.createdAt || m.timestamp || m.time || m.date;
|
|
2079
|
+
const rankingScoreOf = (m) => { var _a; return (_a = scoreOf(m)) !== null && _a !== void 0 ? _a : m.rank !== undefined ? Number(m.rank) : 1; };
|
|
2080
|
+
const merged = new Map();
|
|
2081
|
+
// add semantic part
|
|
2082
|
+
for (const m of semMemories) {
|
|
2083
|
+
const id = idOf(m);
|
|
2084
|
+
const created = createdOf(m);
|
|
2085
|
+
let recency = 0.5;
|
|
2086
|
+
if (created) {
|
|
2087
|
+
const t = new Date(created).getTime();
|
|
2088
|
+
if (!isNaN(t)) {
|
|
2089
|
+
const ageH = Math.max(0, (now - t) / 3600000);
|
|
2090
|
+
recency = Math.exp(-LN2 * (ageH / Math.max(1, halfLife)));
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
const sem = Number(rankingScoreOf(m));
|
|
2094
|
+
const hybrid = alpha * sem + (1 - alpha) * recency;
|
|
2095
|
+
merged.set(id, { m, semanticScore: sem, recencyScore: recency, hybrid, source: 'semantic' });
|
|
2096
|
+
}
|
|
2097
|
+
// add recents part
|
|
2098
|
+
for (const m of recents) {
|
|
2099
|
+
const id = idOf(m);
|
|
2100
|
+
const created = createdOf(m);
|
|
2101
|
+
let recency = 0.7;
|
|
2102
|
+
if (created) {
|
|
2103
|
+
const t = new Date(created).getTime();
|
|
2104
|
+
if (!isNaN(t)) {
|
|
2105
|
+
const ageH = Math.max(0, (now - t) / 3600000);
|
|
2106
|
+
recency = Math.exp(-LN2 * (ageH / Math.max(1, halfLife)));
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
const prev = merged.get(id);
|
|
2110
|
+
const sem = (_f = prev === null || prev === void 0 ? void 0 : prev.semanticScore) !== null && _f !== void 0 ? _f : 0;
|
|
2111
|
+
const hybrid = alpha * sem + (1 - alpha) * recency;
|
|
2112
|
+
merged.set(id, { m: (_g = prev === null || prev === void 0 ? void 0 : prev.m) !== null && _g !== void 0 ? _g : m, semanticScore: sem, recencyScore: recency, hybrid, source: prev ? 'hybrid' : 'recent' });
|
|
2113
|
+
}
|
|
2114
|
+
let ranked = Array.from(merged.values());
|
|
2115
|
+
ranked.sort((a, b) => b.hybrid - a.hybrid);
|
|
2116
|
+
if (mmr && ranked.length > 2) {
|
|
2117
|
+
// MMR greedy (sem embeddings; usa score como proxy)
|
|
2118
|
+
const selected = [];
|
|
2119
|
+
const rest = [...ranked];
|
|
2120
|
+
// pick top-1
|
|
2121
|
+
selected.push(rest.shift());
|
|
2122
|
+
while (selected.length < Math.min(maxReturn, ranked.length) && rest.length) {
|
|
2123
|
+
let bestIdx = 0;
|
|
2124
|
+
let bestScore = -Infinity;
|
|
2125
|
+
for (let i = 0; i < rest.length; i++) {
|
|
2126
|
+
const cand = rest[i];
|
|
2127
|
+
const rel = cand.hybrid;
|
|
2128
|
+
// diversidade simples: penaliza se muito parecido com já escolhidos (aproximação via diferença de score)
|
|
2129
|
+
const sim = Math.max(...selected.map((s) => 1 - Math.abs(s.hybrid - cand.hybrid)));
|
|
2130
|
+
const mmrScore = mmrLambda * rel - (1 - mmrLambda) * sim;
|
|
2131
|
+
if (mmrScore > bestScore) {
|
|
2132
|
+
bestScore = mmrScore;
|
|
2133
|
+
bestIdx = i;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
selected.push(rest.splice(bestIdx, 1)[0]);
|
|
2137
|
+
}
|
|
2138
|
+
ranked = selected;
|
|
2139
|
+
}
|
|
2140
|
+
const finalMemories = ranked.slice(0, maxReturn).map((r) => withElefaiScore(r.m, {
|
|
2141
|
+
semanticScore: r.semanticScore,
|
|
2142
|
+
recencyScore: r.recencyScore,
|
|
2143
|
+
hybridScore: r.hybrid,
|
|
2144
|
+
source: r.source,
|
|
2145
|
+
}));
|
|
2146
|
+
vectorMemories = finalMemories;
|
|
2147
|
+
payload = finalMemories.map((m) => { var _a, _b; return ({ role: 'system', content: (_b = (_a = m.memory) !== null && _a !== void 0 ? _a : m.text) !== null && _b !== void 0 ? _b : JSON.stringify(m) }); });
|
|
2148
|
+
}
|
|
2149
|
+
else {
|
|
2150
|
+
vectorMemories = semMemories.map((m) => withElefaiScore(m, {
|
|
2151
|
+
semanticScore: scoreOf(m),
|
|
2152
|
+
source: 'semantic',
|
|
2153
|
+
}));
|
|
2154
|
+
payload = semMemories.map((m) => { var _a, _b; return ({ role: 'system', content: (_b = (_a = m.memory) !== null && _a !== void 0 ? _a : m.text) !== null && _b !== void 0 ? _b : JSON.stringify(m) }); });
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
else {
|
|
2158
|
+
const qs = {};
|
|
2159
|
+
qs.user_id = key;
|
|
2160
|
+
if (adv.agentId)
|
|
2161
|
+
qs.agent_id = String(adv.agentId);
|
|
2162
|
+
if (adv.appId)
|
|
2163
|
+
qs.app_id = String(adv.appId);
|
|
2164
|
+
if (adv.runId)
|
|
2165
|
+
qs.run_id = String(adv.runId);
|
|
2166
|
+
const res = await GenericFunctions_1.mem0ApiRequest.call(this, 'GET', '/v1/memories/', {}, qs);
|
|
2167
|
+
let memories = normalizeResults(res);
|
|
2168
|
+
// lastN limit
|
|
2169
|
+
if (adv.lastN && Number(adv.lastN) > 0)
|
|
2170
|
+
memories = memories.slice(-Number(adv.lastN));
|
|
2171
|
+
vectorMemories = memories;
|
|
2172
|
+
if (retrievalMode === 'summary') {
|
|
2173
|
+
const text = memories
|
|
2174
|
+
.map((m) => m.memory || m.text || m.value)
|
|
2175
|
+
.filter(Boolean)
|
|
2176
|
+
.join('\n');
|
|
2177
|
+
payload = [{ role: 'system', content: `Summary of memories:\n${text}` }];
|
|
2178
|
+
}
|
|
2179
|
+
else {
|
|
2180
|
+
payload = memories.map((m) => { var _a, _b; return ({ role: 'system', content: (_b = (_a = m.memory) !== null && _a !== void 0 ? _a : m.text) !== null && _b !== void 0 ? _b : JSON.stringify(m) }); });
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
let graph = [];
|
|
2184
|
+
if (adv.includeRelations !== false) {
|
|
2185
|
+
try {
|
|
2186
|
+
const entityRes = await GenericFunctions_1.mem0ApiRequest.call(this, 'GET', '/entities/relations', {}, { user_id: key, agent_id: adv.agentId || undefined, run_id: adv.runId || undefined, limit: Number(adv.maxRelations || 10) });
|
|
2187
|
+
graph = normalizeResults(entityRes).slice(0, Number(adv.maxRelations || 10));
|
|
2188
|
+
}
|
|
2189
|
+
catch (error) {
|
|
2190
|
+
connectedAi.errors.push(`graph.relations: ${error.message || String(error)}`);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
let persistedRecentMessages = [];
|
|
2194
|
+
let persistedToolHistory = [];
|
|
2195
|
+
if (adv.includeRecentMessages !== false) {
|
|
2196
|
+
try {
|
|
2197
|
+
const qs = { user_id: key };
|
|
2198
|
+
if (adv.agentId)
|
|
2199
|
+
qs.agent_id = String(adv.agentId);
|
|
2200
|
+
if (adv.runId)
|
|
2201
|
+
qs.run_id = String(adv.runId);
|
|
2202
|
+
const recentRes = await GenericFunctions_1.mem0ApiRequest.call(this, 'GET', '/v1/memories/', {}, qs);
|
|
2203
|
+
persistedRecentMessages = normalizeResults(recentRes)
|
|
2204
|
+
.map(recentMessageFromMemory)
|
|
2205
|
+
.filter(Boolean)
|
|
2206
|
+
.sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
|
|
2207
|
+
}
|
|
2208
|
+
catch (error) {
|
|
2209
|
+
connectedAi.errors.push(`recentMessages.load: ${error.message || String(error)}`);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
if (adv.includeToolHistory !== false) {
|
|
2213
|
+
try {
|
|
2214
|
+
const qs = { user_id: key };
|
|
2215
|
+
if (adv.agentId)
|
|
2216
|
+
qs.agent_id = String(adv.agentId);
|
|
2217
|
+
if (adv.runId)
|
|
2218
|
+
qs.run_id = String(adv.runId);
|
|
2219
|
+
const toolRes = await GenericFunctions_1.mem0ApiRequest.call(this, 'GET', '/v1/memories/', {}, qs);
|
|
2220
|
+
persistedToolHistory = normalizeResults(toolRes)
|
|
2221
|
+
.flatMap(toolHistoryItemsFromMemory)
|
|
2222
|
+
.sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
|
|
2223
|
+
}
|
|
2224
|
+
catch (error) {
|
|
2225
|
+
connectedAi.errors.push(`toolHistory.load: ${error.message || String(error)}`);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
const allRecentMessages = dedupeRecentMessages((store.recentMessages[key] || []).concat(persistedRecentMessages));
|
|
2229
|
+
const recentMessages = adv.includeRecentMessages === false ? [] : pruneByLimit(allRecentMessages, adv.recentMessagesLastN || 8);
|
|
2230
|
+
const toolHistoryFromRecentMessages = [];
|
|
2231
|
+
if (adv.includeToolHistory !== false) {
|
|
2232
|
+
for (const msg of recentMessages)
|
|
2233
|
+
toolHistoryFromRecentMessages.push(...extractToolCallsFromText(msg.content, msg.at));
|
|
2234
|
+
}
|
|
2235
|
+
const toolHistoryFromVectorMemories = adv.includeToolHistory === false || adv.includeToolHistorySemanticFallback !== true ? [] : vectorMemories.flatMap(toolHistoryItemsFromMemory);
|
|
2236
|
+
const toolHistory = adv.includeToolHistory === false ? [] : applyToolHistoryWindow((store.toolHistory[key] || []).concat(persistedToolHistory, toolHistoryFromRecentMessages, toolHistoryFromVectorMemories), adv.toolHistoryTTLSeconds, adv.toolHistoryLastN || 15);
|
|
2237
|
+
const highlights = adv.includeRecentHighlights === false ? [] : getRecentHighlights(recentMessages, toolHistory, adv.recentHighlightsMaxItems || 6);
|
|
2238
|
+
const profileFacts = adv.includeProfileFacts === false ? {} : mergeProfileFacts(store.profileFacts[key], profileFactsFromMessages(allRecentMessages), profileFactsFromMemories(vectorMemories));
|
|
2239
|
+
if (adv.includeProfileFacts !== false && Object.keys(profileFacts).length) {
|
|
2240
|
+
store.profileFacts[key] = mergeProfileFacts(store.profileFacts[key], profileFacts);
|
|
2241
|
+
try {
|
|
2242
|
+
const globalData = this.getWorkflowStaticData('global');
|
|
2243
|
+
globalData.__dataChanged = true;
|
|
2244
|
+
}
|
|
2245
|
+
catch { }
|
|
2246
|
+
}
|
|
2247
|
+
let connectedModelSummary = '';
|
|
2248
|
+
if (connectedLanguageModel && typeof connectedLanguageModel.invoke === 'function' && (vectorMemories.length || String(query || '').trim()) && adv.includeSummary !== false) {
|
|
2249
|
+
try {
|
|
2250
|
+
const facts = vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map(memoryText).filter(Boolean).join('\n') || '(no vector memories found)';
|
|
2251
|
+
if (facts || String(query || '').trim()) {
|
|
2252
|
+
const response = await connectedLanguageModel.invoke([
|
|
2253
|
+
toBaseMessage({
|
|
2254
|
+
role: 'user',
|
|
2255
|
+
content: `Summarize available Tembory context as concise factual bullets. If there are no memories, return exactly "(empty)".\n\nQuery: ${String(query || '')}\n\nMemories:\n${truncate(facts, 3000)}`,
|
|
2256
|
+
}),
|
|
2257
|
+
]);
|
|
2258
|
+
connectedModelSummary = truncate(response?.content || response?.text || String(response || ''), 1200);
|
|
2259
|
+
connectedAi.languageModelSummary = true;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
catch (error) {
|
|
2263
|
+
connectedAi.errors.push(`languageModel.invoke: ${error.message || String(error)}`);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
const diagnostics = {
|
|
2267
|
+
vectorMemories: vectorMemories.length,
|
|
2268
|
+
recentMessages: recentMessages.length,
|
|
2269
|
+
toolHistory: toolHistory.length,
|
|
2270
|
+
graph: graph.length,
|
|
2271
|
+
project: project || null,
|
|
2272
|
+
memoryNamespace: key,
|
|
2273
|
+
connectedAi,
|
|
2274
|
+
};
|
|
2275
|
+
const includeToolResults = adv.includeToolResults !== false;
|
|
2276
|
+
const operationalState = deriveOperationalState(toolHistory, renderProfileFacts(profileFacts), recentMessages, includeToolResults);
|
|
2277
|
+
const actionLedger = deriveActionLedger(toolHistory, adv.actionLedgerMaxItems || adv.toolHistoryLastN || 20, includeToolResults);
|
|
2278
|
+
const entityTimeline = deriveEntityTimeline(profileFacts, graph, recentMessages, toolHistory, adv.entityTimelineMaxItems || 24);
|
|
2279
|
+
const workingMemory = deriveWorkingMemory({
|
|
2280
|
+
query,
|
|
2281
|
+
profileFacts: renderProfileFacts(profileFacts),
|
|
2282
|
+
recentMessages,
|
|
2283
|
+
toolHistory,
|
|
2284
|
+
operationalState,
|
|
2285
|
+
previous: store.workingMemory[key] || {},
|
|
2286
|
+
});
|
|
2287
|
+
const decisionState = deriveDecisionState({
|
|
2288
|
+
query,
|
|
2289
|
+
toolHistory,
|
|
2290
|
+
operationalState,
|
|
2291
|
+
workingMemory,
|
|
2292
|
+
});
|
|
2293
|
+
const memoryCompression = deriveMemoryCompression({
|
|
2294
|
+
recentMessages,
|
|
2295
|
+
toolHistory,
|
|
2296
|
+
profileFacts,
|
|
2297
|
+
operationalState,
|
|
2298
|
+
vectorMemories,
|
|
2299
|
+
maxItems: adv.compressionMaxItems || 6,
|
|
2300
|
+
});
|
|
2301
|
+
const contextHealth = deriveContextHealth({
|
|
2302
|
+
userId: key,
|
|
2303
|
+
project,
|
|
2304
|
+
vectorMemories,
|
|
2305
|
+
recentMessages,
|
|
2306
|
+
toolHistory,
|
|
2307
|
+
workingMemory,
|
|
2308
|
+
decisionState,
|
|
2309
|
+
memoryCompression,
|
|
2310
|
+
operationalState,
|
|
2311
|
+
diagnostics,
|
|
2312
|
+
});
|
|
2313
|
+
store.workingMemory[key] = workingMemory;
|
|
2314
|
+
store.decisionState[key] = decisionState;
|
|
2315
|
+
store.memoryCompression[key] = memoryCompression;
|
|
2316
|
+
try {
|
|
2317
|
+
const globalData = this.getWorkflowStaticData('global');
|
|
2318
|
+
globalData.__dataChanged = true;
|
|
2319
|
+
}
|
|
2320
|
+
catch { }
|
|
2321
|
+
payload = buildContextMessages({
|
|
2322
|
+
payloadFormat,
|
|
2323
|
+
query,
|
|
2324
|
+
userId: key,
|
|
2325
|
+
profileFacts,
|
|
2326
|
+
workingMemory,
|
|
2327
|
+
decisionState,
|
|
2328
|
+
memoryCompression,
|
|
2329
|
+
operationalState,
|
|
2330
|
+
actionLedger,
|
|
2331
|
+
entityTimeline,
|
|
2332
|
+
vectorMemories,
|
|
2333
|
+
recentMessages,
|
|
2334
|
+
toolHistory,
|
|
2335
|
+
highlights,
|
|
2336
|
+
graph,
|
|
2337
|
+
diagnostics,
|
|
2338
|
+
adv,
|
|
2339
|
+
});
|
|
2340
|
+
const normalizedVectorMemories = vectorMemories.map((m) => {
|
|
2341
|
+
const scoreMeta = scoreMetaOf(m);
|
|
2342
|
+
const rawScore = scoreOf(m);
|
|
2343
|
+
const enriched = enrichContextMemory(m);
|
|
2344
|
+
return {
|
|
2345
|
+
memory: memoryText(m),
|
|
2346
|
+
score: adv.includeScores === false ? undefined : (rawScore !== undefined ? rawScore : scoreMeta.hybridScore ?? scoreMeta.recencyScore ?? 'unavailable'),
|
|
2347
|
+
semantic_score: adv.includeScores === false ? undefined : scoreMeta.semanticScore,
|
|
2348
|
+
recency_score: adv.includeScores === false ? undefined : scoreMeta.recencyScore,
|
|
2349
|
+
hybrid_score: adv.includeScores === false ? undefined : scoreMeta.hybridScore,
|
|
2350
|
+
source: scoreMeta.source,
|
|
2351
|
+
status: enriched.status,
|
|
2352
|
+
confidence: enriched.confidence,
|
|
2353
|
+
valid_until: enriched.valid_until,
|
|
2354
|
+
superseded_by: enriched.superseded_by,
|
|
2355
|
+
id: m.id || m.uuid || m.memory_id,
|
|
2356
|
+
created_at: m.created_at || m.createdAt,
|
|
2357
|
+
raw: m,
|
|
2358
|
+
};
|
|
2359
|
+
});
|
|
2360
|
+
const summary = vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map(memoryText).filter(Boolean);
|
|
2361
|
+
const contextText = payload.map((message) => String(message.content || '')).join('\n\n');
|
|
2362
|
+
const audit = {
|
|
2363
|
+
kind: 'elefai.brain.context.v1',
|
|
2364
|
+
userId: key,
|
|
2365
|
+
project: project || undefined,
|
|
2366
|
+
query,
|
|
2367
|
+
retrievalMode,
|
|
2368
|
+
payloadFormat,
|
|
2369
|
+
options: {
|
|
2370
|
+
includeContextHeader: adv.includeContextHeader !== false,
|
|
2371
|
+
includeSummary: adv.includeSummary !== false,
|
|
2372
|
+
includeScores: adv.includeScores !== false,
|
|
2373
|
+
includeDiagnostics: Boolean(adv.includeDiagnostics),
|
|
2374
|
+
includeRelations: adv.includeRelations !== false,
|
|
2375
|
+
includeToolHistory: adv.includeToolHistory !== false,
|
|
2376
|
+
includeToolResults: adv.includeToolResults !== false,
|
|
2377
|
+
includeRecentMessages: adv.includeRecentMessages !== false,
|
|
2378
|
+
includeRecentHighlights: adv.includeRecentHighlights !== false,
|
|
2379
|
+
includeOperationalState: adv.includeOperationalState !== false,
|
|
2380
|
+
includeActionLedger: adv.includeActionLedger !== false,
|
|
2381
|
+
includeEntityTimeline: adv.includeEntityTimeline !== false,
|
|
2382
|
+
includeWorkingMemory: adv.includeWorkingMemory !== false,
|
|
2383
|
+
includeDecisionState: adv.includeDecisionState !== false,
|
|
2384
|
+
includeMemoryCompression: adv.includeMemoryCompression !== false,
|
|
2385
|
+
includeContextHealth: true,
|
|
2386
|
+
},
|
|
2387
|
+
context: contextText,
|
|
2388
|
+
summary,
|
|
2389
|
+
connectedModelSummary,
|
|
2390
|
+
contextHealth,
|
|
2391
|
+
contextQualityScore: contextHealth.quality_score,
|
|
2392
|
+
workingMemory,
|
|
2393
|
+
decisionState,
|
|
2394
|
+
memoryCompression,
|
|
2395
|
+
profileFacts: renderProfileFacts(profileFacts),
|
|
2396
|
+
operationalState,
|
|
2397
|
+
actionLedger,
|
|
2398
|
+
entityTimeline,
|
|
2399
|
+
vectorMemories: normalizedVectorMemories,
|
|
2400
|
+
graph,
|
|
2401
|
+
recentMessages,
|
|
2402
|
+
recentHighlights: highlights,
|
|
2403
|
+
toolHistory: {
|
|
2404
|
+
enabled: adv.includeToolHistory !== false,
|
|
2405
|
+
items: adv.includeToolHistory === false ? [] : toolHistory.map((tool) => ({
|
|
2406
|
+
id: tool.id,
|
|
2407
|
+
call_id: tool.call_id || tool.id,
|
|
2408
|
+
turnId: tool.turnId,
|
|
2409
|
+
turn_id: tool.turn_id || tool.turnId,
|
|
2410
|
+
sequence: tool.sequence,
|
|
2411
|
+
name: tool.name,
|
|
2412
|
+
tool_name: tool.tool_name || tool.name,
|
|
2413
|
+
input: tool.input,
|
|
2414
|
+
tool_args: tool.tool_args || tool.input,
|
|
2415
|
+
normalized_args: tool.normalized_args || tool.input,
|
|
2416
|
+
ok: tool.ok,
|
|
2417
|
+
status: tool.status || (tool.ok === false ? 'failed' : 'ok'),
|
|
2418
|
+
at: tool.at,
|
|
2419
|
+
timestamp: tool.timestamp || tool.at,
|
|
2420
|
+
source: tool.source,
|
|
2421
|
+
result_hash: tool.result_hash || stableHash(tool.result || ''),
|
|
2422
|
+
args_hash: tool.args_hash || stableHash(tool.input || ''),
|
|
2423
|
+
dedupe_key: tool.dedupe_key || toolEventKey(tool),
|
|
2424
|
+
result: adv.includeToolResults === false ? undefined : tool.result,
|
|
2425
|
+
result_summary: adv.includeToolResults === false ? undefined : (tool.result_summary || tool.result),
|
|
2426
|
+
})),
|
|
2427
|
+
},
|
|
2428
|
+
diagnostics,
|
|
2429
|
+
};
|
|
2430
|
+
return {
|
|
2431
|
+
response: {
|
|
2432
|
+
[memoryKey]: payload,
|
|
2433
|
+
elefaiBrainContext: audit,
|
|
2434
|
+
elefaiBrainContextText: contextText,
|
|
2435
|
+
elefaiBrainSummary: summary,
|
|
2436
|
+
elefaiBrainConnectedModelSummary: connectedModelSummary,
|
|
2437
|
+
elefaiBrainContextHealth: contextHealth,
|
|
2438
|
+
elefaiBrainContextQualityScore: contextHealth.quality_score,
|
|
2439
|
+
elefaiBrainWorkingMemory: workingMemory,
|
|
2440
|
+
elefaiBrainDecisionState: decisionState,
|
|
2441
|
+
elefaiBrainMemoryCompression: memoryCompression,
|
|
2442
|
+
elefaiBrainProfileFacts: renderProfileFacts(profileFacts),
|
|
2443
|
+
elefaiBrainOperationalState: operationalState,
|
|
2444
|
+
elefaiBrainActionLedger: actionLedger,
|
|
2445
|
+
elefaiBrainEntityTimeline: entityTimeline,
|
|
2446
|
+
elefaiBrainVectorMemories: normalizedVectorMemories,
|
|
2447
|
+
elefaiBrainGraph: graph,
|
|
2448
|
+
elefaiBrainRecentMessages: recentMessages,
|
|
2449
|
+
elefaiBrainRecentHighlights: highlights,
|
|
2450
|
+
elefaiBrainToolHistory: audit.toolHistory,
|
|
2451
|
+
elefaiBrainDiagnostics: diagnostics,
|
|
2452
|
+
},
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
async execute() {
|
|
2456
|
+
const items = this.getInputData();
|
|
2457
|
+
const returnData = [];
|
|
2458
|
+
const count = Math.max(items.length, 1);
|
|
2459
|
+
for (let i = 0; i < count; i++) {
|
|
2460
|
+
const memoryKey = this.getNodeParameter('memoryKey', i);
|
|
2461
|
+
const retrievalMode = this.getNodeParameter('retrievalMode', i);
|
|
2462
|
+
const threadId = this.getNodeParameter('threadId', i);
|
|
2463
|
+
const project = this.getNodeParameter('project', i, '');
|
|
2464
|
+
const adv = applyOperationalPreset(this.getNodeParameter('advanced', i, {}) || {});
|
|
2465
|
+
const key = userKeyFrom(threadId, adv, project);
|
|
2466
|
+
const query = asSearchQuery(this.getNodeParameter('query', i, ''));
|
|
2467
|
+
const connectedEmbedding = await getConnectedEmbedding(this, i);
|
|
2468
|
+
let payload;
|
|
2469
|
+
if (retrievalMode === 'semantic' || retrievalMode === 'semanticV2') {
|
|
2470
|
+
const body = { query: String(query || '') };
|
|
2471
|
+
body.user_id = key;
|
|
2472
|
+
if (adv.agentId)
|
|
2473
|
+
body.agent_id = String(adv.agentId);
|
|
2474
|
+
if (adv.appId)
|
|
2475
|
+
body.app_id = String(adv.appId);
|
|
2476
|
+
if (adv.runId)
|
|
2477
|
+
body.run_id = String(adv.runId);
|
|
2478
|
+
if (adv.topK)
|
|
2479
|
+
body.top_k = Number(adv.topK);
|
|
2480
|
+
if (adv.rerank !== undefined)
|
|
2481
|
+
body.rerank = Boolean(adv.rerank);
|
|
2482
|
+
if (typeof adv.fields === 'string' && adv.fields)
|
|
2483
|
+
body.fields = String(adv.fields).split(',').map((f) => f.trim());
|
|
2484
|
+
if (retrievalMode === 'semanticV2') {
|
|
2485
|
+
try {
|
|
2486
|
+
const filters = typeof adv.filters === 'string' ? JSON.parse(adv.filters) : (adv.filters || {});
|
|
2487
|
+
body.filters = filters;
|
|
2488
|
+
}
|
|
2489
|
+
catch { }
|
|
2490
|
+
}
|
|
2491
|
+
const url = retrievalMode === 'semanticV2' ? '/v2/memories/search/' : '/v1/memories/search/';
|
|
2492
|
+
const res = connectedEmbedding
|
|
2493
|
+
? await searchClientVectorMemories(this, connectedEmbedding, query, body)
|
|
2494
|
+
: await GenericFunctions_1.mem0ApiRequest.call(this, 'POST', url, body);
|
|
2495
|
+
const memories = Array.isArray(res) ? res : [res];
|
|
2496
|
+
payload = memories.map((m) => { var _a, _b; return ({ role: 'system', content: (_b = (_a = m.memory) !== null && _a !== void 0 ? _a : m.text) !== null && _b !== void 0 ? _b : JSON.stringify(m) }); });
|
|
2497
|
+
}
|
|
2498
|
+
else {
|
|
2499
|
+
const qs = {};
|
|
2500
|
+
qs.user_id = key;
|
|
2501
|
+
if (adv.agentId)
|
|
2502
|
+
qs.agent_id = String(adv.agentId);
|
|
2503
|
+
if (adv.appId)
|
|
2504
|
+
qs.app_id = String(adv.appId);
|
|
2505
|
+
if (adv.runId)
|
|
2506
|
+
qs.run_id = String(adv.runId);
|
|
2507
|
+
const res = await GenericFunctions_1.mem0ApiRequest.call(this, 'GET', '/v1/memories/', {}, qs);
|
|
2508
|
+
let memories = Array.isArray(res) ? res : [res];
|
|
2509
|
+
if (adv.lastN && Number(adv.lastN) > 0)
|
|
2510
|
+
memories = memories.slice(-Number(adv.lastN));
|
|
2511
|
+
if (retrievalMode === 'summary') {
|
|
2512
|
+
const text = memories
|
|
2513
|
+
.map((m) => m.memory || m.text || m.value)
|
|
2514
|
+
.filter(Boolean)
|
|
2515
|
+
.join('\n');
|
|
2516
|
+
payload = [{ role: 'system', content: `Summary of memories:\n${text}` }];
|
|
2517
|
+
}
|
|
2518
|
+
else {
|
|
2519
|
+
payload = memories.map((m) => { var _a, _b; return ({ role: 'system', content: (_b = (_a = m.memory) !== null && _a !== void 0 ? _a : m.text) !== null && _b !== void 0 ? _b : JSON.stringify(m) }); });
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
const json = {};
|
|
2523
|
+
json[memoryKey] = payload;
|
|
2524
|
+
returnData.push({ json });
|
|
2525
|
+
}
|
|
2526
|
+
return [returnData];
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
exports.Mem0Memory = Mem0Memory;
|
|
2530
|
+
exports.__private = {
|
|
2531
|
+
extractToolCallsFromText,
|
|
2532
|
+
extractToolCalls,
|
|
2533
|
+
toolHistoryItemsFromMemory,
|
|
2534
|
+
toolHistoryFromMemory,
|
|
2535
|
+
recentMessageFromMemory,
|
|
2536
|
+
dedupeToolHistory,
|
|
2537
|
+
applyToolHistoryWindow,
|
|
2538
|
+
dedupeRecentMessages,
|
|
2539
|
+
parseToolHistoryMarker,
|
|
2540
|
+
parseRecentMessageMarker,
|
|
2541
|
+
encodeToolCall,
|
|
2542
|
+
encodeRecentMessage,
|
|
2543
|
+
userKeyFrom,
|
|
2544
|
+
applyOperationalPreset,
|
|
2545
|
+
asSearchQuery,
|
|
2546
|
+
canonicalToolInput,
|
|
2547
|
+
buildContextMessages,
|
|
2548
|
+
inferToolGuard,
|
|
2549
|
+
inferUserIntent,
|
|
2550
|
+
deriveWorkingMemory,
|
|
2551
|
+
deriveDecisionState,
|
|
2552
|
+
deriveMemoryCompression,
|
|
2553
|
+
deriveContextHealth,
|
|
2554
|
+
enrichContextMemory,
|
|
2555
|
+
deriveOperationalState,
|
|
2556
|
+
deriveActionLedger,
|
|
2557
|
+
deriveEntityTimeline,
|
|
2558
|
+
scoreOf,
|
|
2559
|
+
scoreMetaOf,
|
|
2560
|
+
extractProfileFactsFromText,
|
|
2561
|
+
mergeProfileFacts,
|
|
2562
|
+
renderProfileFacts,
|
|
2563
|
+
isNoisyProfileValue,
|
|
2564
|
+
};
|